Support downloading game & voicepacks and install, bump to 1.1.0

In CLI too, also optimized code & added test for voiceover functions.
This commit is contained in:
2022-02-18 11:11:55 +07:00
parent 07ba17b576
commit bb37e4554d
5 changed files with 194 additions and 26 deletions

View File

@ -1,13 +1,14 @@
import re
import shutil
import aiohttp
import appdirs
import zipfile
import warnings
import json
from pathlib import Path
from configparser import ConfigParser
from aiopath import AsyncPath
from worthless import constants
from worthless.launcher import Launcher
@ -75,11 +76,12 @@ class Installer:
self._gamedir = gamedir
if not data_dir:
self._appdirs = appdirs.AppDirs(constants.APP_NAME, constants.APP_AUTHOR)
self._temp_path = Path(self._appdirs.user_cache_dir).joinpath("Installer")
self.temp_path = Path(self._appdirs.user_cache_dir).joinpath("Installer")
else:
if not isinstance(data_dir, Path):
data_dir = Path(data_dir)
self._temp_path = data_dir.joinpath("Temp/Installer/")
self.temp_path = data_dir.joinpath("Temp/Installer/")
self.temp_path.mkdir(parents=True, exist_ok=True)
config_file = self._gamedir.joinpath("config.ini")
self._config_file = config_file.resolve()
self._version = None
@ -87,6 +89,33 @@ class Installer:
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
self._version = self.get_game_version()
async def _download_file(self, file_url: str, file_name: str, file_len: int = None):
"""
Download file name to temporary directory,
:param file_url:
:param file_name:
:return:
"""
params = {}
file_path = AsyncPath(self.temp_path).joinpath(file_name)
if file_path.exists():
async with file_path.open("rb") as f:
cur_len = len(await f.read())
params |= {
"Range": f"bytes={cur_len}-{file_len if file_len else ''}"
}
else:
await file_path.touch()
async with aiohttp.ClientSession() as session:
rsp = await session.get(file_url, params=params, timeout=None)
rsp.raise_for_status()
while True:
chunk = await rsp.content.read(8192)
if not chunk:
break
async with file_path.open("ab") as f:
await f.write(chunk)
def get_game_archive_version(self, game_archive: str | Path):
if not game_archive.exists():
raise FileNotFoundError(f"Game archive {game_archive} not found")
@ -110,6 +139,7 @@ class Installer:
return "zh-cn"
case "Korean":
return "ko-kr"
return lang
@staticmethod
def get_voiceover_archive_language(voiceover_archive: str | Path):
@ -174,20 +204,53 @@ class Installer:
archive.extractall(self._gamedir, members=files)
archive.close()
# Update game version on local variable.
self._version = self.get_game_version()
async def download_game_update(self):
if self._version is None:
raise ValueError("Game version not found, use install_game to install the game.")
async def download_full_game(self):
archive = await self._launcher.get_resource_info()
if archive is None:
raise RuntimeError("Failed to fetch game resource info.")
if self._version == archive.game.latest.version:
raise ValueError("Game is already up to date.")
await self._download_file(archive.game.latest.path, archive.game.latest.name, archive.game.latest.size)
async def download_full_voiceover(self, language: str):
archive = await self._launcher.get_resource_info()
if archive is None:
raise RuntimeError("Failed to fetch game resource info.")
translated_lang = self.voiceover_lang_translate(language)
for vo in archive.game.latest.voice_packs:
if vo.language == translated_lang:
await self._download_file(vo.path, vo.name, vo.size)
async def download_game_update(self, from_version: str = None):
if not from_version:
self._version = from_version
if not from_version:
raise ValueError("Game version not found")
version_info = await self._launcher.get_resource_info()
if version_info is None:
raise RuntimeError("Failed to fetch game resource info.")
if self._version == version_info.game.latest.version:
raise ValueError("Game is already up to date.")
diff_archive = self.get_game_diff_archive()
diff_archive = await self.get_game_diff_archive(from_version)
if diff_archive is None:
raise ValueError("Game diff archive is not available for this version, please reinstall.")
# TODO: Download the diff archive
raise NotImplementedError("Downloading game diff archive is not implemented yet.")
await self._download_file(diff_archive.path, diff_archive.name, diff_archive.size)
async def download_voiceover_update(self, language: str, from_version: str = None):
if not from_version:
self._version = from_version
if not from_version:
raise ValueError("Game version not found, use install_game to install the game.")
version_info = await self._launcher.get_resource_info()
if version_info is None:
raise RuntimeError("Failed to fetch game resource info.")
diff_archive = await self.get_voiceover_diff_archive(language, from_version)
if diff_archive is None:
raise ValueError("Voiceover diff archive is not available for this version, please reinstall.")
await self._download_file(diff_archive.path, diff_archive.name, diff_archive.size)
def uninstall_game(self):
shutil.rmtree(self._gamedir)
@ -211,6 +274,30 @@ class Installer:
archive.extractall(self._gamedir)
archive.close()
async def get_voiceover_diff_archive(self, lang: str, from_version: str = None):
"""Gets a diff archive from `from_version` to the latest one
If from_version is not specified, it will be taken from the game version.
"""
if not from_version:
if self._version:
from_version = self._version
else:
from_version = self._version = self.get_game_version()
if not from_version:
raise ValueError("No game version found")
game_resource = await self._launcher.get_resource_info()
if not game_resource:
raise ValueError("Could not fetch game resource")
translated_lang = self.voiceover_lang_translate(lang)
for v in game_resource.game.diffs:
if v.version != from_version:
continue
for vo in v.voice_packs:
if vo.language != translated_lang:
continue
return vo
async def get_game_diff_archive(self, from_version: str = None):
"""Gets a diff archive from `from_version` to the latest one