diff --git a/src/Starward/Services/Download/InstallGameService.cs b/src/Starward/Services/Download/InstallGameService.cs index 53ee93ab9..6a609058f 100644 --- a/src/Starward/Services/Download/InstallGameService.cs +++ b/src/Starward/Services/Download/InstallGameService.cs @@ -51,39 +51,56 @@ public InstallGameService(ILogger logger, HttpClient httpCli + + public GameBiz CurrentGameBiz { get; protected set; } + private InstallGameState _state; + public InstallGameState State + { + get => _state; + set + { + _state = value; + StateChanged?.Invoke(this, value); + } + } - public virtual bool CanRepairGameFiles { get; } + public EventHandler StateChanged; + public EventHandler InstallFailed; - public virtual bool CanRepairAudioFiles { get; } + protected void OnInstallFailed(Exception ex) + { + _pausedState = State; + State = InstallGameState.Error; + InstallFailed?.Invoke(this, ex); + } - public EventHandler InstallGameStateChanged; - public EventHandler Error; - public InstallGameState State { get; protected set; } + #region Internal Property - protected InstallGameState _pausedState; + + protected bool _initialized; protected string _installPath; - protected bool _initialized; + protected InstallGameTask _installTask; - protected InstallGameTask _installTask; + protected InstallGameState _pausedState; protected GamePackage _gamePackage; @@ -111,6 +128,15 @@ public InstallGameService(ILogger logger, HttpClient httpCli + #endregion + + + + + + + #region Initialize & Start + @@ -140,66 +166,12 @@ public void Initialize(GameBiz gameBiz, string installPath) public virtual async Task StartInstallGameAsync(CancellationToken cancellationToken = default) { _gamePackage = await _packageService.GetGamePackageAsync(CurrentGameBiz); - _gamePackageItems = new List(); - foreach (var item in _gamePackage.Main.Major!.GamePackages) - { - _gamePackageItems.Add(new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = Path.GetFileName(item.Url), - Path = Path.Combine(_installPath, Path.GetFileName(item.Url)), - Url = item.Url, - MD5 = item.MD5, - Size = item.Size, - DecompressedSize = item.DecompressedSize, - WriteAsTempFile = true, - }); - } - _audioPackageItems = new List(); - foreach (var item in await GetAudioPackageFilesFromGameResourceAsync(_gamePackage.Main.Major!)) - { - _audioPackageItems.Add(new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = Path.GetFileName(item.Url), - Path = Path.Combine(_installPath, Path.GetFileName(item.Url)), - Url = item.Url, - MD5 = item.MD5, - Size = item.Size, - DecompressedSize = item.DecompressedSize, - WriteAsTempFile = true, - }); - } - _installTask = InstallGameTask.Install; - foreach (var item in _gamePackageItems) - { - _installItemQueue.Enqueue(item); - } - foreach (var item in _audioPackageItems) - { - _installItemQueue.Enqueue(item); - } + await PrepareDownloadGamePackageResourceAsync(_gamePackage.Main.Major!); if (CurrentGameBiz.IsBilibili()) { - _channelSDK = await _hoYoPlayService.GetGameChannelSDKAsync(CurrentGameBiz); - if (_channelSDK is not null) - { - string name = Path.GetFileName(_channelSDK.ChannelSDKPackage.Url); - _gameSDKItem = new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = name, - Path = Path.Combine(_installPath, name), - MD5 = _channelSDK.ChannelSDKPackage.MD5, - Size = _channelSDK.ChannelSDKPackage.Size, - DecompressedSize = _channelSDK.ChannelSDKPackage.DecompressedSize, - Url = _channelSDK.ChannelSDKPackage.Url, - WriteAsTempFile = true, - }; - _installItemQueue.Enqueue(_gameSDKItem); - } + await PrepareBilibiliChannelSDKAsync(InstallGameItemType.Download); } - + _installTask = InstallGameTask.Install; StartTask(InstallGameState.Download); } @@ -223,23 +195,7 @@ public virtual async Task StartRepairGameAsync(CancellationToken cancellationTok } if (CurrentGameBiz.IsBilibili()) { - _channelSDK = await _hoYoPlayService.GetGameChannelSDKAsync(CurrentGameBiz); - if (_channelSDK is not null) - { - string name = Path.GetFileName(_channelSDK.ChannelSDKPackage.Url); - _gameSDKItem = new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = name, - Path = Path.Combine(_installPath, name), - MD5 = _channelSDK.ChannelSDKPackage.MD5, - Size = _channelSDK.ChannelSDKPackage.Size, - DecompressedSize = _channelSDK.ChannelSDKPackage.DecompressedSize, - Url = _channelSDK.ChannelSDKPackage.Url, - WriteAsTempFile = false, - }; - _installItemQueue.Enqueue(_gameSDKItem); - } + await PrepareBilibiliChannelSDKAsync(InstallGameItemType.Verify); } StartTask(InstallGameState.Verify); } @@ -264,45 +220,8 @@ public virtual async Task StartPredownloadAsync(CancellationToken cancellationTo { resource = _gamePackage.PreDownload.Major!; } - _gamePackageItems = new List(); - foreach (var item in resource.GamePackages) - { - _gamePackageItems.Add(new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = Path.GetFileName(item.Url), - Path = Path.Combine(_installPath, Path.GetFileName(item.Url)), - Url = item.Url, - MD5 = item.MD5, - Size = item.Size, - DecompressedSize = item.DecompressedSize, - WriteAsTempFile = true, - }); - } - _audioPackageItems = new List(); - foreach (var item in await GetAudioPackageFilesFromGameResourceAsync(resource)) - { - _audioPackageItems.Add(new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = Path.GetFileName(item.Url), - Path = Path.Combine(_installPath, Path.GetFileName(item.Url)), - Url = item.Url, - MD5 = item.MD5, - Size = item.Size, - DecompressedSize = item.DecompressedSize, - WriteAsTempFile = true, - }); - } + await PrepareDownloadGamePackageResourceAsync(resource); _installTask = InstallGameTask.Predownload; - foreach (var item in _gamePackageItems) - { - _installItemQueue.Enqueue(item); - } - foreach (var item in _audioPackageItems) - { - _installItemQueue.Enqueue(item); - } StartTask(InstallGameState.Download); } @@ -322,37 +241,29 @@ public virtual async Task StartUpdateGameAsync(CancellationToken cancellationTok { resource = _gamePackage.Main.Major!; } + await PrepareDownloadGamePackageResourceAsync(resource); + if (CurrentGameBiz.IsBilibili()) + { + await PrepareBilibiliChannelSDKAsync(InstallGameItemType.Download); + } + _installTask = InstallGameTask.Update; + StartTask(InstallGameState.Download); + } + + + + protected async Task PrepareDownloadGamePackageResourceAsync(GamePackageResource resource) + { _gamePackageItems = new List(); foreach (var item in resource.GamePackages) { - _gamePackageItems.Add(new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = Path.GetFileName(item.Url), - Path = Path.Combine(_installPath, Path.GetFileName(item.Url)), - Url = item.Url, - MD5 = item.MD5, - Size = item.Size, - DecompressedSize = item.DecompressedSize, - WriteAsTempFile = true, - }); + _gamePackageItems.Add(GamePackageFileToInstallGameItem(item)); } _audioPackageItems = new List(); foreach (var item in await GetAudioPackageFilesFromGameResourceAsync(resource)) { - _audioPackageItems.Add(new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = Path.GetFileName(item.Url), - Path = Path.Combine(_installPath, Path.GetFileName(item.Url)), - Url = item.Url, - MD5 = item.MD5, - Size = item.Size, - DecompressedSize = item.DecompressedSize, - WriteAsTempFile = true, - }); + _audioPackageItems.Add(GamePackageFileToInstallGameItem(item)); } - _installTask = InstallGameTask.Update; foreach (var item in _gamePackageItems) { _installItemQueue.Enqueue(item); @@ -361,33 +272,33 @@ public virtual async Task StartUpdateGameAsync(CancellationToken cancellationTok { _installItemQueue.Enqueue(item); } - if (CurrentGameBiz.IsBilibili()) + } + + + + protected async Task PrepareBilibiliChannelSDKAsync(InstallGameItemType type) + { + _channelSDK = await _hoYoPlayService.GetGameChannelSDKAsync(CurrentGameBiz); + if (_channelSDK is not null) { - _channelSDK = await _hoYoPlayService.GetGameChannelSDKAsync(CurrentGameBiz); - if (_channelSDK is not null) + string name = Path.GetFileName(_channelSDK.ChannelSDKPackage.Url); + _gameSDKItem = new InstallGameItem { - string name = Path.GetFileName(_channelSDK.ChannelSDKPackage.Url); - _gameSDKItem = new InstallGameItem - { - Type = InstallGameItemType.Download, - FileName = name, - Path = Path.Combine(_installPath, name), - MD5 = _channelSDK.ChannelSDKPackage.MD5, - Size = _channelSDK.ChannelSDKPackage.Size, - DecompressedSize = _channelSDK.ChannelSDKPackage.DecompressedSize, - Url = _channelSDK.ChannelSDKPackage.Url, - WriteAsTempFile = true, - }; - _installItemQueue.Enqueue(_gameSDKItem); - } + Type = type, + FileName = name, + Path = Path.Combine(_installPath, name), + MD5 = _channelSDK.ChannelSDKPackage.MD5, + Size = _channelSDK.ChannelSDKPackage.Size, + DecompressedSize = _channelSDK.ChannelSDKPackage.DecompressedSize, + Url = _channelSDK.ChannelSDKPackage.Url, + WriteAsTempFile = true, + }; + _installItemQueue.Enqueue(_gameSDKItem); } - StartTask(InstallGameState.Download); } - - protected async Task> GetAudioPackageFilesFromGameResourceAsync(GamePackageResource resource) { string lang = await GetAudioLanguageAsync(); @@ -409,8 +320,6 @@ protected async Task> GetAudioPackageFilesFromGameResource - - protected async Task GetAudioLanguageAsync() { var sb = new StringBuilder(); @@ -451,6 +360,23 @@ protected async Task SetAudioLanguageAsync(string lang) + protected InstallGameItem GamePackageFileToInstallGameItem(GamePackageFile file) + { + return new InstallGameItem + { + Type = InstallGameItemType.Download, + FileName = Path.GetFileName(file.Url), + Path = Path.Combine(_installPath, Path.GetFileName(file.Url)), + Url = file.Url, + MD5 = file.MD5, + Size = file.Size, + DecompressedSize = file.DecompressedSize, + WriteAsTempFile = true, + }; + } + + + protected async Task> GetPkgVersionsAsync(string prefix, string pkgName) { prefix = prefix.TrimEnd('/') + '/'; @@ -480,83 +406,101 @@ protected async Task> GetPkgVersionsAsync(string prefix, s - protected async Task CleanGameDeprecatedFilesAsync() + + #endregion + + + + + + + #region Control Method + + + + + public void Continue() { - State = InstallGameState.Clean; - foreach (var file in Directory.GetFiles(_installPath, "*_tmp", SearchOption.AllDirectories)) + try { - File.Delete(file); + Debug.WriteLine("Continue"); + StartTask(_pausedState); } - foreach (var file in await _hoYoPlayService.GetGameDeprecatedFilesAsync(CurrentGameBiz)) + catch (Exception ex) { - var path = Path.Combine(_installPath, file.Name); - if (File.Exists(path)) - { - File.Delete(path); - } + _logger.LogError(ex, nameof(Continue)); } - await WriteConfigFileAsync(); - CurrentTaskFinished(); } - protected async Task WriteConfigFileAsync() + + public void Pause() { - string version = _gamePackage.Main.Major?.Version ?? ""; - string sdk_version = _channelSDK?.Version ?? ""; - string cps = "", channel = "1", sub_channel = "1"; - if (CurrentGameBiz.IsBilibili()) - { - cps = "bilibili"; - channel = "14"; - sub_channel = "0"; - } - else if (CurrentGameBiz.IsChinaOfficial()) + try { - cps = "mihoyo"; + Debug.WriteLine("Pause"); + _pausedState = State; + State = InstallGameState.None; + _cancellationTokenSource?.Cancel(); } - else if (CurrentGameBiz.IsGlobalOfficial()) + catch (Exception ex) { - cps = "hoyoverse"; + _logger.LogError(ex, nameof(Pause)); } - string config = $""" - [General] - channel={channel} - cps={cps} - game_version={version} - sub_channel={sub_channel} - sdk_version={sdk_version} - game_biz={CurrentGameBiz} - """; - _logger.LogInformation("Write config.ini (game_version={version})", version); - await File.WriteAllTextAsync(Path.Combine(_installPath, "config.ini"), config).ConfigureAwait(false); } - protected void Finish() + + + public void ClearState() { - State = InstallGameState.Finish; - InstallGameStateChanged?.Invoke(this, InstallGameState.Finish); + try + { + + } + catch (Exception ex) + { + _logger.LogError(ex, nameof(ClearState)); + } } + + #endregion + + + + + + + #region State Convert + + + + protected void StartTask(InstallGameState state) { if (state is InstallGameState.Download) { + _totalCount = _installItemQueue.Count; + _finishCount = 0; _totalBytes = _installItemQueue.Sum(x => x.Size); _finishBytes = _installItemQueue.Sum(GetFileLength); } else if (state is InstallGameState.Verify) { + _totalCount = _installItemQueue.Count; + _finishCount = 0; _totalBytes = _installItemQueue.Sum(x => x.Size); _finishBytes = 0; } else if (state is InstallGameState.Decompress) { + _totalCount = _installItemQueue.Count; + _finishCount = 0; _totalBytes = _installItemQueue.Sum(x => x.Size); _finishBytes = 0; } @@ -574,7 +518,7 @@ protected void StartTask(InstallGameState state) _cancellationTokenSource = new CancellationTokenSource(); for (int i = 0; i < Environment.ProcessorCount; i++) { - _ = ExecuteTaskAsync(_cancellationTokenSource.Token); + _ = ExecuteTaskItemAsync(_cancellationTokenSource.Token); } } @@ -598,57 +542,6 @@ private static long GetFileLength(InstallGameItem item) - public void Continue() - { - try - { - Debug.WriteLine("Continue"); - StartTask(_pausedState); - } - catch (Exception ex) - { - _logger.LogError(ex, nameof(Continue)); - } - } - - - - - public void Pause() - { - try - { - Debug.WriteLine("Pause"); - _pausedState = State; - State = InstallGameState.None; - _cancellationTokenSource?.Cancel(); - } - catch (Exception ex) - { - _logger.LogError(ex, nameof(Pause)); - } - } - - - - - - public void ClearState() - { - try - { - - } - catch (Exception ex) - { - _logger.LogError(ex, nameof(ClearState)); - } - } - - - - - private void CurrentTaskFinished() { try @@ -669,7 +562,7 @@ private void CurrentTaskFinished() catch (Exception ex) { _logger.LogError(ex, nameof(CurrentTaskFinished)); - Error?.Invoke(this, ex); + OnInstallFailed(ex); } } @@ -837,14 +730,79 @@ protected void FromVerifyToDecompress() + protected async Task CleanGameDeprecatedFilesAsync() + { + State = InstallGameState.Clean; + foreach (var file in Directory.GetFiles(_installPath, "*_tmp", SearchOption.AllDirectories)) + { + File.Delete(file); + } + foreach (var file in await _hoYoPlayService.GetGameDeprecatedFilesAsync(CurrentGameBiz)) + { + var path = Path.Combine(_installPath, file.Name); + if (File.Exists(path)) + { + File.Delete(path); + } + } + await WriteConfigFileAsync(); + CurrentTaskFinished(); + } + + + + protected async Task WriteConfigFileAsync() + { + string version = _gamePackage.Main.Major?.Version ?? ""; + string sdk_version = _channelSDK?.Version ?? ""; + string cps = "", channel = "1", sub_channel = "1"; + if (CurrentGameBiz.IsBilibili()) + { + cps = "bilibili"; + channel = "14"; + sub_channel = "0"; + } + else if (CurrentGameBiz.IsChinaOfficial()) + { + cps = "mihoyo"; + } + else if (CurrentGameBiz.IsGlobalOfficial()) + { + cps = "hoyoverse"; + } + string config = $""" + [General] + channel={channel} + cps={cps} + game_version={version} + sub_channel={sub_channel} + sdk_version={sdk_version} + game_biz={CurrentGameBiz} + """; + _logger.LogInformation("Write config.ini (game_version={version})", version); + await File.WriteAllTextAsync(Path.Combine(_installPath, "config.ini"), config).ConfigureAwait(false); + } + + + + protected void Finish() + { + State = InstallGameState.Finish; + StateChanged?.Invoke(this, InstallGameState.Finish); + } + + #endregion + + - #region Install Internal + + #region Execute Item (Download, Verify, Decompress, Patch) @@ -883,8 +841,7 @@ protected void FromVerifyToDecompress() - - protected async Task ExecuteTaskAsync(CancellationToken cancellationToken = default) + protected async Task ExecuteTaskItemAsync(CancellationToken cancellationToken = default) { try { @@ -904,12 +861,15 @@ protected async Task ExecuteTaskAsync(CancellationToken cancellationToken = defa break; case InstallGameItemType.Download: await DownloadItemAsync(item, cancellationToken); + Interlocked.Increment(ref _finishCount); break; case InstallGameItemType.Verify: await VerifyItemAsync(item, cancellationToken); + Interlocked.Increment(ref _finishCount); break; case InstallGameItemType.Decompress: await DecompressItemAsync(item, cancellationToken); + Interlocked.Increment(ref _finishCount); break; default: break; @@ -919,7 +879,7 @@ protected async Task ExecuteTaskAsync(CancellationToken cancellationToken = defa { // network error _installItemQueue.Enqueue(item); - await Task.Delay(1000); + await Task.Delay(1000, CancellationToken.None); } catch (Exception ex) when (ex is OperationCanceledException or TaskCanceledException) { @@ -931,9 +891,8 @@ protected async Task ExecuteTaskAsync(CancellationToken cancellationToken = defa } catch (Exception ex) { - State = InstallGameState.Error; - _logger.LogError(ex, nameof(ExecuteTaskAsync)); - Error?.Invoke(this, ex); + _logger.LogError(ex, nameof(ExecuteTaskItemAsync)); + OnInstallFailed(ex); } finally {