├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── scripts └── ci-run.sh ├── src └── AFM │ └── Rsync │ ├── AbstractProtocol.php │ ├── Command.php │ ├── Rsync.php │ └── SSH.php └── tests ├── AFM └── Rsync │ └── Tests │ ├── CommandTest.php │ ├── RsyncTest.php │ ├── SSHTest.php │ └── dir1 │ ├── file1 │ └── file2 └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | /phpunit.xml 2 | /vendor 3 | /build 4 | .idea 5 | composer.lock 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | before_script: 4 | - curl -s http://getcomposer.org/installer | php 5 | - php composer.phar install --dev 6 | 7 | script: 8 | - scripts/ci-run.sh 9 | 10 | php: 11 | - 5.4 12 | - 5.5 13 | - 5.6 14 | - 7.0 15 | - 7.1 16 | - 7.2 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Alberto Fernández 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rsync-lib 2 | ========= 3 | 4 | A simple PHP rsync wrapper library 5 | 6 | [![Build Status](https://secure.travis-ci.org/albertofem/rsync-lib.png?branch=master)](http://travis-ci.org/albertofem/rsync-lib) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/albertofem/rsync-lib/badges/quality-score.png?s=e6f2859cbe311a4bd952cdedd78ab0817e4e4c3d)](https://scrutinizer-ci.com/g/albertofem/rsync-lib/) 7 | 8 | Requirements 9 | ---- 10 | 11 | This library requires PHP >=5.4 12 | 13 | Changelog 14 | ---- 15 | 16 | 01-13-2018 17 | 18 | * Dropped PHP 5.3 support 19 | * Rename `public_key` option to the correct `private_key` one. Old one still works and will be deprecated in version 2.0 20 | 21 | Installation 22 | -------- 23 | 24 | Require it in composer: 25 | 26 | composer require albertofem/rsync-lib 1.0.0 27 | 28 | Install it: 29 | 30 | composer update albertofem/rsync-lib 31 | 32 | If you want to run the tests: 33 | 34 | ./vendor/bin/phpunit 35 | 36 | Usage 37 | --------- 38 | 39 | Basic usage example: 40 | 41 | ```php 42 | sync($origin, $target); 51 | ``` 52 | 53 | Change behaviour: 54 | 55 | ```php 56 | true, 65 | 'ssh' => array( 66 | 'host' => 'myhost.com', 67 | 'private_key' => '/my/key' 68 | ) 69 | ); 70 | 71 | $rsync = new Rsync($config); 72 | 73 | // change options programatically 74 | $rsync->setFollowSymlinks(false); 75 | 76 | $rsync->sync($origin, $target); 77 | ``` 78 | 79 | 80 | Options 81 | --------- 82 | 83 | | Construct options | Rsync argument | Comment | 84 | | ------------------ | ------------------------- | ------------------------------------------------ | 85 | | executable | | path of rsync (default: `/usr/bin/rsync`) | 86 | | archive | -a, --archive | archive mode; equals -rlptgoD (no -H,-A,-X) | 87 | | update | -u, --update | skip files that are newer on the receiver | 88 | | follow_symlinks | -L, --copy-links | transform symlink into referent file/dir | 89 | | dry_run | -n, --dry-run | perform a trial run with no changes made | 90 | | option_parameters | | add any optional options we've specified | 91 | | verbose | -v, --verbose | increase verbosity | 92 | | delete_from_target | --delete | delete extraneous files from destination dirs | 93 | | delete_excluded | --delete-excluded | also delete excluded files from destination dirs | 94 | | exclude | --exclude=PATTERN | exclude files matching PATTERN | 95 | | excludeFrom | --exclude-from=FILE | read exclude patterns from FILE | 96 | | recursive | -r, --recursive | recurse into directories | 97 | | times | -t, --times | preserve modification times | 98 | | show_output | | execute and buffers command result to print it | 99 | | ssh | | set ssh options | 100 | | compression | -z, --compress | compress file data during the transfer | 101 | | remote_origin | | use ssh for origin path | 102 | | remove_source | --remove-source-files | sender removes synchronized files (non-dirs) | 103 | | info | --info=FLAGS | fine-grained informational verbosity | 104 | | compare_dest | --compare-dest=DIR | also compare destination files relative to DIR | 105 | | prune_empty_dirs | -m, --prune-empty-dirs | prune empty directory chains from the file-list | 106 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "albertofem/rsync-lib", 3 | "description": "A simple PHP rsync wrapper library", 4 | "keywords": [ 5 | "rsync library" 6 | ], 7 | "homepage": "http://albertofem.com", 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Alberto Fernandez", 12 | "email": "albertofem@gmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.4" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": ">=4.0,<6" 20 | }, 21 | "autoload": { 22 | "psr-0": { 23 | "AFM": "src/" 24 | } 25 | }, 26 | "extra": { 27 | "branch-alias": { 28 | "dev-master": "1.0.x-dev" 29 | } 30 | }, 31 | "config": { 32 | "platform": { 33 | "php": "5.4" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/AFM/Rsync/ 17 | 18 | 19 | 20 | 21 | ./src 22 | 23 | 24 | -------------------------------------------------------------------------------- /scripts/ci-run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir ~/sshd/ 4 | 5 | cat > ~/sshd/sshd_config <<-EOF 6 | Port 2222 7 | ListenAddress 0.0.0.0 8 | Protocol 2 9 | HostKey ${HOME}/sshd/id_rsa_rsync_test 10 | PidFile ${HOME}/sshd/pid 11 | PasswordAuthentication yes 12 | PubkeyAuthentication yes 13 | ChallengeResponseAuthentication no 14 | UsePAM no 15 | EOF 16 | 17 | ssh-keygen -t rsa -f ~/sshd/id_rsa_rsync_test -N "" -q 18 | /usr/sbin/sshd -f ~/sshd/sshd_config 19 | 20 | ssh-keygen -t rsa -f ~/.ssh/id_rsa_rsync_test -N "" -q 21 | cat ~/.ssh/id_rsa_rsync_test.pub >> ~/.ssh/authorized_keys 22 | 23 | while read algorithm key comment; do 24 | echo "[localhost]:2222 $algorithm $key" >> ~/.ssh/known_hosts 25 | 26 | done < ~/sshd/id_rsa_rsync_test.pub 27 | 28 | ./vendor/bin/phpunit -------------------------------------------------------------------------------- /src/AFM/Rsync/AbstractProtocol.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync; 13 | 14 | /** 15 | * Abstract protocol 16 | * 17 | * @author Alberto 18 | */ 19 | abstract class AbstractProtocol 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $executable = ""; 25 | 26 | /** 27 | * Shortcut to set options from array config 28 | * 29 | * @param array $options 30 | * @param $name 31 | * @param $method 32 | */ 33 | protected function setOption(Array $options, $name, $method) 34 | { 35 | if (isset($options[$name])) { 36 | $this->$method($options[$name]); 37 | } 38 | } 39 | 40 | /** 41 | * Sets rsync executable location, i.e.: /usr/bin/rsync 42 | * 43 | * @param $rsyncLocation 44 | * 45 | * @throws \InvalidArgumentException If the rsync location is not executable 46 | */ 47 | public function setExecutable($rsyncLocation) 48 | { 49 | if (!is_executable($rsyncLocation)) { 50 | throw new \InvalidArgumentException("Rsync location '".$rsyncLocation."' is invalid"); 51 | } 52 | 53 | $this->executable = $rsyncLocation; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/AFM/Rsync/Command.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync; 13 | 14 | /** 15 | * Command abstraction class, construct commands 16 | * using arguments options and parameters 17 | * 18 | * Command format: 19 | *
 20 |  *        [executable] [-abLs](options) [-a value](argument) [--test value](argument) [parameter1] ... [parameterN]
 21 |  * 
22 | * 23 | * @author Alberto 24 | */ 25 | class Command 26 | { 27 | /** 28 | * @var string 29 | */ 30 | private $executable; 31 | 32 | /** 33 | * @var array 34 | */ 35 | private $options = array(); 36 | 37 | /** 38 | * @var array 39 | */ 40 | private $arguments = array(); 41 | 42 | /** 43 | * @var string 44 | */ 45 | private $command; 46 | 47 | /** 48 | * @var array 49 | */ 50 | private $parameters = array(); 51 | 52 | /** 53 | * Every command must have an executable 54 | * 55 | * @param $executable 56 | */ 57 | public function __construct($executable) 58 | { 59 | $this->executable = $executable; 60 | } 61 | 62 | /** 63 | * Adds a parameter to the command, will be appended 64 | * in the same order as insertion at the end 65 | * 66 | * @param $parameter 67 | */ 68 | public function addParameter($parameter) 69 | { 70 | $this->parameters[] = $parameter; 71 | } 72 | 73 | /** 74 | * Adds an option to the command, will be 75 | * appended to the command in the next format: 76 | * 77 | *
 78 |      *        -aBLs
 79 |      * 
80 | * 81 | * @param $option 82 | */ 83 | public function addOption($option) 84 | { 85 | $this->options[] = $option; 86 | } 87 | 88 | /** 89 | * Adds an argument to the command. If the argument 90 | * is more than one letter, "-- " will be appended before 91 | * if not, it will act as an option with a value: 92 | * 93 | *
 94 |      *        --argument [value]
 95 |      *        -p [value]
 96 |      * 
97 | * 98 | * @param $name 99 | * @param bool|mixed $value 100 | */ 101 | public function addArgument($name, $value = true) 102 | { 103 | $this->arguments[$name][] = $value; 104 | } 105 | 106 | /** 107 | * @param $executable 108 | */ 109 | public function setExecutable($executable) 110 | { 111 | $this->executable = $executable; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getExecutable() 118 | { 119 | return $this->executable; 120 | } 121 | 122 | /** 123 | * Constructs the command appendind options, 124 | * arguments, executable and parameters 125 | * 126 | * @return string 127 | */ 128 | protected function constructCommand() 129 | { 130 | $command = array(); 131 | $command[] = $this->executable; 132 | 133 | if (!empty($this->options)) { 134 | $command[] = "-".implode($this->options); 135 | } 136 | 137 | foreach ($this->arguments as $argument => $values) { 138 | foreach ($values as $value) { 139 | if (strlen($argument) == 1) { 140 | $command[] = "-".$argument." '".$value."'"; 141 | } else { 142 | $command[] = "--".(is_string($value) || is_int($value) ? $argument." '".$value."'" : $argument); 143 | } 144 | } 145 | } 146 | 147 | if (!empty($this->parameters)) { 148 | $command[] = implode(" ", $this->parameters); 149 | } 150 | 151 | $stringCommand = implode(" ", $command); 152 | 153 | return $stringCommand; 154 | } 155 | 156 | /** 157 | * Gets the command string 158 | * 159 | * @return mixed 160 | */ 161 | public function getCommand() 162 | { 163 | if (is_null($this->command)) { 164 | $this->command = $this->constructCommand(); 165 | } 166 | 167 | return $this->command; 168 | } 169 | 170 | /** 171 | * @see getCommand 172 | * @return mixed 173 | */ 174 | public function __toString() 175 | { 176 | return $this->getCommand(); 177 | } 178 | 179 | /** 180 | * Execute command, with optional output printer 181 | * 182 | * @param bool $showOutput 183 | */ 184 | public function execute($showOutput = false) 185 | { 186 | $this->getCommand(); 187 | 188 | if ($showOutput) { 189 | $this->executeWithOutput(); 190 | } else { 191 | shell_exec($this->command); 192 | } 193 | } 194 | 195 | /** 196 | * Execute and buffers command result to print it 197 | * 198 | * @throws \InvalidArgumentException When the command couldn't be executed 199 | */ 200 | private function executeWithOutput() 201 | { 202 | if (($fp = popen($this->command, "r"))) { 203 | while (!feof($fp)) { 204 | echo fread($fp, 1024); 205 | flush(); 206 | } 207 | 208 | fclose($fp); 209 | } else { 210 | throw new \InvalidArgumentException("Cannot execute command: '".$this->command."'"); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/AFM/Rsync/Rsync.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync; 13 | 14 | /** 15 | * Rsync wrapper. Many options are not implemented, 16 | * but you can use setOptionalParameters 17 | * 18 | * @author Alberto Fernández 19 | */ 20 | class Rsync extends AbstractProtocol 21 | { 22 | /** 23 | * @var string 24 | */ 25 | protected $executable = "/usr/bin/rsync"; 26 | 27 | /** 28 | * @var bool 29 | */ 30 | protected $archive = true; 31 | 32 | /** 33 | * @var bool 34 | */ 35 | protected $skipNewerFiles = false; 36 | 37 | /** 38 | * @var bool 39 | */ 40 | protected $followSymLinks = true; 41 | 42 | /** 43 | * @var bool 44 | */ 45 | protected $dryRun = false; 46 | 47 | /** 48 | * @var array 49 | */ 50 | protected $optionalParameters = array(); 51 | 52 | /** 53 | * @var bool 54 | */ 55 | protected $verbose = false; 56 | 57 | /** 58 | * @var bool 59 | */ 60 | protected $deleteFromTarget = false; 61 | 62 | /** 63 | * @var bool 64 | */ 65 | protected $deleteExcluded = false; 66 | 67 | /** 68 | * @var array 69 | */ 70 | protected $exclude = array(); 71 | 72 | /** 73 | * @var string 74 | */ 75 | protected $excludeFrom = null; 76 | 77 | /** 78 | * @var bool 79 | */ 80 | protected $recursive = true; 81 | 82 | /** 83 | * @var bool 84 | */ 85 | protected $times = false; 86 | 87 | /** 88 | * @var bool 89 | */ 90 | protected $showOutput = true; 91 | 92 | /** 93 | * @var bool 94 | */ 95 | protected $compression = false; 96 | 97 | /** 98 | * @var bool 99 | */ 100 | protected $remoteOrigin = false; 101 | 102 | /** 103 | * @var bool 104 | */ 105 | protected $removeSource = false; 106 | 107 | /** 108 | * @var bool 109 | */ 110 | protected $info = false; 111 | 112 | /** 113 | * @var bool 114 | */ 115 | protected $compareDest = false; 116 | 117 | /** 118 | * @var bool 119 | */ 120 | protected $pruneEmptyDirs = false; 121 | 122 | /** 123 | * @var SSH 124 | */ 125 | protected $ssh; 126 | 127 | /** 128 | * Injects and validates config 129 | * 130 | * @param array $options 131 | */ 132 | public function __construct(Array $options = array()) 133 | { 134 | $this->setOption($options, 'executable', 'setExecutable'); 135 | $this->setOption($options, 'archive', 'setArchive'); 136 | $this->setOption($options, 'update', 'setSkipNewerFiles'); 137 | $this->setOption($options, 'follow_symlinks', 'setFollowSymLinks'); 138 | $this->setOption($options, 'dry_run', 'setDryRun'); 139 | $this->setOption($options, 'option_parameters', 'setOptionalParameters'); 140 | $this->setOption($options, 'verbose', 'setVerbose'); 141 | $this->setOption($options, 'delete_from_target', 'setDeleteFromTarget'); 142 | $this->setOption($options, 'delete_excluded', 'setDeleteExcluded'); 143 | $this->setOption($options, 'exclude', 'setExclude'); 144 | $this->setOption($options, 'excludeFrom', 'setExcludeFrom'); 145 | $this->setOption($options, 'recursive', 'setRecursive'); 146 | $this->setOption($options, 'times', 'setTimes'); 147 | $this->setOption($options, 'show_output', 'setShowOutput'); 148 | $this->setOption($options, 'ssh', 'setSshOptions'); 149 | $this->setOption($options, 'compression', 'setCompression'); 150 | $this->setOption($options, 'remote_origin', 'setRemoteOrigin'); 151 | $this->setOption($options, 'remove_source', 'setRemoveSource'); 152 | $this->setOption($options, 'info', 'setInfo'); 153 | $this->setOption($options, 'compare_dest', 'setCompareDest'); 154 | $this->setOption($options, 'prune_empty_dirs', 'setPruneEmptyDirs'); 155 | } 156 | 157 | /** 158 | * @param $options 159 | */ 160 | public function setSshOptions($options) 161 | { 162 | $this->ssh = new SSH($options); 163 | } 164 | 165 | /** 166 | * Sync $origin directory with $target one. 167 | * If SSH was configured, you must use absolute path 168 | * in the target directory 169 | * 170 | * @param $origin 171 | * @param $target 172 | * 173 | * @throws \InvalidArgumentException If the command failed 174 | */ 175 | public function sync($origin, $target) 176 | { 177 | $command = $this->getCommand($origin, $target); 178 | 179 | $command->execute($this->showOutput); 180 | } 181 | 182 | /** 183 | * @return string 184 | */ 185 | public function getExecutable() 186 | { 187 | return $this->executable; 188 | } 189 | 190 | /** 191 | * @return bool 192 | */ 193 | public function getArchive() 194 | { 195 | return $this->archive; 196 | } 197 | 198 | /** 199 | * @param $archive 200 | */ 201 | public function setArchive($archive) 202 | { 203 | $this->archive = $archive; 204 | } 205 | 206 | /** 207 | * @return bool 208 | */ 209 | public function getPruneEmptyDirs() 210 | { 211 | return $this->pruneEmptyDirs; 212 | } 213 | 214 | /** 215 | * @param $pruneEmptyDirs 216 | */ 217 | public function setPruneEmptyDirs($pruneEmptyDirs) 218 | { 219 | $this->pruneEmptyDirs = $pruneEmptyDirs; 220 | } 221 | 222 | 223 | /** 224 | * @param $skipNewerFiles 225 | */ 226 | public function setSkipNewerFiles($skipNewerFiles) 227 | { 228 | $this->skipNewerFiles = $skipNewerFiles; 229 | } 230 | 231 | /** 232 | * @return bool 233 | */ 234 | public function getSkipNewerFiles() 235 | { 236 | return $this->skipNewerFiles; 237 | } 238 | 239 | /** 240 | * @param $followSymLinks 241 | */ 242 | public function setFollowSymLinks($followSymLinks) 243 | { 244 | $this->followSymLinks = $followSymLinks; 245 | } 246 | 247 | /** 248 | * @return bool 249 | */ 250 | public function getFollowSymLinks() 251 | { 252 | return $this->followSymLinks; 253 | } 254 | 255 | /** 256 | * @param $dryRun 257 | */ 258 | public function setDryRun($dryRun) 259 | { 260 | $this->dryRun = $dryRun; 261 | } 262 | 263 | /** 264 | * @return bool 265 | */ 266 | public function getDryRun() 267 | { 268 | return $this->dryRun; 269 | } 270 | 271 | /** 272 | * @param $optionalParameters 273 | */ 274 | public function setOptionalParameters($optionalParameters) 275 | { 276 | $this->optionalParameters = $optionalParameters; 277 | } 278 | 279 | /** 280 | * @return array 281 | */ 282 | public function getOptionalParameters() 283 | { 284 | return $this->optionalParameters; 285 | } 286 | 287 | /** 288 | * @param $verbose 289 | */ 290 | public function setVerbose($verbose) 291 | { 292 | $this->verbose = $verbose; 293 | } 294 | 295 | /** 296 | * @return bool 297 | */ 298 | public function getVerbose() 299 | { 300 | return $this->verbose; 301 | } 302 | 303 | /** 304 | * @param $deleteExcluded 305 | */ 306 | public function setDeleteExcluded($deleteExcluded) 307 | { 308 | $this->deleteExcluded = $deleteExcluded; 309 | } 310 | 311 | /** 312 | * @return bool 313 | */ 314 | public function getDeleteExcluded() 315 | { 316 | return $this->deleteExcluded; 317 | } 318 | 319 | /** 320 | * @param $deleteFromTarget 321 | */ 322 | public function setDeleteFromTarget($deleteFromTarget) 323 | { 324 | $this->deleteFromTarget = $deleteFromTarget; 325 | } 326 | 327 | /** 328 | * @return bool 329 | */ 330 | public function getDeleteFromTarget() 331 | { 332 | return $this->deleteFromTarget; 333 | } 334 | 335 | /** 336 | * @param $exclude 337 | */ 338 | public function setExclude($exclude) 339 | { 340 | $this->exclude = $exclude; 341 | } 342 | 343 | /** 344 | * @return array 345 | */ 346 | public function getExclude() 347 | { 348 | return $this->exclude; 349 | } 350 | 351 | /** 352 | * @param $exclude 353 | */ 354 | public function setExcludeFrom($excludeFrom) 355 | { 356 | $this->excludeFrom = $excludeFrom; 357 | } 358 | 359 | /** 360 | * @return string 361 | */ 362 | public function getExcludeFrom() 363 | { 364 | return $this->excludeFrom; 365 | } 366 | 367 | /** 368 | * @param $recursive 369 | */ 370 | public function setRecursive($recursive) 371 | { 372 | $this->recursive = $recursive; 373 | } 374 | 375 | /** 376 | * @return bool 377 | */ 378 | public function getRecursive() 379 | { 380 | return $this->recursive; 381 | } 382 | 383 | /** 384 | * @param bool $times 385 | */ 386 | public function setTimes($times) 387 | { 388 | $this->times = $times; 389 | } 390 | 391 | /** 392 | * @return bool 393 | */ 394 | public function getTimes() 395 | { 396 | return $this->times; 397 | } 398 | 399 | /** 400 | * @param $showOutput 401 | */ 402 | public function setShowOutput($showOutput) 403 | { 404 | $this->showOutput = $showOutput; 405 | } 406 | 407 | /** 408 | * @return bool 409 | */ 410 | public function getShowOutput() 411 | { 412 | return $this->showOutput; 413 | } 414 | 415 | /** 416 | * @param $compression 417 | */ 418 | public function setCompression($compression) 419 | { 420 | $this->compression = $compression; 421 | } 422 | 423 | /** 424 | * @return bool 425 | */ 426 | public function getCompression() 427 | { 428 | return $this->compression; 429 | } 430 | 431 | /** 432 | * @param $remoteOrigin 433 | */ 434 | public function setRemoteOrigin($remoteOrigin) 435 | { 436 | $this->remoteOrigin = (bool)$remoteOrigin; 437 | } 438 | 439 | /** 440 | * @return bool 441 | */ 442 | public function getRemoteOrigin() 443 | { 444 | return $this->remoteOrigin; 445 | } 446 | 447 | /** 448 | * @param $removeSource 449 | */ 450 | public function setRemoveSource($removeSource) 451 | { 452 | $this->removeSource = (bool)$removeSource; 453 | } 454 | 455 | /** 456 | * @return bool 457 | */ 458 | public function getRemoveSource() 459 | { 460 | return $this->removeSource; 461 | } 462 | 463 | /** 464 | * @param $info 465 | */ 466 | public function setInfo($info) 467 | { 468 | $this->info = $info; 469 | } 470 | 471 | /** 472 | * @return bool 473 | */ 474 | public function getInfo() 475 | { 476 | return $this->info; 477 | } 478 | 479 | /** 480 | * @param $dest 481 | */ 482 | public function setCompareDest($dest) 483 | { 484 | $this->compareDest = $dest; 485 | } 486 | 487 | /** 488 | * @return string 489 | */ 490 | public function getCompareDest() 491 | { 492 | return $this->compareDest; 493 | } 494 | 495 | /** 496 | * Gets command generated for this current 497 | * rsync configuration. You can use it to test 498 | * or execute it later without using the sync method 499 | * 500 | * @param $origin 501 | * @param $target 502 | * 503 | * @return Command 504 | */ 505 | public function getCommand($origin, $target) 506 | { 507 | $command = new Command($this->executable); 508 | 509 | if ($this->skipNewerFiles) { 510 | $command->addOption("u"); 511 | } 512 | 513 | if ($this->followSymLinks) { 514 | $command->addOption("L"); 515 | } 516 | 517 | if ($this->dryRun) { 518 | $command->addOption("n"); 519 | } 520 | 521 | if ($this->verbose) { 522 | $command->addOption("v"); 523 | } 524 | 525 | if ($this->compression) { 526 | $command->addOption("z"); 527 | } 528 | 529 | // add any optional options we've specified 530 | $extra_options = $this->getOptionalParameters(); 531 | if (!empty($extra_options)) { 532 | // if the extra options were given as a flat string, then convert it to an array 533 | if (is_string($extra_options)) { 534 | $extra_options = str_split($extra_options); 535 | } 536 | 537 | // add each extra option we've defined. 538 | if (is_array($extra_options)) { 539 | foreach ($extra_options as $option) { 540 | $command->addOption($option); 541 | } 542 | } 543 | } 544 | 545 | if ($this->times) { 546 | $command->addArgument('times'); 547 | } 548 | 549 | if ($this->deleteFromTarget) { 550 | $command->addArgument('delete'); 551 | } 552 | 553 | if ($this->removeSource) { 554 | $command->addArgument('remove-source-files'); 555 | } 556 | 557 | if ($this->deleteExcluded) { 558 | $command->addArgument('delete-excluded'); 559 | } 560 | 561 | if ($this->info) { 562 | $command->addArgument('info', $this->info); 563 | } 564 | 565 | if ($this->compareDest) { 566 | $command->addArgument('compare-dest', $this->compareDest); 567 | } 568 | 569 | if (!empty($this->exclude)) { 570 | foreach ($this->exclude as $excluded) { 571 | $command->addArgument('exclude', $excluded); 572 | } 573 | } 574 | 575 | if (!empty($this->excludeFrom)) { 576 | $command->addArgument('exclude-from', $this->excludeFrom); 577 | } 578 | 579 | if ($this->archive) { 580 | $command->addOption("a"); 581 | } 582 | 583 | if (!$this->archive && $this->recursive) { 584 | $command->addOption("r"); 585 | } 586 | 587 | if ($this->pruneEmptyDirs) { 588 | $command->addArgument('prune-empty-dirs'); 589 | } 590 | 591 | if (!is_null($this->ssh)) { 592 | $ssh = $this->ssh->getConnectionOptions(); 593 | $command->addArgument("rsh", $ssh); 594 | } 595 | 596 | if (is_null($this->ssh)) { 597 | $command->addParameter($origin); 598 | $command->addParameter($target); 599 | } elseif ($this->remoteOrigin) { 600 | $command->addParameter($this->ssh->getHostConnection().":".$origin); 601 | $command->addParameter($target); 602 | } else { 603 | $command->addParameter($origin); 604 | $command->addParameter($this->ssh->getHostConnection().":".$target); 605 | } 606 | 607 | return $command; 608 | } 609 | } 610 | -------------------------------------------------------------------------------- /src/AFM/Rsync/SSH.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync; 13 | 14 | /** 15 | * Abstract SSH connection command. Note that if you 16 | * don't specify a public key, you will be prompted for 17 | * the remote server password 18 | * 19 | * @author Alberto 20 | */ 21 | class SSH extends AbstractProtocol 22 | { 23 | /** 24 | * @var string 25 | */ 26 | protected $executable = "ssh"; 27 | 28 | /** 29 | * @var string 30 | */ 31 | protected $host; 32 | 33 | /** 34 | * @var int 35 | */ 36 | protected $port = 22; 37 | 38 | /** 39 | * @var string 40 | */ 41 | protected $username; 42 | 43 | /** 44 | * @var null 45 | */ 46 | protected $privateKey = null; 47 | 48 | /** 49 | * Injects and validates config 50 | * 51 | * @param array $options 52 | */ 53 | public function __construct(Array $options = array()) 54 | { 55 | $this->setOption($options, 'executable', 'setExecutable'); 56 | $this->setOption($options, 'host', 'setHost'); 57 | $this->setOption($options, 'port', 'setPort'); 58 | $this->setOption($options, 'username', 'setUsername'); 59 | $this->setOption($options, 'private_key', 'setPrivateKey'); 60 | $this->setOption($options, 'public_key', 'setPrivateKey'); 61 | } 62 | 63 | /** 64 | * @param $host 65 | */ 66 | public function setHost($host) 67 | { 68 | $this->host = $host; 69 | } 70 | 71 | /** 72 | * @return mixed 73 | */ 74 | public function getHost() 75 | { 76 | return $this->host; 77 | } 78 | 79 | /** 80 | * @param $port 81 | * 82 | * @throws \InvalidArgumentException If the port is not numeric 83 | */ 84 | public function setPort($port) 85 | { 86 | if (!is_int($port)) { 87 | throw new \InvalidArgumentException("SSH port must be an integer"); 88 | } 89 | 90 | $this->port = $port; 91 | } 92 | 93 | /** 94 | * @return int 95 | */ 96 | public function getPort() 97 | { 98 | return $this->port; 99 | } 100 | 101 | /** 102 | * @param $privateKey 103 | * @throws \InvalidArgumentException 104 | */ 105 | public function setPrivateKey($privateKey) 106 | { 107 | if (!is_readable($privateKey)) { 108 | throw new \InvalidArgumentException("SSH private key '".$privateKey."' is not readable"); 109 | } 110 | 111 | $this->privateKey = $privateKey; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getPrivateKey() 118 | { 119 | return $this->privateKey; 120 | } 121 | 122 | /** 123 | * @param $username 124 | */ 125 | public function setUsername($username) 126 | { 127 | $this->username = $username; 128 | } 129 | 130 | /** 131 | * @return string 132 | */ 133 | public function getUsername() 134 | { 135 | return $this->username; 136 | } 137 | 138 | /** 139 | * Gets commands for this SSH connection 140 | * 141 | * @param bool $hostConnection 142 | * 143 | * @return string 144 | * 145 | * @throws \InvalidArgumentException If you don't specify a SSH username or host 146 | */ 147 | public function getCommand($hostConnection = true) 148 | { 149 | if (is_null($this->username)) { 150 | throw new \InvalidArgumentException("You must specify a SSH username"); 151 | } 152 | 153 | if (is_null($this->host)) { 154 | throw new \InvalidArgumentException("You must specify a SSH host to connect"); 155 | } 156 | 157 | $command = new Command($this->executable); 158 | 159 | if ($this->port != 22) { 160 | $command->addArgument("p", $this->port); 161 | } 162 | 163 | if (!is_null($this->privateKey)) { 164 | $command->addArgument("i", $this->privateKey); 165 | } 166 | 167 | if ($hostConnection) { 168 | $command->addParameter($this->getHostConnection()); 169 | } 170 | 171 | return $command; 172 | } 173 | 174 | /** 175 | * Gets only connection options, without user@host string 176 | * 177 | * @return string 178 | */ 179 | public function getConnectionOptions() 180 | { 181 | return (string)$this->getCommand(false); 182 | } 183 | 184 | /** 185 | * Gets only host connection, without the rest 186 | * of options 187 | * 188 | * @return string 189 | */ 190 | public function getHostConnection() 191 | { 192 | return $this->username."@".$this->host; 193 | } 194 | 195 | /** 196 | * @param $executable 197 | */ 198 | public function setExecutable($executable) 199 | { 200 | $this->executable = $executable; 201 | } 202 | 203 | /** 204 | * @return string 205 | */ 206 | public function getExecutable() 207 | { 208 | return $this->executable; 209 | } 210 | 211 | /** 212 | * @deprecated 213 | */ 214 | public function setPublicKey($privateKey) 215 | { 216 | $this->setPrivateKey($privateKey); 217 | } 218 | 219 | /** 220 | * @deprecated 221 | */ 222 | public function getPublicKey() 223 | { 224 | return $this->getPrivateKey(); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /tests/AFM/Rsync/Tests/CommandTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync\Tests; 13 | 14 | use AFM\Rsync\Command; 15 | 16 | class CommandTest extends \PHPUnit_Framework_TestCase 17 | { 18 | public function testCommandWithOnlyOptions() 19 | { 20 | $command = new Command("test"); 21 | 22 | $command->addOption("a"); 23 | 24 | $actual = $command->getCommand(); 25 | $expected = "test -a"; 26 | 27 | $this->assertEquals($expected, $actual); 28 | } 29 | 30 | public function testCommandWithMultipleOptions() 31 | { 32 | $command = new Command("test"); 33 | 34 | $command->addOption("a"); 35 | $command->addOption("b"); 36 | $command->addOption("z"); 37 | 38 | $actual = $command->getCommand(); 39 | $expected = "test -abz"; 40 | 41 | $this->assertEquals($expected, $actual); 42 | } 43 | 44 | public function testCommandWithOnlyOneParameter() 45 | { 46 | $command = new Command("test"); 47 | 48 | $command->addParameter("test"); 49 | 50 | $actual = $command->getCommand(); 51 | $expected = "test test"; 52 | 53 | $this->assertEquals($expected, $actual); 54 | } 55 | 56 | public function testCommandWithOneOptionAndOneParameter() 57 | { 58 | $command = new Command("test"); 59 | 60 | $command->addOption("a"); 61 | $command->addParameter("test"); 62 | 63 | $actual = $command->getCommand(); 64 | $expected = "test -a test"; 65 | 66 | $this->assertEquals($expected, $actual); 67 | } 68 | 69 | public function testCommandWithOneOptionAndMultipleParameters() 70 | { 71 | $command = new Command("test"); 72 | 73 | $command->addOption("a"); 74 | $command->addParameter("test"); 75 | $command->addParameter("test2"); 76 | 77 | $actual = $command->getCommand(); 78 | $expected = "test -a test test2"; 79 | 80 | $this->assertEquals($expected, $actual); 81 | } 82 | 83 | public function testCommandWithOnlyOneSimpleArgument() 84 | { 85 | $command = new Command("test"); 86 | 87 | $command->addArgument("test"); 88 | 89 | $actual = $command->getCommand(); 90 | $expected = "test --test"; 91 | 92 | $this->assertEquals($expected, $actual); 93 | } 94 | 95 | public function testCommandWithOnlyOneArgument() 96 | { 97 | $command = new Command("test"); 98 | 99 | $command->addArgument("test", "value"); 100 | 101 | $actual = $command->getCommand(); 102 | $expected = "test --test 'value'"; 103 | 104 | $this->assertEquals($expected, $actual); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/AFM/Rsync/Tests/RsyncTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync\Tests; 13 | 14 | use AFM\Rsync\Rsync; 15 | 16 | class RsyncTest extends \PHPUnit_Framework_TestCase 17 | { 18 | private static $targetDir; 19 | 20 | private static $sourceDir; 21 | 22 | public function setUp() 23 | { 24 | @rrmdir(self::$targetDir); 25 | } 26 | 27 | public static function setUpBeforeClass() 28 | { 29 | self::$sourceDir = __DIR__.'/dir1'; 30 | self::$targetDir = __DIR__.'/dir2'; 31 | 32 | @mkdir(self::$targetDir); 33 | } 34 | 35 | public static function tearDownAfterClass() 36 | { 37 | @rrmdir(self::$targetDir); 38 | } 39 | 40 | public function testValidExecutableLocation() 41 | { 42 | $rsync = new Rsync; 43 | $rsync->setExecutable("/usr/bin/rsync"); 44 | 45 | $this->assertTrue(true); 46 | } 47 | 48 | /** 49 | * @expectedException \InvalidArgumentException 50 | */ 51 | public function testInvalidExecutableLocation() 52 | { 53 | $rsync = new Rsync; 54 | $rsync->setExecutable("/usr/not/exists/rsync!!"); 55 | } 56 | 57 | public function testFollowSymlinkOptions() 58 | { 59 | $rsync = new Rsync(array('follow_symlinks' => true)); 60 | 61 | $this->assertTrue($rsync->getFollowSymLinks()); 62 | } 63 | 64 | public function testBasicSync() 65 | { 66 | $rsync = new Rsync; 67 | 68 | $rsync->sync($this->getSourceDir()."/*", $this->getTargetDir()); 69 | 70 | $this->assertTrue(compare_directories($this->getSourceDir(), $this->getTargetDir())); 71 | } 72 | 73 | public function testRsyncWithSSHConnection() 74 | { 75 | $user = getenv('USER') ?: 'test_no_ssh_server'; 76 | $targetBaseDir = getenv('HOME') ?: '/home'; 77 | 78 | $config = array( 79 | 'ssh' => array( 80 | 'username' => $user, 81 | 'host' => 'localhost', 82 | 'port' => 2222, 83 | ), 84 | ); 85 | 86 | $rsync = new Rsync($config); 87 | 88 | $command = $rsync->getCommand(".", $targetBaseDir."/test/"); 89 | 90 | $actual = $command->getCommand(); 91 | $expected = "/usr/bin/rsync -La --rsh 'ssh -p '2222'' . ".$user."@localhost:".$targetBaseDir."/test/"; 92 | 93 | $this->assertEquals($expected, $actual); 94 | } 95 | 96 | public function testRsyncWithRealSSHConnection() 97 | { 98 | $user = getenv('USER'); 99 | $targetBaseDir = getenv('HOME'); 100 | $sshKey = $targetBaseDir.'/.ssh/id_rsa_rsync_test'; 101 | 102 | if (!file_exists($sshKey)) { 103 | $this->markTestIncomplete('Cannot perform real SSH rsync due to missing SSH configuration'); 104 | } 105 | 106 | $config = array( 107 | 'ssh' => array( 108 | 'username' => $user, 109 | 'host' => 'localhost', 110 | 'port' => 2222, 111 | 'private_key' => $sshKey, 112 | ), 113 | ); 114 | 115 | $rsync = new Rsync($config); 116 | $rsync->sync($this->getSourceDir()."/*", $this->getTargetDir()); 117 | 118 | $this->assertTrue(compare_directories($this->getSourceDir(), $this->getTargetDir())); 119 | } 120 | 121 | public 122 | function testRsyncWithSingleExclude() 123 | { 124 | $rsync = new Rsync(); 125 | $rsync->setExclude(array('exclude1')); 126 | 127 | $expected = "/usr/bin/rsync -La --exclude 'exclude1' /origin /target"; 128 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 129 | 130 | $this->assertEquals($expected, $actual); 131 | } 132 | 133 | public 134 | function testRsyncWithMultipleExcludes() 135 | { 136 | $rsync = new Rsync(); 137 | $rsync->setExclude(array('exclude1', 'exclude2', 'exclude3')); 138 | 139 | $expected = "/usr/bin/rsync -La --exclude 'exclude1' --exclude 'exclude2' --exclude 'exclude3' /origin /target"; 140 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 141 | 142 | $this->assertEquals($expected, $actual); 143 | } 144 | 145 | public 146 | function testRsyncWithExcludeFrom() 147 | { 148 | $rsync = new Rsync(); 149 | $rsync->setExcludeFrom('rsync_exclude.txt'); 150 | 151 | $expected = "/usr/bin/rsync -La --exclude-from 'rsync_exclude.txt' /origin /target"; 152 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 153 | 154 | $this->assertEquals($expected, $actual); 155 | } 156 | 157 | public 158 | function testRsyncWithTimes() 159 | { 160 | $rsync = new Rsync(); 161 | $rsync->setTimes(true); 162 | 163 | $expected = "/usr/bin/rsync -La --times /origin /target"; 164 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 165 | 166 | $this->assertEquals($expected, $actual); 167 | } 168 | 169 | public 170 | function testRsyncWithCompression() 171 | { 172 | $rsync = new Rsync(); 173 | $rsync->setCompression(true); 174 | 175 | $expected = "/usr/bin/rsync -Lza /origin /target"; 176 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 177 | 178 | $this->assertEquals($expected, $actual); 179 | } 180 | 181 | public 182 | function testRsyncWithOptionalParametersArray() 183 | { 184 | $rsync = new Rsync(); 185 | $rsync->setOptionalParameters(array('z', 'p')); 186 | 187 | $expected = "/usr/bin/rsync -Lzpa /origin /target"; 188 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 189 | 190 | $this->assertEquals($expected, $actual); 191 | } 192 | 193 | public 194 | function testRsyncWithOptionalParametersString() 195 | { 196 | $rsync = new Rsync(); 197 | $rsync->setOptionalParameters('zp'); 198 | 199 | $expected = "/usr/bin/rsync -Lzpa /origin /target"; 200 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 201 | 202 | $this->assertEquals($expected, $actual); 203 | } 204 | 205 | public 206 | function testRsyncWithInfo() 207 | { 208 | $rsync = new Rsync(); 209 | $rsync->setInfo('all0'); 210 | 211 | $expected = "/usr/bin/rsync -La --info 'all0' /origin /target"; 212 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 213 | 214 | $this->assertEquals($expected, $actual); 215 | } 216 | 217 | public 218 | function testRsyncWithCompareDest() 219 | { 220 | $rsync = new Rsync(); 221 | $rsync->setCompareDest('/Path/To/File'); 222 | 223 | $expected = "/usr/bin/rsync -La --compare-dest '/Path/To/File' /origin /target"; 224 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 225 | 226 | $this->assertEquals($expected, $actual); 227 | } 228 | 229 | public 230 | function testRsyncWithRemoveSourceFile() 231 | { 232 | $rsync = new Rsync(); 233 | $rsync->setRemoveSource(true); 234 | 235 | $expected = "/usr/bin/rsync -La --remove-source-files /origin /target"; 236 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 237 | 238 | $this->assertEquals($expected, $actual); 239 | } 240 | 241 | public 242 | function testRsyncWithPruneEmptyDIrs() 243 | { 244 | $rsync = new Rsync(); 245 | $rsync->setPruneEmptyDirs(true); 246 | 247 | $expected = "/usr/bin/rsync -La --prune-empty-dirs /origin /target"; 248 | $actual = $rsync->getCommand('/origin', '/target')->getCommand(); 249 | 250 | $this->assertEquals($expected, $actual); 251 | } 252 | 253 | public 254 | function getTargetDir() 255 | { 256 | return self::$targetDir; 257 | } 258 | 259 | public 260 | function getSourceDir() 261 | { 262 | return self::$sourceDir; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /tests/AFM/Rsync/Tests/SSHTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace AFM\Rsync\Tests; 13 | 14 | use AFM\Rsync\SSH; 15 | 16 | class SSHTest extends \PHPUnit_Framework_TestCase 17 | { 18 | public function testValidConfiguration() 19 | { 20 | $fakePrivateKey = __DIR__.'/fake_key'; 21 | 22 | touch($fakePrivateKey); 23 | 24 | new SSH(array('port' => 1443, 'private_key' => $fakePrivateKey)); 25 | 26 | $this->assertTrue(true); 27 | 28 | unlink($fakePrivateKey); 29 | } 30 | 31 | /** 32 | * @expectedException \InvalidArgumentException 33 | */ 34 | public function testInvalidPrivateKey() 35 | { 36 | new SSH(array('private_key' => '/cant/read!')); 37 | } 38 | 39 | /** 40 | * @expectedException \InvalidArgumentException 41 | */ 42 | public function testInvalidPortNumber() 43 | { 44 | new SSH(array('port' => 'not_a_number')); 45 | } 46 | 47 | public function testGetConnectionString() 48 | { 49 | $ssh = new SSH(array('username' => 'test', 'host' => 'test.com')); 50 | 51 | $actual = $ssh->getCommand(); 52 | $expected = "ssh test@test.com"; 53 | 54 | $this->assertEquals($expected, $actual); 55 | } 56 | 57 | public function testGetConnectionNonStandardPort() 58 | { 59 | $ssh = new SSH(array('username' => 'test', 'host' => 'test.com', 'port' => 231)); 60 | 61 | $actual = $ssh->getCommand(); 62 | $expected = "ssh -p '231' test@test.com"; 63 | 64 | $this->assertEquals($expected, $actual); 65 | } 66 | 67 | public function testGetConnectionWithPrivateKey() 68 | { 69 | $privateKey = "./key"; 70 | $privateKeyWithSpaces = "./key key"; 71 | 72 | touch($privateKey); 73 | touch($privateKeyWithSpaces); 74 | 75 | $ssh = new SSH(array('username' => 'test', 'host' => 'test.com', 'private_key' => $privateKey)); 76 | 77 | $actual = $ssh->getCommand(); 78 | $expected = "ssh -i '".$privateKey."' test@test.com"; 79 | 80 | $this->assertEquals($expected, $actual); 81 | 82 | $ssh->setPrivateKey($privateKeyWithSpaces); 83 | 84 | $actual = $ssh->getCommand(); 85 | $expected = "ssh -i '".$privateKeyWithSpaces."' test@test.com"; 86 | 87 | $this->assertEquals($expected, $actual); 88 | 89 | unlink($privateKey); 90 | unlink($privateKeyWithSpaces); 91 | } 92 | 93 | public function testGetHostConnection() 94 | { 95 | $ssh = new SSH(array('username' => 'test', 'host' => 'test.com')); 96 | 97 | $actual = $ssh->getHostConnection(); 98 | $expected = "test@test.com"; 99 | 100 | $this->assertEquals($expected, $actual); 101 | } 102 | 103 | public function testGetConnectionOptions() 104 | { 105 | $ssh = new SSH(array('username' => 'test', 'host' => 'test.com', 'port' => 231, 'private_key' => '/dev/null')); 106 | 107 | $actual = $ssh->getConnectionOptions(); 108 | $expected = "ssh -p '231' -i '/dev/null'"; 109 | 110 | $this->assertEquals($expected, $actual); 111 | } 112 | 113 | /** 114 | * @expectedException \InvalidArgumentException 115 | */ 116 | public function testGetConnectionNoUsername() 117 | { 118 | $ssh = new SSH; 119 | 120 | $ssh->getCommand(); 121 | } 122 | 123 | /** 124 | * @expectedException \InvalidArgumentException 125 | */ 126 | public function testGetConnectionNoHost() 127 | { 128 | $ssh = new SSH(array('username' => 'test')); 129 | 130 | $ssh->getCommand(); 131 | } 132 | 133 | public function testSetExecutable() 134 | { 135 | $ssh = new SSH( 136 | array('username' => 'test', 'host' => 'test.com', 'port' => 231, 'executable' => 'c:/cygwin/bin/ssh.exe') 137 | ); 138 | 139 | $actual = $ssh->getConnectionOptions(); 140 | $expected = "c:/cygwin/bin/ssh.exe -p '231'"; 141 | 142 | $this->assertEquals($expected, $actual); 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /tests/AFM/Rsync/Tests/dir1/file1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertofem/rsync-lib/a4a1f1f26cb7309ac87a299a62aac04b77af0429/tests/AFM/Rsync/Tests/dir1/file1 -------------------------------------------------------------------------------- /tests/AFM/Rsync/Tests/dir1/file2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertofem/rsync-lib/a4a1f1f26cb7309ac87a299a62aac04b77af0429/tests/AFM/Rsync/Tests/dir1/file2 -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please read 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | $loader = require __DIR__.'/../vendor/autoload.php'; 13 | $loader->add('AFM\Rsync\Tests', __DIR__); 14 | 15 | /** 16 | * http://www.php.net/manual/en/function.rmdir.php#108113 17 | */ 18 | function rrmdir($dir) 19 | { 20 | foreach (glob($dir.'/*') as $file) { 21 | if (is_dir($file)) { 22 | rrmdir($file); 23 | } else { 24 | unlink($file); 25 | } 26 | } 27 | 28 | rmdir($dir); 29 | } 30 | 31 | function compare_directories($dir1, $dir2) 32 | { 33 | $output = shell_exec("diff --brief ".$dir1." ".$dir2." 2>&1"); 34 | 35 | if (strlen($output) > 0) { 36 | return false; 37 | } 38 | 39 | return true; 40 | } --------------------------------------------------------------------------------