Esempio n. 1
0
        private void PatchWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            Logger?.Debug("Patch request");

            // guard against multiple tasks running at the same time
            if (!TryEnterTask())
            {
                return;
            }

            Logger?.Info("Patch started");

            var packageDirectory = Utility.GetTempDirectory();

            try
            {
                // disable UI
                Dispatcher.Invoke(() =>
                {
                    Patch.IsEnabled         = false;
                    TargetVersion.IsEnabled = false;
                });

                var version = e.Argument as Version;

                Package package = PackageRepository.FindPackage(_detectedSnapshot.Version, version);
                Logger?.Info("Package found for source version {0} to target version {1}",
                             package.SourceSnapshot.Version, package.TargetSnapshot.Version);

                // download package contents locally
                Logger?.Info("Attempting to download package contents from {0}", Config.ContentRepoUri);
                package.Save(Path.GetDirectoryName(Config.ContentRepoUri), packageDirectory,
                             WorkerOnProgressChanged, "Downloading package");

                // apply package
                Logger?.Info("Applying package");
                package.Apply(Path.Combine(packageDirectory, package.Id + "\\"), _contentPath,
                              Config.ValidateBeforePackageApply, Config.ValidateAfterPackageApply, WorkerOnProgressChanged,
                              "Applying package");

                // update detected version
                _detectedSnapshot = package.TargetSnapshot;

                Logger?.Info("Patch completed");

                MessageBox.Show("Patch applied successfully!",
                                "Done", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch
            {
                MessageBox.Show("Patch failed! Make sure the base game files haven't been modified in any way. If unsure, please run the updater against a fresh copy.",
                                "Hold up!", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {
                // allow other tasks to run
                ExitTask();

                // cleanup the temp package directory
                Directory.Delete(packageDirectory, true);
            }
        }
Esempio n. 2
0
        //TODO: will need to disable multi-threaded processing of actions during application if specified
        //[JsonProperty(Required = Required.Default)]
        //public bool PreserveActionOrder { get; set; }

        // TODO: when order is not important, perform multi-threaded execution in order of biggest to smallest
        /// <summary>
        /// Applies package actions in-order against the target directory using the contents of the package directory.
        /// </summary>
        /// <param name="packageDirectory">The package contents directory.</param>
        /// <param name="targetDirectory">The target base directory.</param>
        /// <param name="validateBefore">Indicates whether or not the target directory should be validated before package application.</param>
        /// <param name="validateAfter">Indicates whether or not the target directory should be validated after package application.</param>
        /// <param name="progressHandler">The optional handler to report progress to.</param>
        /// <param name="progressStatus">The optional progress status description.</param>
        public void Apply(Path packageDirectory, Path targetDirectory, bool validateBefore = true, bool validateAfter = true, ProgressChangedEventHandler progressHandler = null, string progressStatus = null)
        {
            // validate source content
            if (validateBefore)
            {
                Logger?.Info("Validating package source content");
            }
            if (validateBefore && !new Snapshot(targetDirectory, progressHandler, "Validating source content").Contains(SourceSnapshot))
            {
                throw new DataException("Directory contents do not match the source snapshot.");
            }

            // create temporary directory to contain target backup in case of rollback
            Path backupDirectory = Utility.GetTempDirectory();

            int processedCount = 0;

            // TODO: verify backup/rollback logic
            try
            {
                const string backupStepName = "Performing backup";
                progressHandler?.Invoke(this, new ProgressChangedEventArgs(0, backupStepName));

                // backup content to be modified, skipping NoAction types
                foreach (var action in Actions.Where(a => !(a is NoAction)))
                {
                    Path targetPath = Path.Combine(targetDirectory, action.TargetPath);
                    if (File.Exists(targetPath))
                    {
                        Path backupPath = Path.Combine(backupDirectory, action.TargetPath);
                        Directory.CreateDirectory(Path.GetDirectoryName(backupPath));
                        File.Copy(targetPath, backupPath, true);
                    }

                    // propagate current progress upstream
                    processedCount++;
                    int progressPercentage = (int)(processedCount / (float)Actions.Count * 100);
                    progressHandler?.Invoke(this, new ProgressChangedEventArgs(progressPercentage, backupStepName));
                }

                progressHandler?.Invoke(this, new ProgressChangedEventArgs(0, progressStatus));

                // apply the actions in-order
                processedCount = 0;
                foreach (var action in Actions)
                {
                    action.Run(new ActionContext(targetDirectory, packageDirectory));

                    // propagate current progress upstream
                    processedCount++;
                    int progressPercentage = (int)(processedCount / (float)Actions.Count * 100);
                    progressHandler?.Invoke(this, new ProgressChangedEventArgs(progressPercentage, progressStatus));
                }

                // TODO: selective file validation fed from list of changed files after patch application
                // validate modified content
                if (validateAfter)
                {
                    Logger?.Info("Validating package output content");
                }
                if (validateAfter && !new Snapshot(targetDirectory, progressHandler, "Validating output").Contains(TargetSnapshot))
                {
                    throw new DataException("Directory contents do not match the target snapshot.");
                }
            }
            catch (Exception ex)
            {
                Logger?.Error(ex, "Performing rollback.");

                const string rollbackStepName = "Performing rollback";
                progressHandler?.Invoke(this, new ProgressChangedEventArgs(0, rollbackStepName));
                processedCount = 0;

                // if failure is detected, delete any existing targets and restore any backups
                foreach (var action in Actions.Where(a => !(a is NoAction)))
                {
                    // log any failures encountered during the rollback, keep chugging through regardless
                    try
                    {
                        File.Delete(Path.Combine(targetDirectory, action.TargetPath));
                        Path backupPath = Path.Combine(backupDirectory, action.TargetPath);
                        if (File.Exists(backupPath))
                        {
                            File.Copy(backupPath, Path.Combine(targetDirectory, action.TargetPath), true);
                        }
                    }
                    catch (Exception e)
                    {
                        Logger?.Warn(e, "Unknown package restore failure.");
                    }

                    // propagate current progress upstream
                    processedCount++;
                    int progressPercentage = (int)(processedCount / (float)Actions.Count * 100);
                    progressHandler?.Invoke(this, new ProgressChangedEventArgs(progressPercentage, rollbackStepName));
                }

                // re-throw the original exception
                throw;
            }
            finally
            {
                Directory.Delete(backupDirectory, true);
            }

            // TODO: remove empty directories or leave that up to the package actions?
        }
Esempio n. 3
0
        private void UpdateWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            Logger?.Debug("Update request");

            // guard against multiple tasks running at the same time
            if (!TryEnterTask())
            {
                return;
            }

            try
            {
                // attempt to open the updater repo config
                _updateWorker.ReportProgress(0, "Checking for updates");
                Logger?.Info("Downloading updater repository configuration from {0}", Config.UpdaterRepoUri);
                var repo = Repository.Deserialize(new ReadOnlyFile(Config.UpdaterRepoUri).ReadAllText());

                // check for the newest version available
                var updateSnapshot = repo.Snapshots.OrderByDescending(t => t.Version).FirstOrDefault();
                if (updateSnapshot == null)
                {
                    Logger?.Info("No updates available");
                    return;
                }

                // get the current version
                var binPath        = Assembly.GetExecutingAssembly().Location;
                var binDir         = System.IO.Path.GetDirectoryName(binPath) + "\\";
                var currentVersion = new Version(FileVersionInfo.GetVersionInfo(binPath).FileVersion);

                // abort if no updates available
                if (updateSnapshot.Version <= currentVersion)
                {
                    Logger?.Info("Already updated");
                    return;
                }

                if (!Config.UpdaterAutoUpdate)
                {
                    // give user the choice to accept or decline
                    MessageBoxResult choice = MessageBox.Show(
                        $"Update {updateSnapshot.Version} is available. Would you like to apply it?",
                        "Update Available", MessageBoxButton.YesNo, MessageBoxImage.Information);

                    // abort if declined
                    if (choice != MessageBoxResult.Yes)
                    {
                        Logger?.Debug("Update declined");
                        return;
                    }
                }

                const string backupExtension     = ".backup";
                string       remoteFileDirectory = Path.Combine(Path.GetDirectoryName(Config.UpdaterRepoUri) + "/", updateSnapshot.Version.ToString());

                try
                {
                    // apply updates
                    foreach (var file in updateSnapshot.Files)
                    {
                        Logger?.Info("Updating {0}", file);

                        // build local and remote file paths
                        string remoteFilePath = Path.Combine(remoteFileDirectory + "/", file.Path);
                        string localFilePath  = Path.Combine(binDir, file.Path);

                        // rename local file with backup extension if it exists, deleting any that already exist
                        if (File.Exists(localFilePath))
                        {
                            string backupPath = localFilePath + backupExtension;
                            File.Delete(backupPath);
                            File.Move(localFilePath, backupPath);
                        }

                        // download remote file
                        new ReadOnlyFile(remoteFilePath).Copy(localFilePath);

                        // TODO: validate downloaded content

                        Logger?.Info("Finished updating {0}", file);
                    }

                    Logger?.Info("Update completed, restarting application");

                    // start a new instance of the updated application and exit the existing
                    // TODO: start cmd with timer to delete backup files
                    Process.Start(Assembly.GetExecutingAssembly().Location);
                    Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
                }
                catch
                {
                    foreach (var file in Directory.GetFiles(binDir, "*" + backupExtension, SearchOption.AllDirectories))
                    {
                        string originalPath = file.Substring(0, file.Length - backupExtension.Length);
                        File.Delete(originalPath);
                        File.Move(file, originalPath);
                    }

                    throw;
                }
            }
            finally
            {
                // allow other tasks to run
                ExitTask();
            }
        }