/// <summary> /// Add missing <seealso cref="PSO2File"/> from the destination list and update existing <seealso cref="PSO2File"/> in the current list /// </summary> /// <param name="list">The destination list</param> /// <param name="ignoreExisting">Determine if the existing <seealso cref="PSO2File"/> in the current list will also be updated</param> public void Merge(RemotePatchlist list, bool ignoreExisting) { foreach (var value in list.Values) { if (ignoreExisting) { this.TryAdd(value); } else { this.AddOrUpdate(value); } } }
/// <summary> /// Add missing <seealso cref="PSO2File"/> from the destination list and update existing <seealso cref="PSO2File"/> in the current list /// </summary> /// <param name="list">The destination list</param> public void Merge(RemotePatchlist list) => this.Merge(list, true);
/// <summary> /// Verify and redownload any missing/old files /// </summary> /// <param name="clientDirectory">The pso2_dir directory</param> /// <param name="version">Specify the version</param> /// <param name="filelist">Specify the patchlist to check and download</param> /// <param name="options">Provide the options for current download sessions</param> public async Task VerifyAndDownloadAsync(string clientDirectory, ClientVersionCheckResult version, RemotePatchlist filelist, ClientUpdateOptions options) { ConcurrentDictionary <PSO2File, Exception> failedfiles = new ConcurrentDictionary <PSO2File, Exception>(); int currentprogress = 0, downloadedfiles = 0; CancellationTokenSource totalCancelSource = new CancellationTokenSource(); options.ParallelOptions.CancellationToken = totalCancelSource.Token; //* options.ParallelOptions.CancellationToken.Register(() => { if (options.ChecksumCache != null && downloadedfiles > 0) { this.StepChanged?.Invoke(UpdateStep.WriteCache, options.ChecksumCache); options.ChecksumCache.WriteChecksumCache(version.LatestVersion); } options.Dispose(); this.UpdateCompleted?.Invoke(new PSO2NotifyEventArgs(true, clientDirectory, new ReadOnlyDictionary <PSO2File, Exception>(failedfiles))); }); //*/ this.cancelBag.Add(totalCancelSource); this.StepChanged.Invoke(UpdateStep.BeginFileCheckAndDownload, null); if (options.ChecksumCache == null) { options.Profile = UpdaterProfile.PreferAccuracy; } else { options.ChecksumCache.ReadChecksumCache(); } ConcurrentBag <PSO2File> pso2fileBag = new ConcurrentBag <PSO2File>(filelist.Values); Func <Task> actionV; switch (options.Profile) { case UpdaterProfile.PreferAccuracy: actionV = new Func <Task>(async() => { PSO2File pso2file; while (!totalCancelSource.IsCancellationRequested && pso2fileBag.TryTake(out pso2file)) { string fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename); try { if (string.Equals(pso2file.SafeFilename, DefaultValues.CensorFilename, StringComparison.OrdinalIgnoreCase)) { if (File.Exists(fullpath)) { string md5fromfile = MD5Wrapper.HashFromFile(fullpath); if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); if (options.ChecksumCache != null) { ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } else { if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); Interlocked.Increment(ref downloadedfiles); } } } else { fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename + ".backup"); if (File.Exists(fullpath)) { string md5fromfile = MD5Wrapper.HashFromFile(fullpath); if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); if (options.ChecksumCache != null) { ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } else { if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); Interlocked.Increment(ref downloadedfiles); } } } } } else { if (File.Exists(fullpath)) { string md5fromfile = MD5Wrapper.HashFromFile(fullpath); if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); if (options.ChecksumCache != null) { ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } else { if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); Interlocked.Increment(ref downloadedfiles); } } } else { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); if (options.ChecksumCache != null) { ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } } } #if !DEBUG catch (Exception ex) { failedfiles.TryAdd(pso2file, ex); } #endif finally { try { File.Delete(fullpath + ".dtmp"); } catch { } Interlocked.Increment(ref currentprogress); this.ProgressChanged?.Invoke(currentprogress, filelist.Count); } } }); break; case UpdaterProfile.PreferSpeed: actionV = new Func <Task>(async() => { PSO2File pso2file; while (!totalCancelSource.IsCancellationRequested && pso2fileBag.TryTake(out pso2file)) { string fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename); try { if (!string.Equals(pso2file.SafeFilename, DefaultValues.CensorFilename, StringComparison.OrdinalIgnoreCase)) { if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { if (!string.Equals(options.ChecksumCache.ChecksumList[pso2file.Filename].MD5, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } } else { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } } } #if !DEBUG catch (Exception ex) { failedfiles.TryAdd(pso2file, ex); } #endif finally { try { File.Delete(fullpath + ".dtmp"); } catch { } Interlocked.Increment(ref currentprogress); this.ProgressChanged?.Invoke(currentprogress, filelist.Count); } } }); break; default: actionV = new Func <Task>(async() => { PSO2File pso2file; while (!totalCancelSource.IsCancellationRequested && pso2fileBag.TryTake(out pso2file)) { string fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename); try { if (string.Equals(pso2file.SafeFilename, DefaultValues.CensorFilename, StringComparison.OrdinalIgnoreCase)) { if (File.Exists(fullpath)) { string md5fromfile = null; if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { var checksumfile = options.ChecksumCache.ChecksumList[pso2file.Filename]; using (FileStream fs = File.OpenRead(fullpath)) if (fs.Length == checksumfile.FileSize) { md5fromfile = checksumfile.MD5; } } if (string.IsNullOrEmpty(md5fromfile)) { md5fromfile = MD5Wrapper.HashFromFile(fullpath); } if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) { try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } else { if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); Interlocked.Increment(ref downloadedfiles); } } } else { fullpath = Path.Combine(clientDirectory, pso2file.Filename + ".backup"); if (File.Exists(fullpath)) { string md5fromfile = null; if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { var checksumfile = options.ChecksumCache.ChecksumList[pso2file.Filename]; using (FileStream fs = File.OpenRead(fullpath)) if (fs.Length == checksumfile.FileSize) { md5fromfile = checksumfile.MD5; } } if (string.IsNullOrEmpty(md5fromfile)) { md5fromfile = MD5Wrapper.HashFromFile(fullpath); } if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) { try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } else { if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); Interlocked.Increment(ref downloadedfiles); } } } } } else { if (File.Exists(fullpath)) { string md5fromfile = null; if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { var checksumfile = options.ChecksumCache.ChecksumList[pso2file.Filename]; using (FileStream fs = File.OpenRead(fullpath)) if (fs.Length == checksumfile.FileSize) { md5fromfile = checksumfile.MD5; } } if (string.IsNullOrEmpty(md5fromfile)) { md5fromfile = MD5Wrapper.HashFromFile(fullpath); } if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase)) { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) { try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } else { if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename)) { ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); Interlocked.Increment(ref downloadedfiles); } } } else { this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file); using (FileStream fs = File.Create(fullpath + ".dtmp")) { try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { } ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash); options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); })); } File.Delete(fullpath); File.Move(fullpath + ".dtmp", fullpath); Interlocked.Increment(ref downloadedfiles); this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file); } } } #if !DEBUG catch (Exception ex) { failedfiles.TryAdd(pso2file, ex); } #endif finally { try { File.Delete(fullpath + ".dtmp"); } catch { } Interlocked.Increment(ref currentprogress); this.ProgressChanged?.Invoke(currentprogress, filelist.Count); } } }); break; } try { Task[] tasks = new Task[options.ParallelOptions.MaxDegreeOfParallelism]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Factory.StartNew(actionV, TaskCreationOptions.LongRunning).Unwrap(); } await Task.WhenAll(tasks); if (options.ChecksumCache != null && downloadedfiles > 0) { this.StepChanged?.Invoke(UpdateStep.WriteCache, options.ChecksumCache); options.ChecksumCache.WriteChecksumCache(version.LatestVersion); } options.Dispose(); if (!totalCancelSource.IsCancellationRequested) { if (failedfiles.Count < 4) { Settings.VersionString = version.LatestVersion; } this.UpdateCompleted?.Invoke(new PSO2NotifyEventArgs(version.LatestVersion, clientDirectory, new ReadOnlyDictionary <PSO2File, Exception>(failedfiles))); } else { this.UpdateCompleted?.Invoke(new PSO2NotifyEventArgs(true, clientDirectory, new ReadOnlyDictionary <PSO2File, Exception>(failedfiles))); } } catch (OperationCanceledException) { } catch (Exception) { if (options.ChecksumCache != null && downloadedfiles > 0) { this.StepChanged?.Invoke(UpdateStep.WriteCache, options.ChecksumCache); options.ChecksumCache.WriteChecksumCache(version.LatestVersion); } options.Dispose(); throw; } }
/// <summary> /// Get the patch list(s) from a specific version /// </summary> /// <param name="version">Specific version to determine the patchlist</param> /// <param name="patchListType">Specify which patch lists will be downloaded</param> /// <returns></returns> public async Task <RemotePatchlist> GetPatchlistAsync(ClientVersionCheckResult version, PatchListType patchListType) { RemotePatchlist result = null; int total = 0, current = 0; if ((patchListType & PatchListType.Master) == PatchListType.Master) { total++; } if ((patchListType & PatchListType.Patch) == PatchListType.Patch) { total++; } if ((patchListType & PatchListType.LauncherList) == PatchListType.LauncherList) { total++; } if ((patchListType & PatchListType.Master) == PatchListType.Master) { using (var response = await this.downloader.GetAsync(UriHelper.URLConcat(version.MasterURL, DefaultValues.PatchInfo.called_patchlist), HttpCompletionOption.ResponseHeadersRead)) { if (response.IsSuccessStatusCode) { if (result == null) { result = new RemotePatchlist(); } using (var responseStream = await response.Content.ReadAsStreamAsync()) using (var sr = new StreamReader(responseStream)) { string currentline; while (!sr.EndOfStream) { currentline = sr.ReadLine(); if (!string.IsNullOrWhiteSpace(currentline)) { if (PSO2File.TryParse(currentline, version, out var _pso2file)) { result.AddOrUpdate(_pso2file); } } } } } } Interlocked.Increment(ref current); this.ProgressChanged?.Invoke(current, total); } if ((patchListType & PatchListType.Patch) == PatchListType.Patch) { using (var response = await this.downloader.GetAsync(UriHelper.URLConcat(version.PatchURL, DefaultValues.PatchInfo.called_patchlist), HttpCompletionOption.ResponseHeadersRead)) { if (response.IsSuccessStatusCode) { if (result == null) { result = new RemotePatchlist(); } using (var responseStream = await response.Content.ReadAsStreamAsync()) using (var sr = new StreamReader(responseStream)) { string currentline; while (!sr.EndOfStream) { currentline = sr.ReadLine(); if (!string.IsNullOrWhiteSpace(currentline)) { if (PSO2File.TryParse(currentline, version, out var _pso2file)) { result.AddOrUpdate(_pso2file); } } } } } } Interlocked.Increment(ref current); this.ProgressChanged?.Invoke(current, total); } if ((patchListType & PatchListType.LauncherList) == PatchListType.LauncherList) { using (var response = await this.downloader.GetAsync(UriHelper.URLConcat(version.PatchURL, DefaultValues.PatchInfo.file_launcher), HttpCompletionOption.ResponseHeadersRead)) { if (response.IsSuccessStatusCode) { if (result == null) { result = new RemotePatchlist(); } using (var responseStream = await response.Content.ReadAsStreamAsync()) using (var sr = new StreamReader(responseStream)) { string currentline; while (!sr.EndOfStream) { currentline = sr.ReadLine(); if (!string.IsNullOrWhiteSpace(currentline)) { if (PSO2File.TryParse(currentline, version, out var _pso2file)) { result.AddOrUpdate(_pso2file); } } } } } } Interlocked.Increment(ref current); this.ProgressChanged?.Invoke(current, total); } if (result == null) { throw new WebException(); } else { return(result); } }