"use strict";
const Promise = require("bluebird");
const child_process_1 = require("child_process");
const electron_1 = require("electron");
const Utils = require("../util/electronutils");
const events_1 = require("events");
const path = require("path");
/**
 * Windows implementation of the AutoUpdater. This re-implements the built-in Electron AutoUpdater module for Windows.
 */
class WindowsUpdater extends events_1.EventEmitter {
    constructor(cacheInfo) {
        super();
        this.cacheInfo = cacheInfo;
        this.updateDownloadUrl = null;
        this.currentRequest = null;
        this.downloadedInstallerPath = null;
    }
    /**
     * Simply saves the URL to query when checking for an update. The reason we put this in a separate method is to be
     * consistent with how the built-in AutoUpdater is implemented (we use the built-in AutoUpdater for Mac updates).
     */
    setFeedURL(url) {
        this.updateDownloadUrl = url;
    }
    /**
     * Queries the product's update download URL to check if an update is available. If an update is available, the
     * installer for that update is downloaded to a cache in the temp folder. This method emits the same events as the
     * built-in Electron AutoUpdater:
     *   checking-for-update:  Emitted at the start of the method
     *   update-not-available: Emitted if there is no update available
     *   update-available:     Emitted when an update was found and is being downloaded
     *   update-downloaded:    Emitted by the downloadUpdate() method when the installer finishes downloading
     *   error:                Emitted when an error of any kind occurs
     */
    checkForUpdates() {
        if (!this.updateDownloadUrl) {
            throw new Error("No feed url set");
        }
        if (this.currentRequest) {
            return;
        }
        this.emit("checking-for-update");
        // Query the product's update download URL
        this.currentRequest = Utils.requestAsStream({ url: this.updateDownloadUrl })
            .then(Utils.asJson)
            .then(update => {
            // As per Electron's AutoUpdater module, the queried endpoint must return a JSON object containing a
            // "url" property. This url property is the download location of the installer for the update.
            if (!update || !update.url) {
                this.emit("update-not-available");
                return this.cleanup();
            }
            this.emit("update-available");
            // Download the installer for the update
            return this.downloadUpdate(update);
        })
            .catch((e) => {
            this.emit("error", e);
        })
            .finally(() => {
            this.currentRequest = null;
        });
    }
    /**
     * As per Electron's AutoUpdater module, this method quits the app and performs the update. In our specific
     * implementation, we download the installer to a temporary cache, launch the installer as a detached child
     * process, and then exit the app.
     */
    quitAndInstall() {
        if (!this.downloadedInstallerPath) {
            return;
        }
        child_process_1.spawn(this.downloadedInstallerPath, ["/silent", "/mergetasks=runpxt,!desktopicon,!quicklaunchicon"], {
            detached: true,
            stdio: ["ignore", "ignore", "ignore"]
        });
        electron_1.app.quit();
    }
    getInstallerName(version) {
        return `PxtSetup-${version}.exe`;
    }
    getInstallerPath(version) {
        return path.join(this.cacheInfo.cachePath, this.getInstallerName(version));
    }
    /**
     * Downloads the installer for the specified Update, using the Update.url property. Emits update-downloaded when
     * the download is complete.
     */
    downloadUpdate(update) {
        let installerPath;
        return this.cleanup(update.version)
            .then(() => {
            installerPath = this.getInstallerPath(update.version);
            return Utils.fileExistsAsync(installerPath);
        }).then((exists) => {
            if (exists) {
                return Promise.resolve();
            }
            const url = update.url;
            const downloadPath = `${installerPath}.tmp`;
            return Utils.requestAsStream({ url })
                .then(context => Utils.download(downloadPath, context))
                .then(() => Utils.fsRenamePromise(downloadPath, installerPath));
        })
            .then(() => {
            this.downloadedInstallerPath = installerPath;
            this.emit("update-downloaded");
        });
    }
    /**
     * Utility method to clean the installer cache of old installers.
     */
    cleanup(exceptVersion = null) {
        return Utils.fsReaddirPromise(this.cacheInfo.cachePath)
            .then((files) => {
            const escapedVersion = exceptVersion && exceptVersion.replace(/\./g, "\\.");
            const filterRegExp = new RegExp(`-${escapedVersion}\\.exe$`);
            files = files.filter((f) => {
                if (f === this.cacheInfo.releaseManifestFileName) {
                    return false;
                }
                if (!exceptVersion) {
                    return true;
                }
                return !filterRegExp.test(f);
            });
            return Promise.map(files, (f) => {
                return Utils.fsUnlinkPromise(path.join(this.cacheInfo.cachePath, f));
            })
                .then(() => null);
        });
    }
}
exports.WindowsUpdater = WindowsUpdater;
