1<?php 2 3/** 4 * @author Victor Dubiniuk <dubiniuk@owncloud.com> 5 * 6 * @copyright Copyright (c) 2015, ownCloud, Inc. 7 * @license AGPL-3.0 8 * 9 * This code is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU Affero General Public License, version 3, 11 * as published by the Free Software Foundation. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License, version 3, 19 * along with this program. If not, see <http://www.gnu.org/licenses/> 20 * 21 */ 22 23namespace Owncloud\Updater\Command; 24 25use Owncloud\Updater\Utils\Checkpoint; 26use Owncloud\Updater\Utils\FilesystemHelper; 27use Symfony\Component\Console\Input\InputInterface; 28use Symfony\Component\Console\Output\OutputInterface; 29use Symfony\Component\Process\Exception\ProcessFailedException; 30use Owncloud\Updater\Utils\OccRunner; 31use Owncloud\Updater\Utils\ZipExtractor; 32 33/** 34 * Class ExecuteCoreUpgradeScriptsCommand 35 * 36 * @package Owncloud\Updater\Command 37 */ 38class ExecuteCoreUpgradeScriptsCommand extends Command { 39 40 /** 41 * @var OccRunner $occRunner 42 */ 43 protected $occRunner; 44 45 /** 46 * ExecuteCoreUpgradeScriptsCommand constructor. 47 * 48 * @param null|string $occRunner 49 */ 50 public function __construct($occRunner){ 51 parent::__construct(); 52 $this->occRunner = $occRunner; 53 } 54 55 protected function configure(){ 56 $this 57 ->setName('upgrade:executeCoreUpgradeScripts') 58 ->setDescription('execute core upgrade scripts [danger, might take long]'); 59 } 60 61 /** 62 * @param InputInterface $input 63 * @param OutputInterface $output 64 * @throws \Exception 65 */ 66 protected function execute(InputInterface $input, OutputInterface $output){ 67 $locator = $this->container['utils.locator']; 68 /** @var FilesystemHelper $fsHelper */ 69 $fsHelper = $this->container['utils.filesystemhelper']; 70 $registry = $this->container['utils.registry']; 71 $fetcher = $this->container['utils.fetcher']; 72 /** @var Checkpoint $checkpoint */ 73 $checkpoint = $this->container['utils.checkpoint']; 74 75 $installedVersion = implode('.', $locator->getInstalledVersion()); 76 $registry->set('installedVersion', $installedVersion); 77 78 /** @var \Owncloud\Updater\Utils\Feed $feed */ 79 $feed = $registry->get('feed'); 80 81 if ($feed){ 82 $path = $fetcher->getBaseDownloadPath($feed); 83 $fullExtractionPath = $locator->getExtractionBaseDir() . '/' . $feed->getVersion(); 84 85 if (file_exists($fullExtractionPath)){ 86 $fsHelper->removeIfExists($fullExtractionPath); 87 } 88 try{ 89 $fsHelper->mkdir($fullExtractionPath, true); 90 } catch (\Exception $e){ 91 $output->writeln('Unable create directory ' . $fullExtractionPath); 92 throw $e; 93 } 94 95 $output->writeln('Extracting source into ' . $fullExtractionPath); 96 $extractor = new ZipExtractor($path, $fullExtractionPath, $output); 97 98 try{ 99 $extractor->extract(); 100 } catch (\Exception $e){ 101 $output->writeln('Extraction has been failed'); 102 $fsHelper->removeIfExists($locator->getExtractionBaseDir()); 103 throw $e; 104 } 105 106 $tmpDir = $locator->getExtractionBaseDir() . '/' . $installedVersion; 107 $fsHelper->removeIfExists($tmpDir); 108 $fsHelper->mkdir($tmpDir); 109 $oldSourcesDir = $locator->getOwncloudRootPath(); 110 $newSourcesDir = $fullExtractionPath . '/owncloud'; 111 112 $packageVersion = $this->loadVersion($newSourcesDir); 113 $allowedPreviousVersions = $this->loadAllowedPreviousVersions($newSourcesDir); 114 115 if (!$this->isUpgradeAllowed($installedVersion, $packageVersion, $allowedPreviousVersions)){ 116 $message = sprintf( 117 'Update from %s to %s is not possible. Updates between multiple major versions and downgrades are unsupported.', 118 $installedVersion, 119 $packageVersion 120 ); 121 $this->getApplication()->getLogger()->error($message); 122 $output->writeln('<error>'. $message .'</error>'); 123 return 1; 124 } 125 126 $rootDirContent = array_unique( 127 array_merge( 128 $locator->getRootDirContent(), 129 $locator->getRootDirItemsFromSignature($oldSourcesDir), 130 $locator->getRootDirItemsFromSignature($newSourcesDir) 131 ) 132 ); 133 foreach ($rootDirContent as $dir){ 134 if ($dir === 'updater') { 135 continue; 136 } 137 $this->getApplication()->getLogger()->debug('Replacing ' . $dir); 138 $fsHelper->tripleMove($oldSourcesDir, $newSourcesDir, $tmpDir, $dir); 139 } 140 141 $fsHelper->copyr($tmpDir . '/config/config.php', $oldSourcesDir . '/config/config.php'); 142 143 //Get a new shipped apps list 144 $newAppsDir = $fullExtractionPath . '/owncloud/apps'; 145 $newAppsList = $fsHelper->scandirFiltered($newAppsDir); 146 147 //Remove old apps 148 $appDirectories = $fsHelper->scandirFiltered($oldSourcesDir . '/apps'); 149 $oldAppList = array_intersect($appDirectories, $newAppsList); 150 foreach ($oldAppList as $appDirectory){ 151 $fsHelper->rmdirr($oldSourcesDir . '/apps/' . $appDirectory); 152 } 153 154 foreach ($newAppsList as $appId){ 155 $output->writeln('Copying the application ' . $appId); 156 $fsHelper->copyr($newAppsDir . '/' . $appId, $locator->getOwnCloudRootPath() . '/apps/' . $appId, false); 157 } 158 159 try { 160 $plain = $this->occRunner->run('upgrade'); 161 $output->writeln($plain); 162 } catch (ProcessFailedException $e){ 163 $lastCheckpointId = $checkpoint->getLastCheckpointId(); 164 if ($lastCheckpointId){ 165 $lastCheckpointPath = $checkpoint->getCheckpointPath($lastCheckpointId); 166 $fsHelper->copyr($lastCheckpointPath . '/apps', $oldSourcesDir . '/apps', false); 167 } 168 if ($e->getProcess()->getExitCode() != 3){ 169 throw ($e); 170 } 171 } 172 } 173 } 174 175 public function isUpgradeAllowed($installedVersion, $packageVersion, $canBeUpgradedFrom){ 176 if (version_compare($installedVersion, $packageVersion, '<=')){ 177 foreach ($canBeUpgradedFrom as $allowedPreviousVersion){ 178 if (version_compare($allowedPreviousVersion, $installedVersion, '<=')){ 179 return true; 180 } 181 } 182 } 183 return false; 184 } 185 186 /** 187 * @param string $pathToPackage 188 * @return string 189 */ 190 protected function loadVersion($pathToPackage){ 191 require $pathToPackage . '/version.php'; 192 /** @var $OC_Version array */ 193 return implode('.', $OC_Version); 194 } 195 196 /** 197 * @param string $pathToPackage 198 * @return string[] 199 */ 200 protected function loadAllowedPreviousVersions($pathToPackage) { 201 $canBeUpgradedFrom = $this->loadCanBeUpgradedFrom($pathToPackage); 202 203 $firstItem = reset($canBeUpgradedFrom); 204 if (!is_array($firstItem)){ 205 $canBeUpgradedFrom = [$canBeUpgradedFrom]; 206 } 207 208 $allowedVersions = []; 209 foreach ($canBeUpgradedFrom as $version){ 210 $allowedVersions[] = implode('.', $version); 211 } 212 213 return $allowedVersions; 214 } 215 216 /** 217 * @param string $pathToPackage 218 * @return array 219 */ 220 protected function loadCanBeUpgradedFrom($pathToPackage){ 221 require $pathToPackage . '/version.php'; 222 /** @var array $OC_VersionCanBeUpgradedFrom */ 223 return $OC_VersionCanBeUpgradedFrom; 224 } 225} 226