public async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null) { sourceDirectory = sourceDirectory ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var releasesPath = Path.Combine(sourceDirectory, "RELEASES"); this.Log().Info("Starting install, writing to {0}", sourceDirectory); if (!File.Exists(releasesPath)) { this.Log().Info("RELEASES doesn't exist, creating it at " + releasesPath); var nupkgs = (new DirectoryInfo(sourceDirectory)).GetFiles() .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Select(x => ReleaseEntry.GenerateFromFile(x.FullName)); ReleaseEntry.WriteReleaseFile(nupkgs, releasesPath); } var ourAppName = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesPath, Encoding.UTF8)) .First().PackageName; using (var mgr = new UpdateManager(sourceDirectory, ourAppName)) { this.Log().Info("About to install to: " + mgr.RootAppDirectory); if (Directory.Exists(mgr.RootAppDirectory)) { this.Log().Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory); await this.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory), "Failed to remove existing directory on full install, is the app still running???"); this.ErrorIfThrows(() => Utility.Retry(() => Directory.CreateDirectory(mgr.RootAppDirectory), 3), "Couldn't recreate app directory, perhaps Antivirus is blocking it"); } Directory.CreateDirectory(mgr.RootAppDirectory); var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe"); this.ErrorIfThrows(() => File.Copy(Assembly.GetExecutingAssembly().Location, updateTarget, true), "Failed to copy Update.exe to " + updateTarget); await mgr.FullInstall(silentInstall, progressSource.Raise); await this.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(), "Failed to create uninstaller registry entry"); } }
public void ApplyReleasesWithOneReleaseFile() { string tempDir; using (Utility.WithTempDirectory(out tempDir)) { Directory.CreateDirectory(Path.Combine(tempDir, "theApp")); Directory.CreateDirectory(Path.Combine(tempDir, "theApp", "packages")); new[] { "Shimmer.Core.1.0.0.0-full.nupkg", "Shimmer.Core.1.1.0.0-full.nupkg", }.ForEach(x => File.Copy(IntegrationTestHelper.GetPath("fixtures", x), Path.Combine(tempDir, "theApp", "packages", x))); var fixture = new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, tempDir, null, new FakeUrlDownloader()); var baseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(tempDir, "theApp", "packages", "Shimmer.Core.1.0.0.0-full.nupkg")); var latestFullEntry = ReleaseEntry.GenerateFromFile(Path.Combine(tempDir, "theApp", "packages", "Shimmer.Core.1.1.0.0-full.nupkg")); var updateInfo = UpdateInfo.Create(baseEntry, new[] { latestFullEntry }, "dontcare", FrameworkVersion.Net40); updateInfo.ReleasesToApply.Contains(latestFullEntry).ShouldBeTrue(); using (fixture.AcquireUpdateLock()) { var progress = fixture.ApplyReleases(updateInfo).ToList().First(); this.Log().Info("Progress: [{0}]", String.Join(",", progress)); progress.Buffer(2, 1).All(x => x.Count != 2 || x[1] > x[0]).ShouldBeTrue(); progress.Last().ShouldEqual(100); } var filesToFind = new[] { new { Name = "NLog.dll", Version = new Version("2.0.0.0") }, new { Name = "NSync.Core.dll", Version = new Version("1.1.0.0") }, new { Name = "Ionic.Zip.dll", Version = new Version("1.9.1.8") }, }; filesToFind.ForEach(x => { var path = Path.Combine(tempDir, "theApp", "app-1.1.0.0", x.Name); this.Log().Info("Looking for {0}", path); File.Exists(path).ShouldBeTrue(); var vi = FileVersionInfo.GetVersionInfo(path); var verInfo = new Version(vi.FileVersion ?? "1.0.0.0"); x.Version.ShouldEqual(verInfo); }); } }
async Task <string> createSetupEmbeddedZip(string fullPackage, string releasesDir, string backgroundGif, string signingOpts) { string tempPath; this.Log().Info("Building embedded zip file for Setup.exe"); using (Utility.WithTempDirectory(out tempPath, null)) { this.ErrorIfThrows(() => { File.Copy(Assembly.GetEntryAssembly().Location.Replace("-Mono.exe", ".exe"), Path.Combine(tempPath, "Update.exe")); File.Copy(fullPackage, Path.Combine(tempPath, Path.GetFileName(fullPackage))); }, "Failed to write package files to temp dir: " + tempPath); if (!String.IsNullOrWhiteSpace(backgroundGif)) { this.ErrorIfThrows(() => { File.Copy(backgroundGif, Path.Combine(tempPath, "background.gif")); }, "Failed to write animated GIF to temp dir: " + tempPath); } var releases = new[] { ReleaseEntry.GenerateFromFile(fullPackage) }; ReleaseEntry.WriteReleaseFile(releases, Path.Combine(tempPath, "RELEASES")); var target = Path.GetTempFileName(); File.Delete(target); // Sign Update.exe so that virus scanners don't think we're // pulling one over on them if (signingOpts != null) { var di = new DirectoryInfo(tempPath); var files = di.EnumerateFiles() .Where(x => x.Name.ToLowerInvariant().EndsWith(".exe")) .Select(x => x.FullName); await files.ForEachAsync(x => signPEFile(x, signingOpts)); } this.ErrorIfThrows(() => ZipFile.CreateFromDirectory(tempPath, target, CompressionLevel.Optimal, false), "Failed to create Zip file from directory: " + tempPath); return(target); } }
public void WhenNoNewReleasesAreAvailableTheListIsEmpty() { string tempDir; using (Utility.WithTempDirectory(out tempDir)) { var appDir = Directory.CreateDirectory(Path.Combine(tempDir, "theApp")); var packages = Path.Combine(appDir.FullName, "packages"); Directory.CreateDirectory(packages); var package = "Squirrel.Core.1.0.0.0-full.nupkg"; File.Copy(IntegrationTestHelper.GetPath("fixtures", package), Path.Combine(packages, package)); var aGivenPackage = Path.Combine(packages, package); var baseEntry = ReleaseEntry.GenerateFromFile(aGivenPackage); var updateInfo = UpdateInfo.Create(baseEntry, new[] { baseEntry }, "dontcare"); Assert.Empty(updateInfo.ReleasesToApply); } }
public void DownloadReleasesFromFileDirectoryIntegrationTest() { string tempDir = null; var updateDir = new DirectoryInfo(IntegrationTestHelper.GetPath("..", "SampleUpdatingApp", "SampleReleasesFolder")); var entriesToDownload = updateDir.GetFiles("*.nupkg") .Select(x => ReleaseEntry.GenerateFromFile(x.FullName)) .ToArray(); entriesToDownload.Count().ShouldBeGreaterThan(0); using (Utility.WithTempDirectory(out tempDir)) { // NB: This is normally done by CheckForUpdates, but since // we're skipping that in the test we have to do it ourselves Directory.CreateDirectory(Path.Combine(tempDir, "SampleUpdatingApp", "packages")); var fixture = new UpdateManager(updateDir.FullName, "SampleUpdatingApp", FrameworkVersion.Net40, tempDir); using (fixture) { var progress = new ReplaySubject <int>(); fixture.DownloadReleases(entriesToDownload, progress).First(); this.Log().Info("Progress: [{0}]", String.Join(",", progress)); progress.Buffer(2, 1).All(x => x.Count != 2 || x[1] > x[0]).First().ShouldBeTrue(); progress.Last().ShouldEqual(100); } entriesToDownload.ForEach(x => { this.Log().Info("Looking for {0}", x.Filename); var actualFile = Path.Combine(tempDir, "SampleUpdatingApp", "packages", x.Filename); File.Exists(actualFile).ShouldBeTrue(); var actualEntry = ReleaseEntry.GenerateFromFile(actualFile); actualEntry.SHA1.ShouldEqual(x.SHA1); actualEntry.Version.ShouldEqual(x.Version); }); } }
public void ExecutablesPinnedToTaskbarShouldPointToNewVersion() { string tempDir; using (Utility.WithTempDirectory(out tempDir)) { string packagesDir = Path.Combine(tempDir, "theApp", "packages"); Directory.CreateDirectory(packagesDir); new[] { "SampleUpdatingApp.1.0.0.0.nupkg", "SampleUpdatingApp.1.1.0.0.nupkg", }.ForEach(x => File.Copy(IntegrationTestHelper.GetPath("fixtures", x), Path.Combine(packagesDir, x))); var fixture = new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, tempDir, null, new FakeUrlDownloader()); var baseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(packagesDir, "SampleUpdatingApp.1.0.0.0.nupkg")); var latestFullEntry = ReleaseEntry.GenerateFromFile(Path.Combine(packagesDir, "SampleUpdatingApp.1.1.0.0.nupkg")); var updateInfo = UpdateInfo.Create(null, new[] { baseEntry }, packagesDir, FrameworkVersion.Net40); using (fixture) { fixture.ApplyReleases(updateInfo).ToList().First(); } var oldExecutable = Path.Combine(tempDir, "theApp", "app-1.0.0.0", "SampleUpdatingApp.exe"); File.Exists(oldExecutable).ShouldBeTrue(); TaskbarHelper.PinToTaskbar(oldExecutable); updateInfo = UpdateInfo.Create(baseEntry, new[] { latestFullEntry }, packagesDir, FrameworkVersion.Net40); using (fixture) { fixture.ApplyReleases(updateInfo).ToList().First(); } var newExecutable = Path.Combine(tempDir, "theApp", "app-1.1.0.0", "SampleUpdatingApp.exe"); File.Exists(newExecutable).ShouldBeTrue(); TaskbarHelper.IsPinnedToTaskbar(newExecutable).ShouldBeTrue(); Utility.Retry(() => TaskbarHelper.UnpinFromTaskbar(newExecutable)); } }
public void InstallRunsHooks() { string dir; string outDir; var package = "SampleUpdatingApp.1.2.0.0.nupkg"; using (Utility.WithTempDirectory(out outDir)) using (IntegrationTestHelper.WithFakeInstallDirectory(package, out dir)) { var di = new DirectoryInfo(dir); var bundledRelease = ReleaseEntry.GenerateFromFile(di.GetFiles("*.nupkg").First().FullName); var fixture = new InstallManager(bundledRelease, outDir); var pkg = new ZipPackage(Path.Combine(dir, package)); fixture.ExecuteInstall(dir, pkg).Wait(); var generatedFile = Path.Combine(outDir, "SampleUpdatingApp", "app-1.2.0.0", "install"); Assert.True(File.Exists(generatedFile)); } }
IObservable <ReleaseEntry> createFullPackagesFromDeltas(IEnumerable <ReleaseEntry> releasesToApply, ReleaseEntry currentVersion) { Contract.Requires(releasesToApply != null); // If there are no deltas in our list, we're already done if (!releasesToApply.Any() || releasesToApply.All(x => !x.IsDelta)) { return(Observable.Return(releasesToApply.MaxBy(x => x.Version).First())); } if (!releasesToApply.All(x => x.IsDelta)) { return(Observable.Throw <ReleaseEntry>(new Exception("Cannot apply combinations of delta and full packages"))); } // Smash together our base full package and the nearest delta var ret = Observable.Start(() => { var basePkg = new ReleasePackage(Path.Combine(rootAppDirectory, "packages", currentVersion.Filename)); var deltaPkg = new ReleasePackage(Path.Combine(rootAppDirectory, "packages", releasesToApply.First().Filename)); var deltaBuilder = new DeltaPackageBuilder(); return(deltaBuilder.ApplyDeltaPackage(basePkg, deltaPkg, Regex.Replace(deltaPkg.InputPackageFile, @"-delta.nupkg$", ".nupkg", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))); }, RxApp.TaskpoolScheduler); if (releasesToApply.Count() == 1) { return(ret.Select(x => ReleaseEntry.GenerateFromFile(x.InputPackageFile))); } return(ret.SelectMany(x => { var fi = fileSystem.GetFileInfo(x.InputPackageFile); var entry = ReleaseEntry.GenerateFromFile(fi.OpenRead(), fi.Name); // Recursively combine the rest of them return createFullPackagesFromDeltas(releasesToApply.Skip(1), entry); })); }
public void UninstallRemovesEverything() { string dir; string appDir; using (IntegrationTestHelper.WithFakeInstallDirectory(out dir)) using (IntegrationTestHelper.WithFakeAlreadyInstalledApp(out appDir)) { var di = new DirectoryInfo(dir); var progress = new Subject <int>(); var bundledRelease = ReleaseEntry.GenerateFromFile(di.GetFiles("*.nupkg").First().FullName); var fixture = new InstallManager(bundledRelease, appDir); var progressValues = new List <int>(); progress.Subscribe(progressValues.Add); fixture.ExecuteUninstall().First(); di = new DirectoryInfo(appDir); di.GetDirectories().Any().ShouldBeFalse(); di.GetFiles().Any().ShouldBeFalse(); } }
public void ChecksumShouldFailIfFilesAreBogus() { var filename = "Squirrel.Core.1.0.0.0.nupkg"; var nuGetPkg = IntegrationTestHelper.GetPath("fixtures", filename); var fs = new Mock <IFileSystemFactory>(); var urlDownloader = new Mock <IUrlDownloader>(); ReleaseEntry entry; using (var f = File.OpenRead(nuGetPkg)) { entry = ReleaseEntry.GenerateFromFile(f, filename); } var fileInfo = new Mock <FileInfoBase>(); fileInfo.Setup(x => x.OpenRead()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("Lol broken"))); fileInfo.Setup(x => x.Exists).Returns(true); fileInfo.Setup(x => x.Length).Returns(new FileInfo(nuGetPkg).Length); fileInfo.Setup(x => x.Delete()).Verifiable(); fs.Setup(x => x.GetFileInfo(Path.Combine(".", "theApp", "packages", filename))).Returns(fileInfo.Object); var fixture = ExposedObject.From( new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, ".", fs.Object, urlDownloader.Object)); bool shouldDie = true; try { fixture.checksumPackage(entry); } catch (Exception ex) { this.Log().InfoException("Checksum failure", ex); shouldDie = false; } shouldDie.ShouldBeFalse(); fileInfo.Verify(x => x.Delete(), Times.Once()); }
string createSetupEmbeddedZip(string fullPackage, string releasesDir) { string tempPath; this.Log().Info("Building embedded zip file for Setup.exe"); using (Utility.WithTempDirectory(out tempPath)) { this.ErrorIfThrows(() => { File.Copy(Assembly.GetEntryAssembly().Location, Path.Combine(tempPath, "Update.exe")); File.Copy(fullPackage, Path.Combine(tempPath, Path.GetFileName(fullPackage))); }, "Failed to write package files to temp dir: " + tempPath); var releases = new[] { ReleaseEntry.GenerateFromFile(fullPackage) }; ReleaseEntry.WriteReleaseFile(releases, Path.Combine(tempPath, "RELEASES")); var target = Path.GetTempFileName(); File.Delete(target); this.ErrorIfThrows(() => ZipFile.CreateFromDirectory(tempPath, target, CompressionLevel.Optimal, false), "Failed to create Zip file from directory: " + tempPath); return(target); } }
public void InstallWithContentInPackageDropsInSameFolder() { string dir; string outDir; var package = "ProjectWithContent.1.0.0.0-beta-full.nupkg"; using (Utility.WithTempDirectory(out outDir)) using (IntegrationTestHelper.WithFakeInstallDirectory(package, out dir)) { try { var di = new DirectoryInfo(dir); var bundledRelease = ReleaseEntry.GenerateFromFile(di.GetFiles("*.nupkg").First().FullName); var fixture = new InstallManager(bundledRelease, outDir); var pkg = new ZipPackage(Path.Combine(dir, package)); fixture.ExecuteInstall(dir, pkg).Wait(); var filesToLookFor = new[] { "ProjectWithContent\\app-1.0.0.0\\project-with-content.exe", "ProjectWithContent\\app-1.0.0.0\\some-words.txt", "ProjectWithContent\\app-1.0.0.0\\dir\\item-in-subdirectory.txt", "ProjectWithContent\\packages\\RELEASES", "ProjectWithContent\\packages\\ProjectWithContent.1.0.0.0-beta-full.nupkg", }; filesToLookFor.ForEach(f => Assert.True(File.Exists(Path.Combine(outDir, f)), "Could not find file: " + f)); } finally { Directory.Delete(dir, true); } } }
public async Task <UpdateInfo> CheckForUpdate( string localReleaseFile, string updateUrlOrPath, bool ignoreDeltaUpdates = false, Action <int> progress = null, IFileDownloader urlDownloader = null) { progress = progress ?? (_ => { }); var localReleases = Enumerable.Empty <ReleaseEntry>(); var stagingId = GetOrCreateStagedUserId(); var shouldInitialize = false; try { localReleases = Utility.LoadLocalReleases(localReleaseFile); } catch (Exception ex) { // Something has gone pear-shaped, let's start from scratch Log.Warn("Failed to load local releases, starting from scratch", ex); shouldInitialize = true; } if (shouldInitialize) { await InitializeClientAppDirectory(); } string releaseFile; var latestLocalRelease = localReleases.Count() > 0 ? localReleases.MaxBy(x => x.Version).First() : default; // Fetch the remote RELEASES file, whether it's a local dir or an // HTTP URL if (Utility.IsHttpUrl(updateUrlOrPath)) { if (updateUrlOrPath.EndsWith("/")) { updateUrlOrPath = updateUrlOrPath.Substring(0, updateUrlOrPath.Length - 1); } Log.InfoFormat("Downloading RELEASES file from {0}", updateUrlOrPath); var retries = 3; retry: try { var uri = Utility.AppendPathToUri(new Uri(updateUrlOrPath), "RELEASES"); if (latestLocalRelease != null) { uri = Utility.AddQueryParamsToUri( uri, new Dictionary <string, string> { { "id", latestLocalRelease.PackageName }, { "localVersion", latestLocalRelease.Version.ToString() }, { "arch", Environment.Is64BitOperatingSystem ? "amd64" : "x86" } }); } var data = await urlDownloader.DownloadUrl(uri.ToString()); releaseFile = Encoding.UTF8.GetString(data); } catch (WebException ex) { Log.Info("Download resulted in WebException (returning blank release list)", ex); if (retries <= 0) { throw; } retries--; goto retry; } progress(33); } else { Log.InfoFormat("Reading RELEASES file from {0}", updateUrlOrPath); if (!Directory.Exists(updateUrlOrPath)) { var message = $"The directory {updateUrlOrPath} does not exist, something is probably broken with your application"; throw new Exception(message); } var fi = new FileInfo(Path.Combine(updateUrlOrPath, "RELEASES")); if (!fi.Exists) { var message = $"The file {fi.FullName} does not exist, something is probably broken with your application"; Log.WarnFormat(message); var packages = new DirectoryInfo(updateUrlOrPath).GetFiles("*.nupkg"); if (packages.Length == 0) { throw new Exception(message); } // NB: Create a new RELEASES file since we've got a directory of packages ReleaseEntry.WriteReleaseFile( packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)), fi.FullName); } releaseFile = File.ReadAllText(fi.FullName, Encoding.UTF8); progress(33); } var ret = default(UpdateInfo); var remoteReleases = ReleaseEntry.ParseReleaseFileAndApplyStaging(releaseFile, stagingId); progress(66); if (!remoteReleases.Any()) { throw new Exception("Remote release File is empty or corrupted"); } ret = DetermineUpdateInfo(localReleases, remoteReleases, ignoreDeltaUpdates); progress(100); return(ret); }
async Task <int> main(string[] args) { using (var logger = new SetupLogLogger(false) { Level = Splat.LogLevel.Info }) { Splat.Locator.CurrentMutable.Register(() => logger, typeof(Splat.ILogger)); var releaseDir = default(string); var repoUrl = default(string); var token = default(string); opts = new OptionSet() { "Usage: SyncGitHubReleases.exe command [OPTS]", "Builds a Releases directory from releases on GitHub", "", "Options:", { "h|?|help", "Display Help and exit", _ => {} }, { "r=|releaseDir=", "Path to a release directory to download to", v => releaseDir = v }, { "u=|repoUrl=", "The URL to the repository root page", v => repoUrl = v }, { "t=|token=", "The OAuth token to use as login credentials", v => token = v }, }; opts.Parse(args); if (token == null || repoUrl == null || repoUrl.StartsWith("http", true, CultureInfo.InvariantCulture) == false) { ShowHelp(); return(-1); } var releaseDirectoryInfo = new DirectoryInfo(releaseDir ?? Path.Combine(".", "Releases")); if (!releaseDirectoryInfo.Exists) { releaseDirectoryInfo.Create(); } var repoUri = new Uri(repoUrl); var userAgent = new ProductHeaderValue("SyncGitHubReleases", Assembly.GetExecutingAssembly().GetName().Version.ToString()); var client = new GitHubClient(userAgent, repoUri) { Credentials = new Credentials(token) }; var nwo = nwoFromRepoUrl(repoUrl); var releases = await client.Release.GetAll(nwo.Item1, nwo.Item2); await releases.ForEachAsync(async release => { // NB: Why do I have to double-fetch the release assets? It's already in GetAll var assets = await client.Release.GetAssets(nwo.Item1, nwo.Item2, release.Id); await assets .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Where(x => { var fi = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name)); return(!(fi.Exists && fi.Length == x.Size)); }) .ForEachAsync(async x => { var target = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name)); if (target.Exists) { target.Delete(); } var hc = new HttpClient(); var rq = new HttpRequestMessage(HttpMethod.Get, x.Url); rq.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/octet-stream")); rq.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(userAgent.Name, userAgent.Version)); rq.Headers.Add("Authorization", "Bearer " + token); var resp = await hc.SendAsync(rq); resp.EnsureSuccessStatusCode(); using (var from = await resp.Content.ReadAsStreamAsync()) using (var to = File.OpenWrite(target.FullName)) { await from.CopyToAsync(to); } }); }); var entries = releaseDirectoryInfo.GetFiles("*.nupkg") .AsParallel() .Select(x => ReleaseEntry.GenerateFromFile(x.FullName)); ReleaseEntry.WriteReleaseFile(entries, Path.Combine(releaseDirectoryInfo.FullName, "RELEASES")); } return(0); }
public void Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null, string setupIcon = null) { if (baseUrl != null) { if (!Utility.IsHttpUrl(baseUrl)) { throw new Exception(string.Format("Invalid --baseUrl '{0}'. A base URL must start with http or https and be a valid URI.", baseUrl)); } if (!baseUrl.EndsWith("/")) { baseUrl += "/"; } } targetDir = targetDir ?? ".\\Releases"; packagesDir = packagesDir ?? "."; bootstrapperExe = bootstrapperExe ?? ".\\Setup.exe"; if (!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } if (!File.Exists(bootstrapperExe)) { bootstrapperExe = Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Setup.exe"); } this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe); var di = new DirectoryInfo(targetDir); File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true); var allNuGetFiles = di.EnumerateFiles() .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)); var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full")); var processed = new List <string>(); var releaseFilePath = Path.Combine(di.FullName, "RELEASES"); var previousReleases = Enumerable.Empty <ReleaseEntry>(); if (File.Exists(releaseFilePath)) { previousReleases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8)); } foreach (var file in toProcess) { this.Log().Info("Creating release package: " + file.FullName); var rp = new ReleasePackage(file.FullName); rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), packagesDir, contentsPostProcessHook: pkgPath => { if (signingOpts == null) { return; } new DirectoryInfo(pkgPath).GetAllFilesRecursively() .Where(x => x.Name.ToLowerInvariant().EndsWith(".exe")) .ForEachAsync(x => signPEFile(x.FullName, signingOpts)) .Wait(); }); processed.Add(rp.ReleasePackageFile); var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir); if (prev != null) { var deltaBuilder = new DeltaPackageBuilder(); var dp = deltaBuilder.CreateDeltaPackage(prev, rp, Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta"))); processed.Insert(0, dp.InputPackageFile); } } foreach (var file in toProcess) { File.Delete(file.FullName); } var releaseEntries = previousReleases.Concat(processed.Select(packageFilename => ReleaseEntry.GenerateFromFile(packageFilename, baseUrl))); ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath); var targetSetupExe = Path.Combine(di.FullName, "Setup.exe"); var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First(); File.Copy(bootstrapperExe, targetSetupExe, true); var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName, backgroundGif, signingOpts).Result; try { var zip = File.ReadAllBytes(zipPath); IntPtr handle = NativeMethods.BeginUpdateResource(targetSetupExe, false); if (handle == IntPtr.Zero) { throw new Win32Exception(); } if (!NativeMethods.UpdateResource(handle, "DATA", new IntPtr(131), 0x0409, zip, zip.Length)) { throw new Win32Exception(); } if (!NativeMethods.EndUpdateResource(handle, false)) { throw new Win32Exception(); } } catch (Exception ex) { this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex); } finally { File.Delete(zipPath); } Utility.Retry(() => setPEVersionInfoAndIcon(targetSetupExe, new ZipPackage(package), setupIcon).Wait()); if (signingOpts != null) { signPEFile(targetSetupExe, signingOpts).Wait(); } }
public void Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null, string setupIcon = null, bool generateMsi = true) { if (baseUrl != null) { if (!Utility.IsHttpUrl(baseUrl)) { throw new Exception(string.Format("Invalid --baseUrl '{0}'. A base URL must start with http or https and be a valid URI.", baseUrl)); } if (!baseUrl.EndsWith("/")) { baseUrl += "/"; } } targetDir = targetDir ?? Path.Combine(".", "Releases"); packagesDir = packagesDir ?? "."; bootstrapperExe = bootstrapperExe ?? Path.Combine(".", "Setup.exe"); if (!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } if (!File.Exists(bootstrapperExe)) { bootstrapperExe = Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Setup.exe"); } this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe); var di = new DirectoryInfo(targetDir); File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true); var allNuGetFiles = di.EnumerateFiles() .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)); var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full")); var processed = new List <string>(); var releaseFilePath = Path.Combine(di.FullName, "RELEASES"); var previousReleases = new List <ReleaseEntry>(); if (File.Exists(releaseFilePath)) { previousReleases.AddRange(ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8))); } foreach (var file in toProcess) { this.Log().Info("Creating release package: " + file.FullName); var rp = new ReleasePackage(file.FullName); rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), packagesDir, contentsPostProcessHook: pkgPath => { new DirectoryInfo(pkgPath).GetAllFilesRecursively() .Where(x => x.Name.ToLowerInvariant().EndsWith(".exe")) .Where(x => !x.Name.ToLowerInvariant().Contains("squirrel.exe")) .Where(x => Utility.ExecutableUsesWin32Subsystem(x.FullName)) .ForEachAsync(x => createExecutableStubForExe(x.FullName)) .Wait(); if (signingOpts == null) { return; } new DirectoryInfo(pkgPath).GetAllFilesRecursively() .Where(x => Utility.FileIsLikelyPEImage(x.Name)) .ForEachAsync(async x => { if (isPEFileSigned(x.FullName)) { this.Log().Info("{0} is already signed, skipping", x.FullName); return; } this.Log().Info("About to sign {0}", x.FullName); await signPEFile(x.FullName, signingOpts); }) .Wait(); }); processed.Add(rp.ReleasePackageFile); var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir); if (prev != null) { var deltaBuilder = new DeltaPackageBuilder(null); var dp = deltaBuilder.CreateDeltaPackage(prev, rp, Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta"))); processed.Insert(0, dp.InputPackageFile); } } foreach (var file in toProcess) { File.Delete(file.FullName); } var newReleaseEntries = processed .Select(packageFilename => ReleaseEntry.GenerateFromFile(packageFilename, baseUrl)) .ToList(); var distinctPreviousReleases = previousReleases .Where(x => !newReleaseEntries.Select(e => e.Version).Contains(x.Version)); var releaseEntries = distinctPreviousReleases.Concat(newReleaseEntries).ToList(); ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath); var targetSetupExe = Path.Combine(di.FullName, "Setup.exe"); var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First(); File.Copy(bootstrapperExe, targetSetupExe, true); var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName, backgroundGif, signingOpts).Result; var writeZipToSetup = findExecutable("WriteZipToSetup.exe"); try { var result = Utility.InvokeProcessAsync(writeZipToSetup, String.Format("\"{0}\" \"{1}\"", targetSetupExe, zipPath), CancellationToken.None).Result; if (result.Item1 != 0) { throw new Exception("Failed to write Zip to Setup.exe!\n\n" + result.Item2); } } catch (Exception ex) { this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex); } finally { File.Delete(zipPath); } Utility.Retry(() => setPEVersionInfoAndIcon(targetSetupExe, new ZipPackage(package), setupIcon).Wait()); if (signingOpts != null) { signPEFile(targetSetupExe, signingOpts).Wait(); } if (generateMsi) { createMsiPackage(targetSetupExe, new ZipPackage(package)).Wait(); if (signingOpts != null) { signPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait(); } } }
private void CreateDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory, Dictionary <string, string> baseFileListing) { // NB: There are three cases here that we'll handle: // // 1. Exists only in new => leave it alone, we'll use it directly. // 2. Exists in both old and new => write a dummy file so we know // to keep it. // 3. Exists in old but changed in new => create a delta file // // The fourth case of "Exists only in old => delete it in new" // is handled when we apply the delta package var relativePath = targetFile.FullName.Replace(workingDirectory.FullName, ""); if (!baseFileListing.ContainsKey(relativePath)) { Log.InfoFormat("{0} not found in base package, marking as new", relativePath); return; } var oldData = File.ReadAllBytes(baseFileListing[relativePath]); var newData = File.ReadAllBytes(targetFile.FullName); if (BytesAreIdentical(oldData, newData)) { Log.InfoFormat("{0} hasn't changed, writing dummy file", relativePath); File.Create(targetFile.FullName + ".diff").Dispose(); File.Create(targetFile.FullName + ".shasum").Dispose(); targetFile.Delete(); return; } Log.InfoFormat("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName); var msDelta = new MsDeltaCompression(); if (targetFile.Extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) || targetFile.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) || targetFile.Extension.Equals(".node", StringComparison.OrdinalIgnoreCase)) { try { msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff"); goto exit; } catch (Exception) { Log.WarnFormat("We couldn't create a delta for {0}, attempting to create bsdiff", targetFile.Name); } } try { using (var of = File.Create(targetFile.FullName + ".bsdiff")) { BinaryPatchUtility.Create(oldData, newData, of); // NB: Create a dummy corrupt .diff file so that older // versions which don't understand bsdiff will fail out // until they get upgraded, instead of seeing the missing // file and just removing it. File.WriteAllText(targetFile.FullName + ".diff", "1"); } } catch (Exception ex) { Log.Warn($"We really couldn't create a delta for {targetFile.Name}", ex); Utility.DeleteFileHarder(targetFile.FullName + ".bsdiff", true); Utility.DeleteFileHarder(targetFile.FullName + ".diff", true); return; } exit: var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum"); File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); targetFile.Delete(); }
IObservable <UpdateInfo> checkForUpdate(bool ignoreDeltaUpdates = false, IObserver <int> progress = null) { var localReleases = Enumerable.Empty <ReleaseEntry>(); progress = progress ?? new Subject <int>(); try { var file = fileSystem.GetFileInfo(LocalReleaseFile).OpenRead(); // NB: sr disposes file using (var sr = new StreamReader(file, Encoding.UTF8)) { localReleases = ReleaseEntry.ParseReleaseFile(sr.ReadToEnd()); } } catch (Exception ex) { // Something has gone wrong, we'll start from scratch. log.WarnException("Failed to load local release list", ex); initializeClientAppDirectory(); } IObservable <string> releaseFile; // Fetch the remote RELEASES file, whether it's a local dir or an // HTTP URL try { if (isHttpUrl(updateUrlOrPath)) { log.Info("Downloading RELEASES file from {0}", updateUrlOrPath); releaseFile = urlDownloader.DownloadUrl(String.Format("{0}/{1}", updateUrlOrPath, "RELEASES"), progress) .Catch <string, TimeoutException>(ex => { log.Info("Download timed out (returning blank release list)"); return(Observable.Return(String.Empty)); }) .Catch <string, WebException>(ex => { log.InfoException("Download resulted in WebException (returning blank release list)", ex); return(Observable.Return(String.Empty)); }); } else { log.Info("Reading RELEASES file from {0}", updateUrlOrPath); if (!fileSystem.GetDirectoryInfo(updateUrlOrPath).Exists) { var message = String.Format( "The directory {0} does not exist, something is probably broken with your application", updateUrlOrPath); var ex = new SquirrelConfigurationException(message); return(Observable.Throw <UpdateInfo>(ex)); } var fi = fileSystem.GetFileInfo(Path.Combine(updateUrlOrPath, "RELEASES")); if (!fi.Exists) { var message = String.Format( "The file {0} does not exist, something is probably broken with your application", fi.FullName); log.Warn(message); var packages = fileSystem.GetDirectoryInfo(updateUrlOrPath).GetFiles("*.nupkg"); if (packages.Length == 0) { var ex = new SquirrelConfigurationException(message); return(Observable.Throw <UpdateInfo>(ex)); } // NB: Create a new RELEASES file since we've got a directory of packages ReleaseEntry.WriteReleaseFile( packages.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)), fi.FullName); } using (var sr = new StreamReader(fi.OpenRead(), Encoding.UTF8)) { var text = sr.ReadToEnd(); releaseFile = Observable.Return(text); } progress.OnNext(100); progress.OnCompleted(); } } catch (Exception ex) { progress.OnCompleted(); return(Observable.Throw <UpdateInfo>(ex)); } // Return null if no updates found var ret = releaseFile .Select(ReleaseEntry.ParseReleaseFile) .SelectMany(releases => releases.Any() ? determineUpdateInfo(localReleases, releases, ignoreDeltaUpdates) : Observable.Return <UpdateInfo>(null)) .PublishLast(); ret.Connect(); return(ret); }
public async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null) { sourceDirectory = sourceDirectory ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var releasesPath = Path.Combine(sourceDirectory, "RELEASES"); this.Log().Info("Starting install, writing to {0}", sourceDirectory); if (!File.Exists(releasesPath)) { this.Log().Info("RELEASES doesn't exist, creating it at " + releasesPath); var nupkgs = (new DirectoryInfo(sourceDirectory)).GetFiles() .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Select(x => ReleaseEntry.GenerateFromFile(x.FullName)); ReleaseEntry.WriteReleaseFile(nupkgs, releasesPath); } var ourAppName = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesPath, Encoding.UTF8)) .First().PackageName; this.Log().Warn("Preparing to install"); string tempSettingsFile = null; string destSettingsFile = null; using (var mgr = new UpdateManager(sourceDirectory, ourAppName)) { this.Log().Info("About to install to: " + mgr.RootAppDirectory); if (Directory.Exists(mgr.RootAppDirectory)) { this.Log().Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory); var settingsName = "UiPath.settings"; var settingsFile = Path.Combine(mgr.RootAppDirectory, settingsName); tempSettingsFile = File.Exists(settingsFile) ? Path.GetTempPath() + Path.GetRandomFileName() : null; if (tempSettingsFile != null) { this.Log().Warn("Backup uipath.settings"); destSettingsFile = settingsFile; File.Copy(settingsFile, tempSettingsFile); } bool success = false; int maxRetries = 3; do { var killed = mgr.KillAllExecutablesBelongingToPackage(); await Task.Delay(500); this.Log().Warn($"Deleted {killed} processes"); try { await this.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory), "Failed to remove existing directory on full install, is the app still running???"); success = true; } catch (Exception ex) { this.Log().Warn($"Failed to delete whole folder. Reason {ex.ToString()}"); this.Log().Warn($"Attempts left {maxRetries}"); if (maxRetries == 0) { throw; } } }while (!success || maxRetries-- > 0); this.ErrorIfThrows(() => Utility.Retry(() => Directory.CreateDirectory(mgr.RootAppDirectory), 3), "Couldn't recreate app directory, perhaps Antivirus is blocking it"); } Directory.CreateDirectory(mgr.RootAppDirectory); var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe"); this.ErrorIfThrows(() => File.Copy(Assembly.GetExecutingAssembly().Location, updateTarget, true), "Failed to copy Update.exe to " + updateTarget); await mgr.FullInstall(silentInstall, progressSource.Raise); if (destSettingsFile != null && tempSettingsFile != null) { File.Copy(tempSettingsFile, destSettingsFile); } await this.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(), "Failed to create uninstaller registry entry"); } }
// NB: Once we uninstall the old version of the app, we try to schedule // it to be deleted at next reboot. Unfortunately, depending on whether // the user has admin permissions, this can fail. So as a failsafe, // before we try to apply any update, we assume previous versions in the // directory are "dead" (i.e. already uninstalled, but not deleted), and // we blow them away. This is to make sure that we don't attempt to run // an uninstaller on an already-uninstalled version. private async Task CleanDeadVersions(SemanticVersion originalVersion, SemanticVersion currentVersion, bool forceUninstall = false) { if (currentVersion == null) { return; } var di = new DirectoryInfo(rootAppDirectory); if (!di.Exists) { return; } Log.InfoFormat("cleanDeadVersions: for version {0}", currentVersion); string originalVersionFolder = null; if (originalVersion != null) { originalVersionFolder = GetDirectoryForRelease(originalVersion).Name; Log.InfoFormat("cleanDeadVersions: exclude folder {0}", originalVersionFolder); } string currentVersionFolder = null; if (currentVersion != null) { currentVersionFolder = GetDirectoryForRelease(currentVersion).Name; Log.InfoFormat("cleanDeadVersions: exclude folder {0}", currentVersionFolder); } // NB: If we try to access a directory that has already been // scheduled for deletion by MoveFileEx it throws what seems like // NT's only error code, ERROR_ACCESS_DENIED. Squelch errors that // come from here. var toCleanup = di.GetDirectories() .Where(x => x.Name.ToLowerInvariant().Contains("app-")) .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder) .Where(x => !IsAppFolderDead(x.FullName)); if (forceUninstall == false) { await toCleanup.ForEachAsync( async x => { var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(x.FullName); var args = $"--squirrel-obsolete {x.Name.Replace("app-", "")}"; if (squirrelApps.Count > 0) { // For each app, run the install command in-order and wait await squirrelApps.ForEachAsync( async exe => { using (var cts = new CancellationTokenSource()) { cts.CancelAfter(10 * 1000); try { await Utility.InvokeProcessAsync(exe, args, cts.Token); } catch (Exception ex) { Log.Error("Coudln't run Squirrel hook, continuing: " + exe, ex); } } }, 1 /* at a time */); } }); } // Include dead folders in folders to :fire: toCleanup = di.GetDirectories() .Where(x => x.Name.ToLowerInvariant().Contains("app-")) .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder); // Get the current process list in an attempt to not burn // directories which have running processes var runningProcesses = UnsafeUtility.EnumerateProcesses(); // Finally, clean up the app-X.Y.Z directories await toCleanup.ForEachAsync( async x => { try { if (runningProcesses.All(p => p.Item1 == null || !p.Item1.StartsWith(x.FullName, StringComparison.OrdinalIgnoreCase))) { await Utility.DeleteDirectoryOrJustGiveUp(x.FullName); } if (Directory.Exists(x.FullName)) { // NB: If we cannot clean up a directory, we need to make // sure that anyone finding it later won't attempt to run // Squirrel events on it. We'll mark it with a .dead file MarkAppFolderAsDead(x.FullName); } } catch (UnauthorizedAccessException ex) { Log.Warn("Couldn't delete directory: " + x.FullName, ex); // NB: Same deal as above MarkAppFolderAsDead(x.FullName); } }); // Clean up the packages directory too var releasesFile = Utility.LocalReleaseFileForAppDir(rootAppDirectory); var entries = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesFile, Encoding.UTF8)); var pkgDir = Utility.PackageDirectoryForAppDir(rootAppDirectory); var releaseEntry = default(ReleaseEntry); foreach (var entry in entries) { if (entry.Version == currentVersion) { releaseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(pkgDir, entry.Filename)); continue; } File.Delete(Path.Combine(pkgDir, entry.Filename)); } ReleaseEntry.WriteReleaseFile(new[] { releaseEntry }, releasesFile); }
public void Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null) { targetDir = targetDir ?? ".\\Releases"; packagesDir = packagesDir ?? "."; bootstrapperExe = bootstrapperExe ?? ".\\Setup.exe"; if (!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } if (!File.Exists(bootstrapperExe)) { bootstrapperExe = Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Setup.exe"); } this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe); var di = new DirectoryInfo(targetDir); File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true); var allNuGetFiles = di.EnumerateFiles() .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)); var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full")); var releaseFilePath = Path.Combine(di.FullName, "RELEASES"); var previousReleases = Enumerable.Empty <ReleaseEntry>(); if (File.Exists(releaseFilePath)) { previousReleases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8)); } foreach (var file in toProcess) { this.Log().Info("Creating release package: " + file.FullName); var rp = new ReleasePackage(file.FullName); rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), packagesDir); var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir); if (prev != null) { var deltaBuilder = new DeltaPackageBuilder(); deltaBuilder.CreateDeltaPackage(prev, rp, Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta"))); } } foreach (var file in toProcess) { File.Delete(file.FullName); } var releaseEntries = allNuGetFiles.Select(x => ReleaseEntry.GenerateFromFile(x.FullName)); ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath); var targetSetupExe = Path.Combine(di.FullName, "Setup.exe"); var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First(); File.Copy(bootstrapperExe, targetSetupExe, true); var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName); try { var zip = File.ReadAllBytes(zipPath); IntPtr handle = NativeMethods.BeginUpdateResource(targetSetupExe, false); if (handle == IntPtr.Zero) { throw new Win32Exception(); } if (!NativeMethods.UpdateResource(handle, "DATA", new IntPtr(131), 0x0409, zip, zip.Length)) { throw new Win32Exception(); } if (!NativeMethods.EndUpdateResource(handle, false)) { throw new Win32Exception(); } } catch (Exception ex) { this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex); } finally { File.Delete(zipPath); } }
public static async Task SyncFromGitHub(string repoUrl, string token, DirectoryInfo releaseDirectoryInfo) { var repoUri = new Uri(repoUrl); var userAgent = new ProductHeaderValue("SyncReleases", Assembly.GetExecutingAssembly().GetName().Version.ToString()); var client = new GitHubClient(userAgent, repoUri); if (token != null) { client.Credentials = new Credentials(token); } var nwo = nwoFromRepoUrl(repoUrl); var releases = (await client.Release.GetAll(nwo.Item1, nwo.Item2)) .OrderByDescending(x => x.PublishedAt) .Take(5); await releases.ForEachAsync(async release => { // NB: Why do I have to double-fetch the release assets? It's already in GetAll var assets = await client.Release.GetAssets(nwo.Item1, nwo.Item2, release.Id); await assets .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Where(x => { var fi = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name)); return(!(fi.Exists && fi.Length == x.Size)); }) .ForEachAsync(async x => { var target = new FileInfo(Path.Combine(releaseDirectoryInfo.FullName, x.Name)); if (target.Exists) { target.Delete(); } await retryAsync(3, async() => { var hc = new HttpClient(); var rq = new HttpRequestMessage(HttpMethod.Get, x.Url); rq.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/octet-stream")); rq.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(userAgent.Name, userAgent.Version)); if (token != null) { rq.Headers.Add("Authorization", "Bearer " + token); } var resp = await hc.SendAsync(rq); resp.EnsureSuccessStatusCode(); using (var from = await resp.Content.ReadAsStreamAsync()) using (var to = File.OpenWrite(target.FullName)) { await from.CopyToAsync(to); } }); }); }); var entries = releaseDirectoryInfo.GetFiles("*.nupkg") .AsParallel() .Select(x => ReleaseEntry.GenerateFromFile(x.FullName)); ReleaseEntry.WriteReleaseFile(entries, Path.Combine(releaseDirectoryInfo.FullName, "RELEASES")); }
static int Main(string[] args) { var command = ""; var target = ""; var appName = default(string); var showHelp = false; var opts = new OptionSet() { { "c|command=", "One of 'check' or 'update' to return the latest update information, or to perform the update", v => command = v }, { "t|update-target=", "The URL or directory to download updates from", v => target = v }, { "n|app-name=", "(Optional) The name of the application, only necessary if updater.exe isn't in the install directory", v => appName = v }, { "h|help", "Show this message and exit", v => showHelp = v != null }, }; opts.Parse(args); if (!new[] { "check", "update", "install", }.Any(x => command.ToLowerInvariant() == x)) { Console.Error.WriteLine("Command must be either 'check' or 'update'"); showHelp = true; } if (!Directory.Exists(target) && !File.Exists(target) && !isAUrl(target)) { Console.Error.WriteLine("Target must be either a directory or a URL to check for updates"); showHelp = true; } if (showHelp) { Console.WriteLine("\nSquirrel.Updater.exe - Check for updates or update an application"); Console.WriteLine(@"Usage: Squirrel.Updater.exe [options]"); Console.WriteLine("Options:"); foreach (var v in opts) { if (v.GetNames().Length != 2) { Console.WriteLine(" --{0} - {1}", v.GetNames()[0], v.Description); } else { Console.WriteLine(" -{0}/--{1} - {2}", v.GetNames()[0], v.GetNames()[1], v.Description); } } return(0); } appName = appName ?? determineAppName(); using (var mgr = new UpdateManager(target, appName, FrameworkVersion.Net40)) { if (command.ToLowerInvariant() == "check") { var updateInfo = default(UpdateInfo); try { updateInfo = mgr.CheckForUpdate().First(); if (updateInfo.ReleasesToApply.Count > 0) { mgr.DownloadReleases(updateInfo.ReleasesToApply).First(); } } catch (Exception ex) { writeJsonForException(ex, "Failed to check for updates"); return(-1); } var releaseNotes = new Dictionary <ReleaseEntry, string>(); try { releaseNotes = (updateInfo.ReleasesToApply.Count > 0) ? updateInfo.FetchReleaseNotes() : releaseNotes; } catch (Exception ex) { // TODO: Find a way to log this } Console.WriteLine(JsonConvert.SerializeObject(new { UpdateInfo = updateInfo, ReleaseNotes = releaseNotes, })); return(0); } if (command.ToLowerInvariant() == "update") { var result = default(ReleaseEntry); try { result = mgr.UpdateAppAsync().Result; } catch (Exception ex) { writeJsonForException(ex, "Failed to update application"); return(-1); } Console.WriteLine(JsonConvert.SerializeObject(result)); return(0); } if (command.ToLowerInvariant() == "install") { var targetRelease = ReleaseEntry.GenerateFromFile(target); mgr.ApplyReleases(UpdateInfo.Create(null, new[] { targetRelease }, Path.GetDirectoryName(target), FrameworkVersion.Net40)).First(); return(0); } } throw new Exception("How even did we get here?"); }