/// <summary> /// Adds the file to the <see cref="ZipArchive"/> with all the needed information /// </summary> /// <param name="zipArchive"><see cref="ZipArchive"/> to add the file too</param> /// <param name="fileStream">Stream of the file contents</param> /// <param name="filepath">Path of the file</param> /// <param name="keepFileStreamOpen">If we should keep <see cref="fileStream"/> open once done with it</param> /// <param name="filesize">The size that the final file should be</param> /// <param name="sha256Hash">The hash that the final file should be</param> private static bool AddFile( ZipArchive zipArchive, Stream fileStream, string filepath, bool keepFileStreamOpen = true, long?filesize = null, string?sha256Hash = null) { //Create token and then wait if something else is currently adding a file filesize ??= fileStream.Length; //Create and add the file contents to the zip lock (zipArchive) { var zipFileStream = zipArchive.CreateEntry(filepath).Open(); fileStream.CopyTo(zipFileStream); zipFileStream.Dispose(); //Create .shasum file if we have some content from the fileStream sha256Hash ??= SHA256Util.CreateSHA256Hash(fileStream); using var zipShasumStream = zipArchive.CreateEntry(Path.ChangeExtension(filepath, ".shasum")).Open(); using var textWriter = new StreamWriter(zipShasumStream); textWriter.Write($"{sha256Hash} {filesize}"); } //Dispose fileStream if we been asked to if (!keepFileStreamOpen) { fileStream.Dispose(); } return(true); }
/// <summary> /// Reports if this <see cref="ReleaseEntry"/> is valid and can be applied /// </summary> /// <param name="applicationVersion">What is the application version is</param> /// <param name="checkFile">If we should also check the update file and not just the metadata we have about it and if it's currently on disk</param> public virtual bool IsValidReleaseEntry(SemanticVersion applicationVersion, bool checkFile = false) { //If we want to check the file then we want to check the SHA256 + file size if (checkFile) { //Check that file exists if (!File.Exists(FileLocation)) { _logger.Warning("{0} doesn't exist, this release entry isn't valid", FileLocation); return(false); } var file = StreamUtil.SafeOpenRead(FileLocation); if (file?.Length != Filesize || !SHA256Util.CheckSHA256(file, SHA256)) { file?.Dispose(); _logger.Warning("{0} validation failed, this release entry isn't valid", FileLocation); return(false); } file.Dispose(); } //Check that this Version is higher then what we are running now return(applicationVersion < Version); }
/// <summary> /// Gets the hash and filesize from a file that contains data about a file we need to use for updating /// </summary> /// <param name="fileStream">Stream of that file</param> /// <returns>SHA256 hash and filesize that is expected</returns> public static async Task <(string?sha256Hash, long filesize)> GetShasumDetails(this Stream fileStream) { //Grab the text from the file var textStream = new StreamReader(fileStream); var text = await textStream.ReadToEndAsync(); textStream.Dispose(); //Return nothing if we don't have anything if (string.IsNullOrWhiteSpace(text)) { return(null, -1); } //Grab what we need, checking that it's what we expect var textS = text.Split(' '); var hash = textS[0]; if (textS.Length != 2 || string.IsNullOrWhiteSpace(hash) || !SHA256Util.IsValidSHA256(hash) || !long.TryParse(textS[1], out var filesize)) { return(null, -1); } return(hash, filesize); }
public ReleaseEntry( string sha256, string filename, long filesize, bool isDelta, SemanticVersion version, string folderPath, SemanticVersion?oldVersion = null, object?tag = null, int?stagingPercentage = null) { //If it's a delta file then we should also be given an oldVersion if (isDelta) { if (oldVersion == null) { throw new OldVersionRequiredException(); } OldVersion = oldVersion; } if (filesize < 0) { throw new BadFilesizeException(); } //Check hash and file name/path if (!SHA256Util.IsValidSHA256(sha256)) { throw new InvalidHashException(); } if (!filename.IsValidForFileName(out var invalidChar)) { throw new InvalidFileNameException(invalidChar); } if (!folderPath.IsValidForFilePath(out var invalidPathChar)) { throw new InvalidFilePathException(invalidPathChar); } _logger = LoggingCreator.CreateLogger($"{nameof(ReleaseEntry)} ({filename})"); SHA256 = sha256; Filename = filename; Filesize = filesize; IsDelta = isDelta; Version = version; FileLocation = Path.Combine(folderPath, Filename); StagingPercentage = stagingPercentage; Tag = tag; }
private static bool IsDeltaFile(string baseFileLocation, string newFileLocation) { //Get the two files from disk using var baseFileStream = File.OpenRead(baseFileLocation); using var newFileStream = File.OpenRead(newFileLocation); //See if the filesize or hash is different, if so then the file has changed var hasChanged = baseFileStream.Length != newFileStream.Length || SHA256Util.CreateSHA256Hash(baseFileStream) != SHA256Util.CreateSHA256Hash(newFileStream); return(hasChanged); }
public void IsValidReleaseEntry_OldVersionCheck() { var data = new byte[69]; Randomizer.NextBytes(data); var hash = SHA256Util.CreateSHA256Hash(data); var version = new SemanticVersion(1, 2, 0); Assert.DoesNotThrow(() => new ReleaseEntry(hash, "wew", data.Length, false, version, string.Empty)); Assert.Throws <Exception>(() => new ReleaseEntry(hash, "wew", data.Length, true, version, string.Empty)); Assert.DoesNotThrow(() => new ReleaseEntry(hash, "wew", data.Length, true, version, string.Empty, new SemanticVersion(1, 1, 0))); }
private static ReleaseEntry CreateUpdate(string fileLocation, SemanticVersion?version = null, SemanticVersion?oldVersion = null) { //Get details about update file var releaseFileLocation = fileLocation; var fileStream = File.OpenRead(releaseFileLocation); var fileHash = SHA256Util.CreateSHA256Hash(fileStream); var fileLength = fileStream.Length; fileStream.Dispose(); return(new ReleaseEntry( fileHash, Path.GetFileName(releaseFileLocation), fileLength, true, version ?? SemanticVersion.Parse("2021.129.1"), Path.GetDirectoryName(fileLocation), oldVersion ?? SemanticVersion.Parse("2021.129.0"))); }
/// <summary> /// Creates a delta file and then adds it the <see cref="ZipArchive"/> /// </summary> /// <param name="tempFolder">Where the temp folder is located</param> /// <param name="zipArchive"><see cref="ZipArchive"/> to add the file too</param> /// <param name="baseFileLocation">Old file</param> /// <param name="newFileLocation">New file</param> /// <param name="intendedOs">What OS this delta file will be intended for</param> /// <param name="progress">Progress of creating delta file (If possible)</param> /// <param name="extensionEnd">What to add onto the end of the extension (If needed)</param> /// <returns>If we was able to create the delta file</returns> private bool AddDeltaFile( TemporaryFolder tempFolder, ZipArchive zipArchive, string baseFileLocation, string newFileLocation, OSPlatform?intendedOs = null, Action <double>?progress = null, string?extensionEnd = null) { //Create where the delta file can be stored to grab once made using var tmpDeltaFile = tempFolder.CreateTemporaryFile(); //Try to create diff file, outputting extension (and maybe a stream) based on what was used to make it if (!DeltaCreation.CreateDeltaFile( tempFolder, baseFileLocation, newFileLocation, tmpDeltaFile.Location, intendedOs, out var extension, out var deltaFileStream)) { //Wasn't able to create delta file, report back as fail _logger.Error("Wasn't able to create delta file"); return(false); } //Check that we got something to work with if (deltaFileStream == null && !File.Exists(tmpDeltaFile.Location)) { _logger.Error("We have no delta file/stream to work off somehow"); return(false); } //Get hash and filesize to add to file var newFileStream = File.OpenRead(newFileLocation); var newFilesize = newFileStream.Length; /*Check that the delta file will take up less space, if not fail here * as it's not worth making it a delta file (Will take more time and processing power then just copying on the user's device)*/ if ((deltaFileStream?.Length ?? new FileInfo(tmpDeltaFile.Location).Length) >= newFilesize) { _logger.Warning("{0} will take up more space as a delta file then as a \"new\" file, failing to trigger it being added as a new file", baseFileLocation.GetRelativePath(newFileLocation)); newFileStream.Dispose(); deltaFileStream?.Dispose(); return(false); } var hash = SHA256Util.CreateSHA256Hash(newFileStream); newFileStream.Dispose(); //Grab file and add it to the set of files deltaFileStream ??= tmpDeltaFile.GetStream(FileMode.Open); var addSuccessful = AddFile( zipArchive, deltaFileStream, baseFileLocation.GetRelativePath(newFileLocation) + extension + extensionEnd, filesize: newFilesize, sha256Hash: hash); //Dispose stream and report back if we was able to add file deltaFileStream.Dispose(); return(addSuccessful); }
public bool CreateDeltaPackage( ApplicationMetadata applicationMetadata, string newVersionLocation, SemanticVersion newVersion, string baseVersionLocation, SemanticVersion oldVersion, string outputFolder, string deltaUpdateLocation, OSPlatform?intendedOs = null, Action <double>?progress = null) { if (!Directory.Exists(newVersionLocation) || !Directory.Exists(baseVersionLocation)) { _logger.Error("One of the folders don't exist, can't create delta update...."); return(false); } _logger.Debug("Creating delta file"); var zipArchive = CreateZipArchive(deltaUpdateLocation); var tempFolder = new TemporaryFolder(applicationMetadata.TempFolder); void Cleanup() { lock (zipArchive) { zipArchive.Dispose(); } tempFolder.Dispose(); progress?.Invoke(1); } //Get all the files that are in the new version (Removing the Path so we only have the relative path of the file) var newVersionFiles = Directory.EnumerateFiles(newVersionLocation, "*", SearchOption.AllDirectories) .RemovePath(newVersionLocation).ToArray(); //and get the files from the old version var baseVersionFiles = Directory.EnumerateFiles(baseVersionLocation, "*", SearchOption.AllDirectories) .RemovePath(baseVersionLocation).ToArray(); //Find any files that are in both version and process them based on if they had any changes var sameFiles = newVersionFiles.Where(x => baseVersionFiles.Contains(x)).ToArray(); var newFiles = newVersionFiles.Where(x => !sameFiles.Contains(x)).ToArray(); var progressReport = new ProgressReport(newFiles.Length + sameFiles.Length, progress); var deltaFiles = new List <string>(sameFiles.Length); //First process any files that didn't change, don't even count them in the progress as it will be quick af _logger.Information("Processing files that are in both versions"); foreach (var maybeDeltaFile in sameFiles) { _logger.Debug("Processing possible delta file {0}", maybeDeltaFile); var newFileLocation = Path.Combine(newVersionLocation, maybeDeltaFile); /*See if we got a delta file, if so then store it for * processing after files that haven't changed*/ if (IsDeltaFile(Path.Combine(baseVersionLocation, maybeDeltaFile), newFileLocation)) { deltaFiles.Add(maybeDeltaFile); continue; } //Add a pointer to the file that hasn't changed _logger.Debug("{0} hasn't changed, processing as unchanged file", maybeDeltaFile); using var fileStream = File.OpenRead(newFileLocation); if (AddSameFile(zipArchive, maybeDeltaFile, SHA256Util.CreateSHA256Hash(fileStream))) { progressReport.ProcessedFile(); continue; } _logger.Warning("We wasn't able to add {0} as a file that was unchanged, adding as a \"new\" file", maybeDeltaFile); //We wasn't able to add the file as a pointer, try to add it as a new file if (!AddNewFile(zipArchive, fileStream, maybeDeltaFile)) { //Hard bail if we can't even do that _logger.Error("Wasn't able to process {0} as a new file as well, bailing", maybeDeltaFile); Cleanup(); return(false); } progressReport.ProcessedFile(); } //TODO: add "moved files" //Now process files that was added into the new version _logger.Information("Processing files that only exist in the new version"); foreach (var newFile in newFiles) { _logger.Debug("Processing new file {0}", newFile); //Process new file using var fileStream = File.OpenRead(Path.Combine(newVersionLocation, newFile)); if (AddNewFile(zipArchive, fileStream, newFile)) { progressReport.ProcessedFile(); continue; } //if we can't add it then hard fail, can't do anything to save this _logger.Error("Wasn't able to process new file, bailing"); Cleanup(); return(false); } //Now process files that changed var result = Parallel.ForEach(deltaFiles, (deltaFile, state) => { var deltaFileLocation = Path.Combine(newVersionLocation, deltaFile); _logger.Debug("Processing changed file {0}", deltaFile); //Try to add the file as a delta file if (AddDeltaFile(tempFolder, zipArchive, Path.Combine(baseVersionLocation, deltaFile), deltaFileLocation, intendedOs, (pro) => progressReport.PartialProcessedFile(pro))) { progressReport.ProcessedFile(); return; } //If we can't make the file as a delta file try to create it as a "new" file _logger.Warning("Wasn't able to make delta file, adding file as \"new\" file"); using var fileStream = File.OpenRead(Path.Combine(newVersionLocation, deltaFileLocation)); if (AddNewFile(zipArchive, fileStream, deltaFile)) { progressReport.ProcessedFile(); return; } //Hard bail if we can't even do that _logger.Error("Wasn't able to process file as a new file, bailing"); Cleanup(); state.Break(); }); //This will return false if something failed if (!result.IsCompleted) { return(false); } if (ShouldMakeLoader && !AddLoaderFile( tempFolder, applicationMetadata, zipArchive, newVersion, newVersionLocation, intendedOs, oldVersion, outputFolder)) { _logger.Error("Wasn't able to create loader for this application"); Cleanup(); return(false); } progressReport.ProcessedFile(); //We have created the delta file if we get here, do cleanup and then report as success! _logger.Information("We are done with creating the delta file, cleaning up"); Cleanup(); return(true); }