setName('self-upgrade')
            ->setAliases(['selfupgrade', 'selfupdate'])
            ->addOption(
                'force',
                'f',
                InputOption::VALUE_NONE,
                'Force re-fetching the data from remote'
            )
            ->addOption(
                'all-yes',
                'y',
                InputOption::VALUE_NONE,
                'Assumes yes (or best approach) instead of prompting'
            )
            ->addOption(
                'overwrite',
                'o',
                InputOption::VALUE_NONE,
                'Option to overwrite packages if they already exist'
            )
            ->addOption(
                'timeout',
                't',
                InputOption::VALUE_OPTIONAL,
                'Option to set the timeout in seconds when downloading the update (0 for no timeout)',
                30
            )
            ->setDescription('Detects and performs an update of Grav itself when available')
            ->setHelp('The update command updates Grav itself when a new version is available');
    }
    protected function serve()
    {
        $this->upgrader = new Upgrader($this->input->getOption('force'));
        $this->all_yes = $this->input->getOption('all-yes');
        $this->overwrite = $this->input->getOption('overwrite');
        $this->timeout = (int) $this->input->getOption('timeout');
        $this->displayGPMRelease();
        $update = $this->upgrader->getAssets()['grav-update'];
        $local = $this->upgrader->getLocalVersion();
        $remote = $this->upgrader->getRemoteVersion();
        $release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
        if (!$this->upgrader->meetsRequirements()) {
            $this->output->writeln('ATTENTION:');
            $this->output->writeln('   Grav has increased the minimum PHP requirement.');
            $this->output->writeln('   You are currently running PHP ' . phpversion() . ', but PHP ' . $this->upgrader->minPHPVersion() . ' is required.');
            $this->output->writeln('   Additional information: http://getgrav.org/blog/changing-php-requirements');
            $this->output->writeln('');
            $this->output->writeln('Selfupgrade aborted.');
            $this->output->writeln('');
            exit;
        }
        if (!$this->overwrite && !$this->upgrader->isUpgradable()) {
            $this->output->writeln("You are already running the latest version of Grav (v{$local}) released on {$release}");
            exit;
        }
        Installer::isValidDestination(GRAV_ROOT . '/system');
        if (Installer::IS_LINK === Installer::lastErrorCode()) {
            $this->output->writeln('ATTENTION: Grav is symlinked, cannot upgrade, aborting...');
            $this->output->writeln('');
            $this->output->writeln("You are currently running a symbolically linked Grav v{$local}. Latest available is v{$remote}.");
            exit;
        }
        // not used but preloaded just in case!
        new ArrayInput([]);
        $questionHelper = $this->getHelper('question');
        $this->output->writeln("Grav v{$remote} is now available [release date: {$release}].");
        $this->output->writeln('You are currently using v' . GRAV_VERSION . '.');
        if (!$this->all_yes) {
            $question = new ConfirmationQuestion('Would you like to read the changelog before proceeding? [y|N] ',
                false);
            $answer = $questionHelper->ask($this->input, $this->output, $question);
            if ($answer) {
                $changelog = $this->upgrader->getChangelog(GRAV_VERSION);
                $this->output->writeln('');
                foreach ($changelog as $version => $log) {
                    $title = $version . ' [' . $log['date'] . ']';
                    $content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
                        return "\n" . ucfirst($match[1]) . ':';
                    }, $log['content']);
                    $this->output->writeln($title);
                    $this->output->writeln(str_repeat('-', \strlen($title)));
                    $this->output->writeln($content);
                    $this->output->writeln('');
                }
                $question = new ConfirmationQuestion('Press [ENTER] to continue.', true);
                $questionHelper->ask($this->input, $this->output, $question);
            }
            $question = new ConfirmationQuestion('Would you like to upgrade now? [y|N] ', false);
            $answer = $questionHelper->ask($this->input, $this->output, $question);
            if (!$answer) {
                $this->output->writeln('Aborting...');
                exit;
            }
        }
        $this->output->writeln('');
        $this->output->writeln("Preparing to upgrade to v{$remote}..");
        $this->output->write("  |- Downloading upgrade [{$this->formatBytes($update['size'])}]...     0%");
        $this->file = $this->download($update);
        $this->output->write('  |- Installing upgrade...  ');
        $installation = $this->upgrade();
        if (!$installation) {
            $this->output->writeln("  '- Installation failed or aborted.");
            $this->output->writeln('');
        } else {
            $this->output->writeln("  '- Success!  ");
            $this->output->writeln('');
        }
        // clear cache after successful upgrade
        $this->clearCache('all');
    }
    /**
     * @param array $package
     *
     * @return string
     */
    private function download($package)
    {
        $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
        $this->tmp = $tmp_dir . '/Grav-' . uniqid('', false);
        $options = [
            'curl' => [
                CURLOPT_TIMEOUT => $this->timeout,
            ],
            'fopen' => [
                'timeout' => $this->timeout,
            ],
        ];
        $output = Response::get($package['download'], $options, [$this, 'progress']);
        Folder::create($this->tmp);
        $this->output->write("\x0D");
        $this->output->write("  |- Downloading upgrade [{$this->formatBytes($package['size'])}]...   100%");
        $this->output->writeln('');
        file_put_contents($this->tmp . DS . $package['name'], $output);
        return $this->tmp . DS . $package['name'];
    }
    /**
     * @return bool
     */
    private function upgrade()
    {
        if ($this->file) {
            $folder = Installer::unZip($this->file, $this->tmp . '/zip');
        } else {
            $folder = false;
        }
        static::upgradeGrav($this->file, $folder);
        $errorCode = Installer::lastErrorCode();
        if ($this->tmp) {
            Folder::delete($this->tmp);
        }
        if ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) {
            $this->output->write("\x0D");
            // extra white spaces to clear out the buffer properly
            $this->output->writeln('  |- Installing upgrade...    error                             ');
            $this->output->writeln("  |  '- " . Installer::lastErrorMsg());
            return false;
        }
        $this->output->write("\x0D");
        // extra white spaces to clear out the buffer properly
        $this->output->writeln('  |- Installing upgrade...    ok                             ');
        return true;
    }
    /**
     * @param array $progress
     */
    public function progress($progress)
    {
        $this->output->write("\x0D");
        $this->output->write("  |- Downloading upgrade [{$this->formatBytes($progress['filesize']) }]... " . str_pad($progress['percent'],
                5, ' ', STR_PAD_LEFT) . '%');
    }
    /**
     * @param int|float $size
     * @param int $precision
     *
     * @return string
     */
    public function formatBytes($size, $precision = 2)
    {
        $base = log($size) / log(1024);
        $suffixes = array('', 'k', 'M', 'G', 'T');
        return round(1024 ** ($base - floor($base)), $precision) . $suffixes[(int)floor($base)];
    }
    private function upgradeGrav($zip, $folder, $keepFolder = false)
    {
        static $ignores = [
            'backup',
            'cache',
            'images',
            'logs',
            'tmp',
            'user',
            '.htaccess',
            'robots.txt'
        ];
        if (!is_dir($folder)) {
            Installer::setError('Invalid source folder');
        }
        try {
            $script = $folder . '/system/install.php';
            /** Install $installer */
            if ((file_exists($script) && $install = include $script) && is_callable($install)) {
                $install($zip);
            } else {
                Installer::install(
                    $zip,
                    GRAV_ROOT,
                    ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $ignores],
                    $folder,
                    $keepFolder
                );
                Cache::clearCache();
            }
        } catch (\Exception $e) {
            Installer::setError($e->getMessage());
        }
    }
}