PageRenderTime 25ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/src/BigWhoop/SVN2FTP.php

https://github.com/bigwhoop/SVN2FTP
PHP | 354 lines | 245 code | 56 blank | 53 comment | 14 complexity | 8a3de841b56381d76c7e0cf96575a595 MD5 | raw file
  1. <?php
  2. namespace BigWhoop;
  3. use BigWhoop\SVN2FTP;
  4. use BigWhoop\SVN2FTP\SVN;
  5. use BigWhoop\SVN2FTP\FTP;
  6. class SVN2FTP
  7. {
  8. const VERSION = '1.0.0-beta';
  9. /**
  10. * @var array
  11. */
  12. protected $_config = array(
  13. 'project' => array(
  14. 'name' => 'Untitled Project',
  15. 'force' => false,
  16. ),
  17. 'ftp' => array(),
  18. 'svn' => array(),
  19. );
  20. /**
  21. * @var string
  22. */
  23. protected $_revision = null;
  24. /**
  25. * Register our own class loader
  26. */
  27. static public function registerAutoloader()
  28. {
  29. spl_autoload_register(array(get_called_class(), 'loadClass'));
  30. }
  31. /**
  32. * Try to load a class according to PSR-0
  33. * http://groups.google.com/group/php-standards/web/psr-0-final-proposal
  34. *
  35. * @param string $className
  36. */
  37. static public function loadClass($className)
  38. {
  39. $className = ltrim($className, '\\');
  40. $fileName = $namespace = '';
  41. if (false !== ($lastNsPos = strripos($className, '\\'))) {
  42. $namespace = substr($className, 0, $lastNsPos);
  43. $className = substr($className, $lastNsPos + 1);
  44. $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
  45. }
  46. $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
  47. require $fileName;
  48. }
  49. static protected function _getCliOptionsDefinition()
  50. {
  51. static $opts = null;
  52. if (null === $opts) {
  53. $opts = new \Zend_Console_Getopt(array(
  54. 'config|c=s' => 'Path to the config file',
  55. 'revision|r=s' => 'List or range of the revisions to update. '
  56. . 'For example 193-204 or 192,194,195.',
  57. ));
  58. }
  59. return $opts;
  60. }
  61. static public function getUsageMessage()
  62. {
  63. return static::_getCliOptionsDefinition()->getUsageMessage();
  64. }
  65. static public function printHeader()
  66. {
  67. static::printLine('---');
  68. static::printLine('SVN2FTP ' . self::VERSION);
  69. static::printLine('---');
  70. static::printLine('Copyright ' . date('Y') . ' Philippe Gerber <philippe@bigwhoop.ch>');
  71. static::printLine('Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported');
  72. static::printLine('http://creativecommons.org/licenses/by-sa/3.0');
  73. static::printLine('---');
  74. }
  75. /**
  76. * Echo text followed by a new line character
  77. *
  78. * @param string $text
  79. */
  80. static public function printLine($text = null)
  81. {
  82. $args = func_get_args();
  83. call_user_func_array(array(get_called_class(), '_print'), $args);
  84. echo PHP_EOL;
  85. }
  86. /**
  87. * Output some text and then read from the console till a LINE BREAK
  88. * character is entered.
  89. *
  90. * @param string $text
  91. * @return string
  92. */
  93. static public function readLine($text)
  94. {
  95. $args = func_get_args();
  96. call_user_func_array(array(get_called_class(), '_print'), $args);
  97. return trim(fgets(STDIN));
  98. }
  99. /**
  100. * Print some text using printf
  101. *
  102. * @param string $text
  103. */
  104. static protected function _print($text = null)
  105. {
  106. if (empty($text)) {
  107. return;
  108. }
  109. $args = func_get_args();
  110. $text = array_shift($args) . ' ';
  111. vprintf($text, $args);
  112. }
  113. static public function runCli()
  114. {
  115. static::registerAutoloader();
  116. static::printHeader();
  117. try {
  118. $definition = static::_getCliOptionsDefinition();
  119. $definition->parse();
  120. } catch (\Zend_Console_Getopt_Exception $e) {
  121. echo 'Some of the arguments are missing or wrong.' . PHP_EOL . PHP_EOL;
  122. echo $e->getUsageMessage();
  123. exit(0);
  124. }
  125. $configPath = $definition->getOption('c');
  126. $revisions = $definition->getOption('r');
  127. try {
  128. $svn2ftp = new static($configPath, $revisions);
  129. $svn2ftp->run();
  130. } catch (SVN2FTP\Exception $e) {
  131. static::printLine();
  132. static::printLine('---');
  133. static::printLine('ERROR: ' . $e->getMessage());
  134. static::printLine('---');
  135. echo static::getUsageMessage();
  136. static::printLine('---');
  137. exit(0);
  138. }
  139. }
  140. /**
  141. * __constructor
  142. *
  143. * @param array|string $config Either an array or a string containing the path to a config .INI file
  144. * @param string|null $revision List or range of revisions. Default is HEAD.
  145. */
  146. public function __construct($config, $revision)
  147. {
  148. if (is_string($config) && !empty($config)) {
  149. if (!is_readable($config)) {
  150. throw new SVN2FTP\Exception('Config file "' . $config . '" is not readable.');
  151. }
  152. $config = @parse_ini_file($config, true);
  153. if (!is_array($config)) {
  154. throw new SVN2FTP\Exception('Config file "' . $config . '" seems to be a corrupted .INI file.');
  155. }
  156. } elseif (!is_array($config)) {
  157. throw new SVN2FTP\Exception('Argument "--config" is invalid.');
  158. }
  159. $this->_config = SVN2FTP\Config::merge($this->_config, $config);
  160. if (empty($revision)) {
  161. $revision = 'HEAD';
  162. } elseif (!preg_match('/^(\d+:(\d+|HEAD)|(\d+|HEAD))$/i', trim($revision))) {
  163. throw new SVN2FTP\Exception('Argument "--revision" is invalid.');
  164. }
  165. $this->_revision = (string)$revision;
  166. }
  167. public function run()
  168. {
  169. $svn = $this->initSVNConnection();
  170. static::printLine('Loaded project "%s".', $this->_config['project']['name']);
  171. static::printLine('Building change set. This can take a few seconds.');
  172. $changeSet = $svn->createChangeSet($this->_revision);
  173. // Show added paths
  174. $addedPaths = $changeSet->getAddedPaths();
  175. static::printLine();
  176. static::printLine('Added path(s): %d', count($addedPaths));
  177. foreach ($addedPaths as $path) {
  178. static::printLine(" $path (r{$path->getRevision()})");
  179. }
  180. // Show modified paths
  181. $modifiedPaths = $changeSet->getModifiedPaths();
  182. static::printLine();
  183. static::printLine('Modified path(s): %d', count($modifiedPaths));
  184. foreach ($modifiedPaths as $path) {
  185. static::printLine(" $path (r{$path->getRevision()})");
  186. }
  187. // Show deleted paths
  188. $deletedPaths = $changeSet->getDeletedPaths();
  189. static::printLine();
  190. static::printLine('Deleted path(s): %d', count($deletedPaths));
  191. foreach ($deletedPaths as $path) {
  192. static::printLine(" $path (r{$path->getRevision()})");
  193. }
  194. $pathsToUpload = array_merge($addedPaths, $modifiedPaths);
  195. usort($pathsToUpload, function($a, $b) {
  196. return strcasecmp($a->getPath(), $b->getPath());
  197. });
  198. // Ask the user if he wants to continue
  199. if (!$this->_config['project']['force']) {
  200. static::printLine();
  201. while (true) {
  202. switch(static::readLine('Continue with uploading/deleting the above path(s)? (y/n)'))
  203. {
  204. case 'y':
  205. case 'yes':
  206. break 2;
  207. case 'n':
  208. case 'no':
  209. throw new SVN2FTP\Exception('User aborted.');
  210. }
  211. }
  212. }
  213. // Fetch all added/modified files
  214. $tmpDirPath = $this->createTempDirectory();
  215. static::printLine();
  216. static::printLine('Exporting added/modified path(s) to a temp directory.');
  217. foreach ($pathsToUpload as $path) {
  218. static::printLine(' ' . $path . ' ...');
  219. $svn->export($path, $tmpDirPath);
  220. }
  221. // Etablish FTP connection
  222. static::printLine();
  223. static::printLine('Etablishing FTP connection.');
  224. // User must provide FTP password?
  225. if (empty($this->_config['ftp']['password'])) {
  226. $pwd = static::readLine(' Please enter the password for user "' . $this->_config['ftp']['user'] . '":');
  227. $this->_config['ftp']['password'] = $pwd;
  228. }
  229. $ftp = new FTP\Connection($this->_config['ftp']);
  230. // Upload/create fetches paths
  231. static::printLine();
  232. static::printLine('Uploading added/modified path(s).');
  233. foreach ($pathsToUpload as $path) {
  234. static::printLine(' ' . $path . ' ...');
  235. $localPath = $tmpDirPath . $path;
  236. $remotePath = $ftp->getDefaultPath() . $path;
  237. if (is_dir($localPath)) {
  238. $ftp->createDirectory($remotePath);
  239. } else {
  240. $remoteDirectory = dirname($remotePath);
  241. $ftp->createDirectory($remoteDirectory);
  242. $ftp->uploadFile($localPath, $remotePath);
  243. }
  244. }
  245. // Delete paths
  246. static::printLine();
  247. static::printLine('Deleting path(s).');
  248. foreach ($deletedPaths as $path) {
  249. static::printLine(' ' . $path . ' ...');
  250. $localPath = $tmpDirPath . $path;
  251. $remotePath = $ftp->getDefaultPath() . $path;
  252. try {
  253. if (is_dir($localPath)) {
  254. $ftp->deleteDirectory($remotePath);
  255. } else {
  256. $ftp->deleteFile($remotePath);
  257. }
  258. } catch (FTP\Exception $e) {
  259. // Ignore...
  260. continue;
  261. }
  262. }
  263. static::printLine();
  264. static::printLine('---');
  265. static::printLine('Done. :]');
  266. static::printLine('---');
  267. }
  268. /**
  269. * Create a temp directory and return the path
  270. *
  271. * @return string
  272. */
  273. public function createTempDirectory()
  274. {
  275. $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR
  276. . '.svn2ftp' . DIRECTORY_SEPARATOR
  277. . uniqid($this->_config['project']['name'] . '-');
  278. if (!is_dir($path)) {
  279. mkdir($path, null, true);
  280. }
  281. return realpath($path);
  282. }
  283. public function initSVNConnection()
  284. {
  285. return new SVN\Connection($this->_config['svn']);
  286. }
  287. }