public void SetData(ModDownload entry) { if (entry == null) { // Download details labelDownloadPublished.Text = null; labelSize.Text = null; labelFileCount.Text = null; // Release details labelReleasePublished.Text = null; linkRelease.Text = null; labelReleaseName.Text = null; labelReleaseTag.Text = null; } else { // Download details labelDownloadPublished.Text = entry.Updated.ToString(CultureInfo.CurrentCulture); labelSize.Text = SizeSuffix.GetSizeSuffix(entry.Size); labelFileCount.Text = entry.FilesToDownload.ToString(); // Release details labelReleasePublished.Text = entry.Published.ToString(CultureInfo.CurrentCulture); linkRelease.Text = entry.ReleaseUrl; labelReleaseName.Text = entry.Name; labelReleaseTag.Text = entry.Version; } linkRelease.Enabled = !string.IsNullOrEmpty(linkRelease.Text); Enabled = entry != null; }
private void OnShown(object sender, EventArgs eventArgs) { DialogResult = DialogResult.OK; SetTaskCount(updates.Sum(update => update.FilesToDownload)); using (var client = new UpdaterWebClient()) { CancellationToken token = tokenSource.Token; void OnExtracting(object o, CancelEventArgs args) { SetTaskAndStep("Extracting..."); args.Cancel = token.IsCancellationRequested; } void OnParsingManifest(object o, CancelEventArgs args) { SetTaskAndStep("Parsing manifest..."); args.Cancel = token.IsCancellationRequested; } void OnApplyingManifest(object o, CancelEventArgs args) { SetTaskAndStep("Applying manifest..."); args.Cancel = token.IsCancellationRequested; } void OnDownloadProgress(object o, DownloadProgressEventArgs args) { SetProgress(args.BytesReceived / (double)args.TotalBytesToReceive); SetTaskAndStep($"Downloading file {args.FileDownloading} of {args.FilesToDownload}:", $"({SizeSuffix.GetSizeSuffix(args.BytesReceived)} / {SizeSuffix.GetSizeSuffix(args.TotalBytesToReceive)})"); args.Cancel = token.IsCancellationRequested; } void OnDownloadCompleted(object o, CancelEventArgs args) { NextTask(); args.Cancel = token.IsCancellationRequested; } int modIndex = 0; foreach (ModDownload update in updates) { DialogResult result; Title = $"Updating mod {++modIndex} of {updates.Count}: {update.Info.Name}"; SetTaskAndStep("Starting download..."); update.Extracting += OnExtracting; update.ParsingManifest += OnParsingManifest; update.ApplyingManifest += OnApplyingManifest; update.DownloadProgress += OnDownloadProgress; update.DownloadCompleted += OnDownloadCompleted; do { result = DialogResult.Cancel; try { // poor man's await Task.Run (not available in .net 4.0) using (var task = new Task(() => update.Download(client, updatePath), token)) { task.Start(); while (!task.IsCompleted && !task.IsCanceled) { Application.DoEvents(); } task.Wait(token); } } catch (AggregateException ae) { ae.Handle(ex => { result = MessageBox.Show(this, $"Failed to update mod { update.Info.Name }:\r\n{ ex.Message }" + "\r\n\r\nPress Retry to try again, or Cancel to skip this mod.", "Update Failed", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); return(true); }); } } while (result == DialogResult.Retry); update.Extracting -= OnExtracting; update.ParsingManifest -= OnParsingManifest; update.ApplyingManifest -= OnApplyingManifest; update.DownloadProgress -= OnDownloadProgress; update.DownloadCompleted -= OnDownloadCompleted; } } }
private void OnShown(object sender, EventArgs eventArgs) { DialogResult = DialogResult.OK; SetTaskCount(1); using (var client = new UpdaterWebClient()) { CancellationToken token = tokenSource.Token; void OnDownloadProgress(object o, DownloadProgressEventArgs args) { SetProgress(args.BytesReceived / (double)args.TotalBytesToReceive); SetTaskAndStep($"Downloading file:", $"({SizeSuffix.GetSizeSuffix(args.BytesReceived)} / {SizeSuffix.GetSizeSuffix(args.TotalBytesToReceive)})"); args.Cancel = token.IsCancellationRequested; } void OnDownloadCompleted(object o, CancelEventArgs args) { NextTask(); args.Cancel = token.IsCancellationRequested; } DialogResult result; SetTaskAndStep("Starting download..."); do { result = DialogResult.Cancel; try { // poor man's await Task.Run (not available in .net 4.0) using (var task = new Task(() => { var cancelArgs = new CancelEventArgs(false); DownloadProgressEventArgs downloadArgs = null; void DownloadComplete(object _sender, AsyncCompletedEventArgs args) { lock (args.UserState) { Monitor.Pulse(args.UserState); } } void DownloadProgressChanged(object _sender, DownloadProgressChangedEventArgs args) { downloadArgs = new DownloadProgressEventArgs(args, 1, 1); OnDownloadProgress(this, downloadArgs); if (downloadArgs.Cancel) { client.CancelAsync(); // This still doesn't work } } var uri = new Uri(url); string filePath = Path.Combine(updatePath, uri.Segments.Last()); var info = new FileInfo(filePath); client.DownloadFileCompleted += DownloadComplete; client.DownloadProgressChanged += DownloadProgressChanged; var sync = new object(); lock (sync) { client.DownloadFileAsync(uri, filePath, sync); Monitor.Wait(sync); } client.DownloadProgressChanged -= DownloadProgressChanged; client.DownloadFileCompleted -= DownloadComplete; if (cancelArgs.Cancel || downloadArgs?.Cancel == true) { return; } OnDownloadCompleted(this, cancelArgs); if (cancelArgs.Cancel) { return; } string dataDir = Path.Combine(updatePath, Path.GetFileNameWithoutExtension(filePath)); if (!Directory.Exists(dataDir)) { Directory.CreateDirectory(dataDir); } SetTaskAndStep("Extracting..."); if (token.IsCancellationRequested) { return; } Process.Start(new ProcessStartInfo("7z.exe", $"x -aoa -o\"{dataDir}\" \"{filePath}\"") { UseShellExecute = false, CreateNoWindow = true }).WaitForExit(); Process.Start(Path.Combine(dataDir, "SAToolsHub.exe"), $"doupdate \"{dataDir}\" \"{Path.GetDirectoryName(Application.ExecutablePath)}\""); }, token)) { task.Start(); while (!task.IsCompleted && !task.IsCanceled) { Application.DoEvents(); } task.Wait(token); } } catch (AggregateException ae) { ae.Handle(ex => { result = MessageBox.Show(this, $"Failed to update:\r\n{ ex.Message }", "Update Failed", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); return(true); }); } } while (result == DialogResult.Retry); } }
/// <summary> /// Downloads files required for updating according to <see cref="Type"/>. /// </summary> /// <param name="client"><see cref="WebClient"/> to be used for downloading.</param> /// <param name="updatePath">Path to store downloaded files.</param> public void Download(WebClient client, string updatePath) { var cancelArgs = new CancelEventArgs(false); DownloadProgressEventArgs downloadArgs = null; int fileDownloading = 0; void downloadComplete(object sender, AsyncCompletedEventArgs args) { lock (args.UserState) { Monitor.Pulse(args.UserState); } } void downloadProgressChanged(object sender, DownloadProgressChangedEventArgs args) { downloadArgs = new DownloadProgressEventArgs(args, fileDownloading, FilesToDownload); if (OnDownloadProgress(downloadArgs)) { ((WebClient)sender).CancelAsync(); } } switch (Type) { case ModDownloadType.Archive: { var uri = new Uri(Url); if (!uri.Host.EndsWith("github.com", StringComparison.OrdinalIgnoreCase)) { var request = (HttpWebRequest)WebRequest.Create(uri); request.Method = "HEAD"; var response = (HttpWebResponse)request.GetResponse(); uri = response.ResponseUri; response.Close(); } string filePath = Path.Combine(updatePath, uri.Segments.Last()); var info = new FileInfo(filePath); if (info.Exists && info.Length == Size) { if (OnDownloadCompleted(cancelArgs)) { return; } } else { if (OnDownloadStarted(cancelArgs)) { return; } client.DownloadFileCompleted += downloadComplete; client.DownloadProgressChanged += downloadProgressChanged; ++fileDownloading; var sync = new object(); lock (sync) { client.DownloadFileAsync(uri, filePath, sync); Monitor.Wait(sync); } client.DownloadProgressChanged -= downloadProgressChanged; client.DownloadFileCompleted -= downloadComplete; if (cancelArgs.Cancel || downloadArgs?.Cancel == true) { return; } if (OnDownloadCompleted(cancelArgs)) { return; } } string dataDir = Path.Combine(updatePath, Path.GetFileNameWithoutExtension(filePath)); if (!Directory.Exists(dataDir)) { Directory.CreateDirectory(dataDir); } if (OnExtracting(cancelArgs)) { return; } Process process = Process.Start( new ProcessStartInfo("7z.exe", $"x -aoa -o\"{dataDir}\" \"{filePath}\"") { UseShellExecute = false, CreateNoWindow = true }); if (process != null) { process.WaitForExit(); } else { throw new NullReferenceException("Failed to create 7z process"); } string workDir = Path.GetDirectoryName(ModInfo.GetModFiles(new DirectoryInfo(dataDir)).FirstOrDefault()); if (string.IsNullOrEmpty(workDir)) { throw new DirectoryNotFoundException($"Unable to locate mod.ini in \"{dataDir}\""); } string newManPath = Path.Combine(workDir, "mod.manifest"); string oldManPath = Path.Combine(Folder, "mod.manifest"); if (OnParsingManifest(cancelArgs)) { return; } if (!File.Exists(newManPath) || !File.Exists(oldManPath)) { CopyDirectory(new DirectoryInfo(workDir), Directory.CreateDirectory(Folder)); Directory.Delete(dataDir, true); if (File.Exists(filePath)) { File.Delete(filePath); } return; } if (OnParsingManifest(cancelArgs)) { return; } List <ModManifestEntry> newManifest = ModManifest.FromFile(newManPath); if (OnApplyingManifest(cancelArgs)) { return; } List <ModManifestEntry> oldManifest = ModManifest.FromFile(oldManPath); List <string> oldFiles = oldManifest.Except(newManifest) .Select(x => Path.Combine(Folder, x.FilePath)) .ToList(); foreach (string file in oldFiles) { if (File.Exists(file)) { File.Delete(file); } } RemoveEmptyDirectories(oldManifest, newManifest); foreach (ModManifestEntry file in newManifest) { string dir = Path.GetDirectoryName(file.FilePath); if (!string.IsNullOrEmpty(dir)) { string newDir = Path.Combine(Folder, dir); if (!Directory.Exists(newDir)) { Directory.CreateDirectory(newDir); } } var sourceFile = new FileInfo(Path.Combine(workDir, file.FilePath)); var destFile = new FileInfo(Path.Combine(Folder, file.FilePath)); if (destFile.Exists) { destFile.Delete(); } sourceFile.Attributes &= ~FileAttributes.ReadOnly; sourceFile.MoveTo(destFile.FullName); } File.Copy(newManPath, oldManPath, true); void removeReadOnly(DirectoryInfo dir) { foreach (DirectoryInfo d in dir.GetDirectories()) { removeReadOnly(d); d.Attributes &= ~FileAttributes.ReadOnly; } } removeReadOnly(new DirectoryInfo(dataDir)); Directory.Delete(dataDir, true); File.WriteAllText(Path.Combine(Folder, "mod.version"), Updated.ToString(DateTimeFormatInfo.InvariantInfo)); if (File.Exists(filePath)) { File.Delete(filePath); } break; } case ModDownloadType.Modular: { List <ModManifestDiff> newEntries = ChangedFiles .Where(x => x.State == ModManifestState.Added || x.State == ModManifestState.Changed) .ToList(); var uri = new Uri(Url); string tempDir = Path.Combine(updatePath, uri.Segments.Last()); if (!Directory.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } var sync = new object(); foreach (ModManifestDiff i in newEntries) { string filePath = Path.Combine(tempDir, i.Current.FilePath); string dir = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } if (OnDownloadStarted(cancelArgs)) { return; } var info = new FileInfo(filePath); ++fileDownloading; if (!info.Exists || info.Length != i.Current.FileSize || !i.Current.Checksum.Equals(ModManifestGenerator.GetFileHash(filePath), StringComparison.OrdinalIgnoreCase)) { client.DownloadFileCompleted += downloadComplete; client.DownloadProgressChanged += downloadProgressChanged; lock (sync) { client.DownloadFileAsync(new Uri(uri, i.Current.FilePath), filePath, sync); Monitor.Wait(sync); } client.DownloadProgressChanged -= downloadProgressChanged; client.DownloadFileCompleted -= downloadComplete; info.Refresh(); if (info.Length != i.Current.FileSize) { throw new Exception(string.Format("Size of downloaded file \"{0}\" ({1}) differs from manifest ({2}).", i.Current.FilePath, SizeSuffix.GetSizeSuffix(info.Length), SizeSuffix.GetSizeSuffix(i.Current.FileSize))); } string hash = ModManifestGenerator.GetFileHash(filePath); if (!i.Current.Checksum.Equals(hash, StringComparison.OrdinalIgnoreCase)) { throw new Exception(string.Format("Checksum of downloaded file \"{0}\" ({1}) differs from manifest ({2}).", i.Current.FilePath, hash, i.Current.Checksum)); } } if (cancelArgs.Cancel || downloadArgs?.Cancel == true) { return; } if (OnDownloadCompleted(cancelArgs)) { return; } } client.DownloadFileCompleted += downloadComplete; lock (sync) { client.DownloadFileAsync(new Uri(uri, "mod.manifest"), Path.Combine(tempDir, "mod.manifest"), sync); Monitor.Wait(sync); } client.DownloadFileCompleted -= downloadComplete; // Handle all non-removal file operations (move, rename) List <ModManifestDiff> movedEntries = ChangedFiles.Except(newEntries) .Where(x => x.State == ModManifestState.Moved) .ToList(); if (OnApplyingManifest(cancelArgs)) { return; } // Handle existing entries marked as moved. foreach (ModManifestDiff i in movedEntries) { ModManifestEntry old = i.Last; // This would be considered an error... if (old == null) { continue; } string oldPath = Path.Combine(Folder, old.FilePath); string newPath = Path.Combine(tempDir, i.Current.FilePath); string dir = Path.GetDirectoryName(newPath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } File.Copy(oldPath, newPath, true); } // Now move the stuff from the temporary folder over to the working directory. foreach (ModManifestDiff i in newEntries.Concat(movedEntries)) { string tempPath = Path.Combine(tempDir, i.Current.FilePath); string workPath = Path.Combine(Folder, i.Current.FilePath); string dir = Path.GetDirectoryName(workPath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } File.Copy(tempPath, workPath, true); } // Once that has succeeded we can safely delete files that have been marked for removal. List <ModManifestDiff> removedEntries = ChangedFiles .Where(x => x.State == ModManifestState.Removed) .ToList(); foreach (string path in removedEntries.Select(i => Path.Combine(Folder, i.Current.FilePath)).Where(File.Exists)) { File.Delete(path); } // Same for files that have been moved. foreach (string path in movedEntries .Where(x => newEntries.All(y => y.Current.FilePath != x.Last.FilePath)) .Select(i => Path.Combine(Folder, i.Last.FilePath)).Where(File.Exists)) { File.Delete(path); } string oldManPath = Path.Combine(Folder, "mod.manifest"); string newManPath = Path.Combine(tempDir, "mod.manifest"); if (File.Exists(oldManPath)) { List <ModManifestEntry> oldManifest = ModManifest.FromFile(oldManPath); List <ModManifestEntry> newManifest = ModManifest.FromFile(newManPath); // Remove directories that are now empty. RemoveEmptyDirectories(oldManifest, newManifest); } // And last but not least, copy over the new manifest. File.Copy(newManPath, oldManPath, true); break; } default: throw new ArgumentOutOfRangeException(); } }