private Process RunRSync(String rsyncUrl, Manifest.SyncItem syncItem, string modPath, Catflap.Repository.DownloadProgressChanged dpc, Catflap.Repository.DownloadMessage dm, Catflap.Repository.DownloadVerifyChecksum dvc, CancellationTokenSource cts, string overrideDestination = null) { var targetFileName = syncItem.name; string targetDir = modPath + "\\" + Path.GetDirectoryName(targetFileName); Directory.CreateDirectory(targetDir); string rsyncTargetSpec = overrideDestination != null ? overrideDestination :"."; bool isDir = targetFileName.EndsWith("/"); if (this.repository.Username != null) rsyncUrl = rsyncUrl.Replace("%user%", this.repository.Username); string va = rsyncFlags + " " + "\"" + rsyncUrl + "\"" + " " + "\"" + rsyncTargetSpec.ShellEscape() + "\""; if (VerifyChecksums) va += " " + rsyncFlagsVerify; if (!VerifyChecksums) va += " " + rsyncFlagsNoVerify; if (Simulate) va += " --dry-run"; if (isDir) va += " " + rsyncFlagsDirectory; if (syncItem.ignoreExisting.GetValueOrDefault()) va += " --ignore-existing"; // Only ever allow purge on directories, obviously. if (isDir && syncItem.purge.GetValueOrDefault()) va += " --delete-delay"; if (syncItem.ignoreCase.GetValueOrDefault()) va += " --ignore-case"; if (syncItem.fuzzy.GetValueOrDefault()) va += " --fuzzy"; va += " \"--temp-dir=" + tmpPath.ShellEscape() + "\""; switch (syncItem.mode) { case "inplace": va += " --inplace"; break; default: // "replace" /* We cannot keep partials for ignore-existing .. */ if (!syncItem.ignoreExisting.GetValueOrDefault()) va += " --partial-dir=catflap.partials"; va += " --delay-updates"; break; } long thisFileTotalSize = 0; string thisFilename = targetFileName; dm.Invoke("(rsync) " + va); Process pProcess = new System.Diagnostics.Process(); pProcess.StartInfo.FileName = appPath + "\\bin\\rsync.exe"; pProcess.StartInfo.Arguments = va; pProcess.StartInfo.CreateNoWindow = true; pProcess.StartInfo.UseShellExecute = false; pProcess.StartInfo.RedirectStandardOutput = true; pProcess.StartInfo.RedirectStandardError = true; pProcess.StartInfo.RedirectStandardInput = true; pProcess.StartInfo.WorkingDirectory = targetDir; if (pProcess.StartInfo.EnvironmentVariables.ContainsKey("PATH")) pProcess.StartInfo.EnvironmentVariables.Remove("PATH"); if (pProcess.StartInfo.EnvironmentVariables.ContainsKey("CYGWIN")) pProcess.StartInfo.EnvironmentVariables.Remove("CYGWIN"); pProcess.StartInfo.EnvironmentVariables.Add("CYGWIN", "nodosfilewarning"); if (this.repository.Password != null) pProcess.StartInfo.EnvironmentVariables.Add("RSYNC_PASSWORD", this.repository.Password); Console.WriteLine("VA = " + va); pProcess.OutputDataReceived += (s, ee) => { if (ee.Data != null) { /* Send everything to the log as-is. */ dm.Invoke("(stdout) " + ee.Data); switch (ee.Data) { case "receiving file list ... ": case "receiving incremental file list": dm.Invoke("<receiving list>", true); break; default: Match mr; // Progress indicator if ((mr = rxRsyncProgress.Match(ee.Data)).Success) { long bytesDone = long.Parse(mr.Groups[1].Value, CultureInfo.InvariantCulture); int percentage = int.Parse(mr.Groups[2].Value, CultureInfo.InvariantCulture); double rate = double.Parse(mr.Groups[3].Value, CultureInfo.InvariantCulture); string rateDesc = mr.Groups[4].Value; string eta = mr.Groups[5].Value; string details = mr.Groups[6].Value; if (details == null) details = ""; details = details.Trim(); if (rateDesc == "kB/s") rate *= 1024; if (rateDesc == "MB/s") rate *= 1024 * 1024; dpc.Invoke(cancelled ? "<cancelling>" : thisFilename, percentage, bytesDone, thisFileTotalSize, (int)rate, details); } else if ((mr = rxRsyncNewTransfer.Match(ee.Data)).Success) { string fname = mr.Groups[1].Value; if (isDir) if (fname == "." || fname == "./") fname = targetFileName; else if (isDir) fname = targetFileName + fname; else fname = targetFileName; thisFilename = fname; // dm.Invoke(fname, true); } // transfer complete / hash else if ((mr = rxRsyncNewFile.Match(ee.Data)).Success) { string flags = mr.Groups[1].Value; thisFileTotalSize = long.Parse(mr.Groups[2].Value, CultureInfo.InvariantCulture); //string fname = mr.Groups[3].Value; // YXcstpogz // X: update type (< > c .) // Y: filetype (f d L D S) // c: checksum, s: size, t: time, var action = ""; switch (flags[0]) { case '*': action = "[deleting] "; break; case '<': action = "[sending] "; break; case '>': action = ""; break; case 'c': action = "[creating] "; break; } dm.Invoke(action + thisFilename, true); if (!dvc.Invoke(thisFilename, mr.Groups[4].Value.ToLowerInvariant())) { cts.Cancel(); } } // Literal data: xx else if ((mr = rxRsyncLiteralData.Match(ee.Data)).Success) { if (mr.Groups[1].Value != "0") currentRunWasChanged = true; } // Total bytes received else if ((mr = rxRsyncTotalBytesReceived.Match(ee.Data)).Success) { if (mr.Groups[1].Value != "0") bytesOnNetwork += long.Parse(mr.Groups[1].Value, CultureInfo.InvariantCulture); } // Total bytes sent else if ((mr = rxRsyncTotalBytesSent.Match(ee.Data)).Success) { if (mr.Groups[1].Value != "0") bytesOnNetwork += long.Parse(mr.Groups[1].Value, CultureInfo.InvariantCulture); } break; } } }; pProcess.ErrorDataReceived += (s, ee) => { if (ee.Data != null) { stdErr += ee.Data + "\n"; dm.Invoke("ERROR: " + ee.Data); } }; dm.Invoke("Verifying " + syncItem.name + " (checksumming/waiting for server)", true); pProcess.Start(); pProcess.BeginOutputReadLine(); pProcess.BeginErrorReadLine(); // This eats "Password:"******"\n"); return pProcess; }
private void OnDownloadStatusInfoChangedHandler(Catflap.Repository.DownloadStatusInfo info) { if (info.currentFile != null) { var msg = info.currentFile.PathEllipsis(60); if (info.currentBps > 0) msg += " - " + info.currentBps.BytesToHuman() + "/s"; if (info.currentPercentage > 0) msg += ", " + ((int)(info.currentPercentage * 100)) + "%"; SetUIProgressState(info.globalFileTotal == 0, info.globalPercentage, msg); } else { SetUIProgressState(info.globalFileTotal == 0, info.globalPercentage, null); } if (info.globalFileTotal > 0) SetGlobalStatus(true, string.Format("{0}%", (int)(info.globalPercentage * 100).Clamp(0, 100), info.globalPercentage)); else { SetGlobalStatus(true); } }
// Returns true if the file was changed in any way. public Task<bool> Download(string source, Manifest.SyncItem syncItem, string modPath, Catflap.Repository.DownloadProgressChanged dpc, Catflap.Repository.DownloadEnd de, Catflap.Repository.DownloadMessage dm, Catflap.Repository.DownloadVerifyChecksum dvc, CancellationTokenSource cts, string overrideDestination = null) { var ct = cts.Token; stdErr = ""; return Task.Run<bool>(delegate() { currentRunWasChanged = false; var p = RunRSync(source, syncItem, modPath, dpc, dm, dvc, cts, overrideDestination); (Application.Current as App).TrackProcess(p); // Wait for the pid to appear. while (0 == p.Id && !p.HasExited); while (!p.HasExited) { if (ct.IsCancellationRequested) { cancelled = true; dm.Invoke("<cancelling (patience)>", true); /* Try Ctrl+C first so we can catch --replace/partial transfers */ SIGTERM(p.Id); /* Lets wait for a generous amount of time to wait for rsync to gracefully * terminate. This can happen on slow disks. */ p.WaitForExit(30000); App.KillProcessAndChildren(p.Id); p.WaitForExit(); ct.ThrowIfCancellationRequested(); } else Thread.Sleep(100); } p.WaitForExit(); de.Invoke(p.ExitCode != 0, stdErr, bytesOnNetwork); return currentRunWasChanged; }, ct); }
private void OnDownloadStatusInfoChangedHandler(Catflap.Repository.DownloadStatusInfo info) { if (info.currentFile != null) { var msg = info.currentFile.PathEllipsis(60); if (info.currentBps > 0) msg += " - " + info.currentBps.BytesToHuman() + "/s"; if (info.currentPercentage > 0) msg += ", " + ((int)(info.currentPercentage * 100)) + "%"; if (info.details != "") labelDownloadStatus.Dispatcher.Invoke(() => { labelDownloadStatus.ToolTip = info.details; }); this.RefreshProgress(UIState.Busy, msg); } else { this.RefreshProgress(UIState.Busy); } }