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;
            }
        }
Beispiel #2
0
        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;
            }
        }
Beispiel #3
0
        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);
        }