public async Task RunAsync(PatchInfo[] toUpdate, PatchCache patchCache, CancellationToken ct = default) { Progress.Progress = 0; Progress.IsIndeterminate = true; Progress.CompletedCount = 0; Progress.TotalCount = 0; if (toUpdate.Length == 0) { return; } var state = new ProcessState { AtomicIndex = 0, AtomicCompletedCount = 0, AtomicProcessCount = 0, UpdateBuckets = new ConcurrentQueue <List <PatchCacheEntry> >() }; var exceptions = new ConcurrentBag <Exception>(); var errorCancellationTokenSource = new CancellationTokenSource(); var processCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(errorCancellationTokenSource.Token, ct); Progress.TotalCount = toUpdate.Length; App.Current.Logger.Info(nameof(VerifyFilesPhase), "Starting processing threads"); Progress.IsIndeterminate = false; // TODO: processor affinity? var threads = Enumerable.Range(0, Environment.ProcessorCount).Select(i => { var thread = new Thread(() => { Interlocked.Increment(ref state.AtomicProcessCount); try { App.Current.Logger.Info(nameof(VerifyFilesPhase), $"Processing thread {i} started"); _ProcessAsync(state, toUpdate, processCancellationTokenSource.Token).GetAwaiter().GetResult(); } catch (OperationCanceledException ex) { App.Current.Logger.Error(nameof(VerifyFilesPhase), $"Processing thread {i} canceled", ex); } catch (Exception ex) { App.Current.Logger.Error(nameof(VerifyFilesPhase), $"Exception in processing thread {i}", ex); exceptions.Add(ex); errorCancellationTokenSource.Cancel(); } finally { Interlocked.Decrement(ref state.AtomicProcessCount); App.Current.Logger.Info(nameof(VerifyFilesPhase), $"Processing thread {i} ended"); } }); thread.Name = $"{nameof(VerifyFilesPhase)}({i})"; thread.Start(); return(thread); }).ToArray(); await Task.Run(async() => { while (state.AtomicProcessCount > 0 || state.UpdateBuckets.Count != 0) { await Task.Delay(250, ct); Progress.Progress = state.AtomicCompletedCount / (double)toUpdate.Length; Progress.CompletedCount = state.AtomicCompletedCount; if (state.UpdateBuckets.Count == 0) { continue; } while (state.UpdateBuckets.TryDequeue(out var list)) { if (list.Count > 0) { await patchCache.InsertUnderTransactionAsync(list); } } } }); Progress.IsIndeterminate = true; App.Current.Logger.Info(nameof(VerifyFilesPhase), "Joining processing threads"); foreach (var t in threads) { await Task.Factory.StartNew(() => t.Join(), TaskCreationOptions.LongRunning); } if (exceptions.Count > 0) { var aggregate = new AggregateException("Error verifying files", exceptions); App.Current.Logger.Error(nameof(VerifyFilesPhase), "Error verifying files", aggregate); throw aggregate; } }
public async Task RunAsync(PatchInfo[] toUpdate, PatchCache patchCache, CancellationToken ct = default) { Progress.Progress = 0; Progress.IsIndeterminate = true; Progress.CompletedCount = 0; Progress.TotalCount = 0; if (toUpdate.Length == 0) { return; } var state = new ProcessState { AtomicIndex = 0, AtomicCompletedCount = 0, AtomicProcessCount = 0, UpdateBuckets = new ConcurrentQueue <List <PatchCacheEntry> >() }; var processCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct); Progress.TotalCount = toUpdate.Length; App.Logger.Info(nameof(VerifyFilesPhase), "Starting processing threads"); Progress.IsIndeterminate = false; // TODO: processor affinity? var threadTasks = Enumerable.Range(0, Environment.ProcessorCount) .Select(i => ConcurrencyUtils.RunOnDedicatedThreadAsync(() => { Interlocked.Increment(ref state.AtomicProcessCount); try { App.Logger.Info(nameof(VerifyFilesPhase), $"Processing thread {i} started"); _Process(state, toUpdate, processCancellationTokenSource.Token); } catch (OperationCanceledException) { App.Logger.Error(nameof(VerifyFilesPhase), $"Processing thread {i} canceled"); processCancellationTokenSource.Cancel(); throw; } catch (Exception ex) { App.Logger.Error(nameof(VerifyFilesPhase), $"Exception in processing thread {i}", ex); processCancellationTokenSource.Cancel(); throw; } finally { Interlocked.Decrement(ref state.AtomicProcessCount); App.Logger.Info(nameof(VerifyFilesPhase), $"Processing thread {i} ended"); } }, $"{nameof(VerifyFilesPhase)}({i})")).ToArray(); await Task.Run(async() => { while (state.AtomicProcessCount > 0 || state.UpdateBuckets.Count != 0) { await Task.Delay(250, ct); Progress.Progress = state.AtomicCompletedCount / (double)toUpdate.Length; Progress.CompletedCount = state.AtomicCompletedCount; if (state.UpdateBuckets.Count == 0) { continue; } while (state.UpdateBuckets.TryDequeue(out var list)) { if (list.Count > 0) { await patchCache.InsertUnderTransactionAsync(list); } } } }); Progress.IsIndeterminate = true; App.Logger.Info(nameof(VerifyFilesPhase), "Joining processing threads"); try { await Task.WhenAll(threadTasks); } catch (Exception ex) { App.Logger.Error(nameof(VerifyFilesPhase), "Error verifying files", ex); throw; } }
private async Task _InstallAsync(PatchCache patchCache, PluginInfo pluginInfo, CancellationToken ct = default) { App.Current.Logger.Info(nameof(EnglishPatchPhase), "Downloading english translation information"); var translation = await TranslationInfo.FetchEnglishAsync(ct); App.Current.Logger.Info(nameof(EnglishPatchPhase), "Getting data from patch cache"); var cacheData = await patchCache.SelectAllAsync(); string CreateRelativePath(string path) { var root = new Uri(_InstallConfiguration.PSO2BinDirectory); var relative = root.MakeRelativeUri(new Uri(path)); return(relative.OriginalString); } bool Verify(string path, string hash) { var relative = CreateRelativePath(path); var info = new FileInfo(path); if (info.Exists == false) { return(false); } if (cacheData.ContainsKey(relative) == false) { return(false); } var data = cacheData[relative]; if (data.Hash != hash) { return(false); } if (data.LastWriteTime != info.LastWriteTimeUtc.ToFileTimeUtc()) { return(false); } return(true); } using (var client = new ArksLayerHttpClient()) { async Task VerifyAndDownlodRar(string path, string downloadHash, Uri downloadPath) { if (Verify(path, downloadHash) == false) { App.Current.Logger.Info(nameof(EnglishPatchPhase), $"Downloading \"{Path.GetFileName(downloadPath.LocalPath)}\""); using (var response = await client.GetAsync(downloadPath)) using (var stream = await response.Content.ReadAsStreamAsync()) using (var archive = RarArchive.Open(stream)) { foreach (var file in archive.Entries.Where(e => !e.IsDirectory)) { var filePath = Path.Combine(_InstallConfiguration.ArksLayer.PatchesDirectory, file.Key); using (var fs = File.Create(filePath, 4096, FileOptions.Asynchronous)) await file.OpenEntryStream().CopyToAsync(fs); await patchCache.InsertUnderTransactionAsync(new[] { new PatchCacheEntry() { Name = CreateRelativePath(filePath), Hash = downloadHash, LastWriteTime = new FileInfo(filePath).LastWriteTimeUtc.ToFileTimeUtc() } }); } } } } await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishBlockPatch, translation.BlockMD5, new Uri(translation.BlockPatch)); await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishItemPatch, translation.ItemMD5, new Uri(translation.ItemPatch)); await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishTextPatch, translation.TextMD5, new Uri(translation.TextPatch)); await VerifyAndDownlodRar(_InstallConfiguration.ArksLayer.EnglishTitlePatch, translation.TitleMD5, new Uri(translation.TitlePatch)); } App.Current.Logger.Info(nameof(EnglishPatchPhase), "Validating plugin dlls"); await pluginInfo.PSO2BlockRenameDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2BlockRenameDll, ct); await pluginInfo.PSO2ItemTranslatorDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2ItemTranslatorDll, ct); await pluginInfo.PSO2TitleTranslatorDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2TitleTranslatorDll, ct); await pluginInfo.PSO2RAISERSystemDll.ValidateFileAsync(_InstallConfiguration.ArksLayer.PluginPSO2RAISERSystemDll, ct); }