private static void SaveActiveVersion(VersionUIComponent component)
        {
            var previousState = component.State;

            component.State = VersionState.Saving;

            bool mainDownloaded      = IsFullyDownloaded(component.version, Package.Main);
            bool gehennaDownloaded   = IsFullyDownloaded(component.version, Package.Gehenna);
            bool prototypeDownloaded = IsFullyDownloaded(component.version, Package.Prototype);
            bool editorDownloaded    = IsFullyDownloaded(component.version, Package.Editor);

            double processedFiles = 0;

            // There is an optimization here, where we could move instead of Copy & Delete -- but it's an edge case, so I'm not worried about it.
            var files = GetRelativeFiles(Settings.Default.activeVersionLocation);

            foreach (var(stem, relativePath) in files)
            {
                var package = DeterminePackage(stem);
                if ((package == Package.Main && !mainDownloaded) ||
                    (package == Package.Gehenna && !gehennaDownloaded) ||
                    (package == Package.Prototype && !prototypeDownloaded) ||
                    (package == Package.Editor && !editorDownloaded))
                {
                    var src = $"{Settings.Default.activeVersionLocation}{Path.DirectorySeparatorChar}{relativePath}";
                    var dst = $"{GetFolder(component.version, package)}{Path.DirectorySeparatorChar}{relativePath}";
                    Utils.FCopy(src, dst);
                }
                if ((package == Package.Gehenna && !Settings.Default.ownsGehenna) ||
                    (package == Package.Prototype && !Settings.Default.ownsPrototype) ||
                    (package == Package.Editor && !Settings.Default.wantsEditor))
                {
                    var src = new FileInfo($"{Settings.Default.activeVersionLocation}{Path.DirectorySeparatorChar}{relativePath}");
                    src.Delete();
                }
                processedFiles++;
                component.SetProgress(processedFiles / files.Count);
            }

            component.State = previousState;
        }
        public void LoadVersions()
        {
            // Ensure that it's safe to discard state
            if (ActionInProgress())
            {
                Debug.Assert(false);
                return;
            }
            foreach (var uiComponent in uiComponents.Values)
            {
                uiComponent.Dispose();
            }
            uiComponents.Clear();

            int installedVersion = DepotManager.GetInstalledVersion();

            this.Height = 50;
            foreach (int version in ManifestData.allVersions)
            {
                var  uiComponent         = new VersionUIComponent(version, this.Height - 50, this);
                bool mainDownloaded      = DepotManager.IsFullyDownloaded(version, Package.Main);
                bool gehennaDownloaded   = DepotManager.IsFullyDownloaded(version, Package.Gehenna);
                bool prototypeDownloaded = DepotManager.IsFullyDownloaded(version, Package.Prototype);
                bool editorDownloaded    = DepotManager.IsFullyDownloaded(version, Package.Editor);

                if (mainDownloaded &&
                    (!Settings.Default.ownsGehenna || gehennaDownloaded) &&
                    (!Settings.Default.ownsPrototype || prototypeDownloaded) &&
                    (!Settings.Default.wantsEditor || editorDownloaded))
                {
                    // We have downloaded everything we should
                    uiComponent.State = VersionState.Downloaded;
                }
                else if (mainDownloaded ||
                         (Settings.Default.ownsGehenna && gehennaDownloaded) ||
                         (Settings.Default.ownsPrototype && prototypeDownloaded) ||
                         (Settings.Default.wantsEditor && editorDownloaded))
                {
                    // We haven't downloaded everything, but we do have *something*
                    uiComponent.State = VersionState.PartiallyDownloaded;
                }
                else
                {
                    // We have nothing downloaded
                    uiComponent.State = VersionState.NotDownloaded;
                }

                if (version == installedVersion)
                {
                    if (uiComponent.State == VersionState.Downloaded && DepotManager.IsFullyCopied(installedVersion))
                    {
                        // Only mark active if the data is fully copied.
                        uiComponent.State = VersionState.Active;
                    }
                    DepotManager.SaveActiveVersionAsync(uiComponent); // Save any additional files that we find in the active version location
                }

                // Only add the version if it's downloaded, common, active in steam, or we're showing all versions
                if (uiComponent.State != VersionState.NotDownloaded ||
                    commonVersions.Contains(version) ||
                    version == installedVersion ||
                    Settings.Default.showAllVersions)
                {
                    uiComponents[version] = uiComponent;
                    this.Height          += 20;
                }
                else
                {
                    uiComponent.Dispose();
                }
            }
        }
        private static void DownloadDepots(VersionUIComponent component)
        {
            var previousState = component.State;

            component.State = VersionState.DownloadPending; // Pending until we acquire the  lock
            lock (downloadLock) {
                int version = component.version;

                Logging.Log($"Downloading depots for {version}");
                var drive = new DriveInfo(new DirectoryInfo(ManifestData.DepotLocation).Root.FullName);
                if (!drive.IsReady)
                {
                    Logging.MessageBox("Drive unavailable", $"Steam install location is in drive {drive.Name}, which is unavailable.");
                    return;
                }

                var neededManifests = new List <SteamManifest>();
                if (!IsFullyDownloaded(version, Package.Main))
                {
                    neededManifests.AddRange(ManifestData.Get(version, Package.Main));
                }
                if (Settings.Default.ownsGehenna && !IsFullyDownloaded(version, Package.Gehenna))
                {
                    neededManifests.AddRange(ManifestData.Get(version, Package.Gehenna));
                }
                if (Settings.Default.ownsPrototype && !IsFullyDownloaded(version, Package.Prototype))
                {
                    neededManifests.AddRange(ManifestData.Get(version, Package.Prototype));
                }
                if (Settings.Default.wantsEditor && !IsFullyDownloaded(version, Package.Editor))
                {
                    neededManifests.AddRange(ManifestData.Get(version, Package.Editor));
                }
                if (neededManifests.Count == 0)
                {
                    Logging.Log($"Attempted to download manifests for {version}, but no manfiests applied.");
                    component.State = VersionState.Downloaded;
                    return;
                }

                if (Settings.Default.steamHack == false)
                {
                    // Note: There are intentional tab characters in this string -- that's because it's a verbatim string.
                    Logging.MessageBox("Unable to download",
                                       @"Steam has broken the download_depots command, so this version can't be downloaded.
To download versions, please change your beta participation in Steam, re-download the game, then re-launch the downpatcher.

Version	| Steam beta
------------|------------------------
440323	| NONE
326589	| previousversion
252786	| legacy_winxp
244371	| speedrun-244371");
                    component.State = previousState;
                    return;
                }

                double totalDownloadSize = 0;
                foreach (var manifest in neededManifests)
                {
                    totalDownloadSize += manifest.size;
                }

                long freeSpace = drive.TotalFreeSpace;
                if (drive.TotalFreeSpace < totalDownloadSize)
                {
                    Logging.MessageBox("Not enough space",
                                       $@"Steam install location is in drive {drive.Name}
has {Math.Round(freeSpace / 1000000000.0, 1)} GB of free space
but {Math.Round(totalDownloadSize / 1000000000.0, 1)} GB are required.");
                    return;
                }

                component.State = VersionState.Downloading;

                { // Keep steam interaction close together, to avoid accidental user interference
                    SteamCommand.OpenConsole();
                    Thread.Sleep(10);
                    foreach (var manifest in neededManifests)
                    {
                        SteamCommand.DownloadDepot(manifest.appId, manifest.depotId, manifest.manifestId);
                    }
                    MainWindow.SetForeground();
                }

                Thread.Sleep(5000); // Extra sleep to avoid a race condition where we check for depots before they're actually cleared.

                while (true)
                {
                    long actualSize = 0;
                    foreach (var manifest in neededManifests)
                    {
                        actualSize += Utils.GetFolderSize(manifest.location);
                    }
                    component.SetProgress(0.8 * actualSize / totalDownloadSize); // 80% - Downloading
                    if (actualSize == totalDownloadSize)
                    {
                        break;
                    }
                    Thread.Sleep(1000);
                }
                component.State = VersionState.Saving;

                long copied = 0;

                // @Performance: Start copying while downloads are in progress?
                foreach (var manifest in neededManifests)
                {
                    CopyAndOverwrite(manifest.location, GetFolder(version, manifest.package), delegate(long fileSize) {
                        copied += fileSize;
                        component.SetProgress(0.8 + 0.2 * (copied / totalDownloadSize)); // 20% - Copying
                    });
                }

                if (drive.TotalFreeSpace < 5 * totalDownloadSize)
                {
                    Logging.Log("Low on disk space, clearing download directory");
                    Directory.Delete(ManifestData.DepotLocation, true);
                }
                component.State = VersionState.Downloaded;
            }
        }
 public static void DownloadDepotsAsync(VersionUIComponent component)
 {
     Utils.RunAsync(delegate { DownloadDepots(component); });
 }
 public static void SaveActiveVersionAsync(VersionUIComponent component)
 {
     Utils.RunAsync(delegate { SaveActiveVersion(component); });
 }
        private static void SetActiveVersion(VersionUIComponent component, Action onSetActiveVersion)
        {
            string activeVersionLocation = Settings.Default.activeVersionLocation;

            component.State = VersionState.CopyPending;
            lock (versionLock) {
                if (component.version == Settings.Default.activeVersion)
                {
                    component.State = VersionState.Active;
                    return;
                }
                Logging.Log($"Changing active version from {Settings.Default.activeVersion} to {component.version}");
                component.State = VersionState.Copying;
                onSetActiveVersion(); // Sets the previously active version to Downloaded

                // Reset active version for the duration of the copy, since any failure will leave us in a bad state.
                Settings.Default.activeVersion = 0;
                Settings.Default.Save();

                // Clean target folder before copying
                try {
                    Directory.Delete(activeVersionLocation, true);
                } catch (DirectoryNotFoundException) {
                    Logging.Log("Caught DirectoryNotFoundException: Folder already deleted");
                } catch (Exception ex) when(ex is UnauthorizedAccessException || ex is IOException)
                {
                    Logging.MessageBox("Folder in use", $"Unable to clear {activeVersionLocation}, please ensure that nothing is using it.");
                    component.State = VersionState.Downloaded;
                    return;
                }
                Logging.Log("Deletion successful");

                // Copy the x86 binaries to the x64 folder. They may be overwritten by the next copy operation if there are real x64 binaries.
                CopyAndOverwrite(GetFolder(component.version, Package.Main) + "/Bin", activeVersionLocation + "/Bin/x64");

                List <Package> requiredPackages = new List <Package> {
                    Package.Main
                };
                if (Settings.Default.ownsGehenna)
                {
                    requiredPackages.Add(Package.Gehenna);
                }
                if (Settings.Default.ownsPrototype)
                {
                    requiredPackages.Add(Package.Prototype);
                }
                if (Settings.Default.wantsEditor)
                {
                    requiredPackages.Add(Package.Editor);
                }

                double totalSize = 0;
                foreach (var package in requiredPackages)
                {
                    totalSize += Utils.GetFolderSize(GetFolder(component.version, package));
                }

                // @Performance Multithreading *may* save time here. I doubt it.
                long copied = 0;
                foreach (var package in requiredPackages)
                {
                    CopyAndOverwrite(GetFolder(component.version, package), activeVersionLocation, delegate(long fileSize) {
                        copied += fileSize;
                        component.SetProgress(copied / totalSize);
                    });
                }

                Settings.Default.activeVersion = component.version;
                Settings.Default.Save();

                component.State = VersionState.Active;
            }
        }
 public static void SetActiveVersionAsync(VersionUIComponent component, Action onSetActiveVersion)
 {
     Utils.RunAsync(delegate { SetActiveVersion(component, onSetActiveVersion); });
 }