public static UpdateInfo Create(ReleaseEntry currentVersion, IEnumerable<ReleaseEntry> availableReleases, string packageDirectory, FrameworkVersion appFrameworkVersion) { Contract.Requires(availableReleases != null); Contract.Requires(!String.IsNullOrEmpty(packageDirectory)); var latestFull = availableReleases.MaxBy(x => x.Version).FirstOrDefault(x => !x.IsDelta); if (latestFull == null) { throw new Exception("There should always be at least one full release"); } if (currentVersion == null) { return new UpdateInfo(currentVersion, new[] { latestFull }, packageDirectory, appFrameworkVersion); } if (currentVersion.Version == latestFull.Version) { return new UpdateInfo(currentVersion, Enumerable.Empty<ReleaseEntry>(), packageDirectory, appFrameworkVersion); } var newerThanUs = availableReleases.Where(x => x.Version > currentVersion.Version) .OrderBy(v => v.Version); var deltasSize = newerThanUs.Where(x => x.IsDelta).Sum(x => x.Filesize); return (deltasSize < latestFull.Filesize && deltasSize > 0) ? new UpdateInfo(currentVersion, newerThanUs.Where(x => x.IsDelta).ToArray(), packageDirectory, appFrameworkVersion) : new UpdateInfo(currentVersion, new[] { latestFull }, packageDirectory, appFrameworkVersion); }
protected UpdateInfo(ReleaseEntry currentlyInstalledVersion, IEnumerable<ReleaseEntry> releasesToApply, string packageDirectory) { // NB: When bootstrapping, CurrentlyInstalledVersion is null! CurrentlyInstalledVersion = currentlyInstalledVersion; ReleasesToApply = releasesToApply ?? Enumerable.Empty<ReleaseEntry>(); FutureReleaseEntry = ReleasesToApply.MaxBy(x => x.Version).FirstOrDefault(); this.packageDirectory = packageDirectory; }
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)) { this.Log().Info("{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)) { this.Log().Info("{0} hasn't changed, writing dummy file", relativePath); File.Create(targetFile.FullName + ".diff").Dispose(); File.Create(targetFile.FullName + ".shasum").Dispose(); targetFile.Delete(); return; } this.Log().Info("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName); using (var of = File.Create(targetFile.FullName + ".diff")) { BinaryPatchUtility.Create(oldData, newData, of); var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum"); File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); targetFile.Delete(); } }
// // ApplyReleases methods // List<string> installPackageToAppDir(UpdateInfo updateInfo, ReleaseEntry release) { var pkg = new ZipPackage(Path.Combine(rootAppDirectory, "packages", release.Filename)); var target = getDirectoryForRelease(release.Version); // NB: This might happen if we got killed partially through applying the release if (target.Exists) { Utility.DeleteDirectory(target.FullName); } target.Create(); // Copy all of the files out of the lib/ dirs in the NuGet package // into our target App directory. // // NB: We sort this list in order to guarantee that if a Net20 // and a Net40 version of a DLL get shipped, we always end up // with the 4.0 version. pkg.GetFiles().Where(x => pathIsInFrameworkProfile(x, appFrameworkVersion)).OrderBy(x => x.Path) .ForEach(x => { var targetPath = Path.Combine(target.FullName, Path.GetFileName(x.Path)); var fi = fileSystem.GetFileInfo(targetPath); if (fi.Exists) fi.Delete(); using (var inf = x.GetStream()) using (var of = fi.Open(FileMode.CreateNew, FileAccess.Write)) { log.Info("Writing {0} to app directory", targetPath); inf.CopyTo(of); } }); var newCurrentVersion = updateInfo.FutureReleaseEntry.Version; // Perform post-install; clean up the previous version by asking it // which shortcuts to install, and nuking them. Then, run the app's // post install and set up shortcuts. return runPostInstallAndCleanup(newCurrentVersion, updateInfo.IsBootstrapping); }
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(), x.InputPackageFile); // Recursively combine the rest of them return createFullPackagesFromDeltas(releasesToApply.Skip(1), entry); }); }
void checksumPackage(ReleaseEntry downloadedRelease) { var targetPackage = fileSystem.GetFileInfo( Path.Combine(rootAppDirectory, "packages", downloadedRelease.Filename)); if (!targetPackage.Exists) { log.Error("File should exist but doesn't", targetPackage.FullName); throw new Exception("Checksummed file doesn't exist: " + targetPackage.FullName); } if (targetPackage.Length != downloadedRelease.Filesize) { log.Error("File Length should be {0}, is {1}", downloadedRelease.Filesize, targetPackage.Length); targetPackage.Delete(); throw new Exception("Checksummed file size doesn't match: " + targetPackage.FullName); } using (var file = targetPackage.OpenRead()) { var hash = Utility.CalculateStreamSHA1(file); if (!hash.Equals(downloadedRelease.SHA1,StringComparison.OrdinalIgnoreCase)) { log.Error("File SHA1 should be {0}, is {1}", downloadedRelease.SHA1, hash); targetPackage.Delete(); throw new Exception("Checksum doesn't match: " + targetPackage.FullName); } } }
public InstallManager(ReleaseEntry bundledRelease, string targetRootDirectory = null) { BundledRelease = bundledRelease; TargetRootDirectory = targetRootDirectory; log = LogManager.GetLogger<InstallManager>(); }
// // ApplyReleases methods // List<string> installPackageToAppDir(UpdateInfo updateInfo, ReleaseEntry release) { var pkg = new ZipPackage(Path.Combine(rootAppDirectory, "packages", release.Filename)); var target = getDirectoryForRelease(release.Version); // NB: This might happen if we got killed partially through applying the release if (target.Exists) { Utility.DeleteDirectory(target.FullName).Wait(); } target.Create(); // Copy all of the files out of the lib/ dirs in the NuGet package // into our target App directory. // // NB: We sort this list in order to guarantee that if a Net20 // and a Net40 version of a DLL get shipped, we always end up // with the 4.0 version. pkg.GetLibFiles().Where(x => pathIsInFrameworkProfile(x, appFrameworkVersion)) .OrderBy(x => x.Path) .ForEach(x => CopyFileToLocation(target, x)); pkg.GetContentFiles().ForEach(x => CopyFileToLocation(target, x)); var newCurrentVersion = updateInfo.FutureReleaseEntry.Version; // Perform post-install; clean up the previous version by asking it // which shortcuts to install, and nuking them. Then, run the app's // post install and set up shortcuts. return runPostInstallAndCleanup(newCurrentVersion, updateInfo.IsBootstrapping); }
public InstallManager(ReleaseEntry bundledRelease, string targetRootDirectory = null) { BundledRelease = bundledRelease; TargetRootDirectory = targetRootDirectory; }
public ReleasePackage CreateDeltaPackage(ReleasePackage baseFixture, string outputFile) { Contract.Requires(baseFixture != null && baseFixture.ReleasePackageFile != null); Contract.Requires(!String.IsNullOrEmpty(outputFile) && !File.Exists(outputFile)); string baseTempPath = null; string tempPath = null; using (Utility.WithTempDirectory(out baseTempPath)) using (Utility.WithTempDirectory(out tempPath)) { var baseTempInfo = new DirectoryInfo(baseTempPath); var tempInfo = new DirectoryInfo(tempPath); using (var zf = new ZipFile(baseFixture.ReleasePackageFile)) { zf.ExtractAll(baseTempInfo.FullName); } using (var zf = new ZipFile(ReleasePackageFile)) { zf.ExtractAll(tempInfo.FullName); } // Collect a list of relative paths under 'lib' and map them // to their full name. We'll use this later to determine in // the new version of the package whether the file exists or // not. var baseLibFiles = baseTempInfo.GetAllFilesRecursively() .Where(x => x.FullName.ToLowerInvariant().Contains("lib" + Path.DirectorySeparatorChar)) .ToDictionary(k => k.FullName.Replace(baseTempInfo.FullName, ""), v => v.FullName); var newLibDir = tempInfo.GetDirectories().First(x => x.Name.ToLowerInvariant() == "lib"); // 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 newLibDir.GetAllFilesRecursively().ForEach(libFile => { var relativePath = libFile.FullName.Replace(tempInfo.FullName, ""); if (!baseLibFiles.ContainsKey(relativePath)) { this.Log().Info("{0} not found in base package, marking as new", relativePath); return; } var oldData = File.ReadAllBytes(baseLibFiles[relativePath]); var newData = File.ReadAllBytes(libFile.FullName); if (bytesAreIdentical(oldData, newData)) { this.Log().Info("{0} hasn't changed, writing dummy file", relativePath); File.Create(libFile.FullName + ".diff").Dispose(); File.Create(libFile.FullName + ".shasum").Dispose(); libFile.Delete(); return; } this.Log().Info("Delta patching {0} => {1}", baseLibFiles[relativePath], libFile.FullName); using (var of = File.Create(libFile.FullName + ".diff")) { BinaryPatchUtility.Create(oldData, newData, of); var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), libFile.Name + ".shasum"); File.WriteAllText(libFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); libFile.Delete(); } }); addDeltaFilesToContentTypes(tempInfo.FullName); using (var zf = new ZipFile(outputFile)) { zf.AddDirectory(tempInfo.FullName); zf.Save(); } } return(new ReleasePackage(outputFile)); }