Esempio n. 1
0
        /// <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);
        }
Esempio n. 2
0
        /// <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);
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        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;
        }
Esempio n. 5
0
        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);
        }
Esempio n. 6
0
        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)));
        }
Esempio n. 7
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")));
        }
Esempio n. 8
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);
        }
Esempio n. 9
0
        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);
        }