Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Sync local files with FTP
<?php
require_once realpath(__DIR__) . '/RecursiveFilter.php';
// can be replace with http://www.php.net/manual/en/class.recursivecallbackfilteriterator.php
// https://github.com/nette/nette/tree/master/Nette/Iterators
/**
* Copyright (c) 2011 Roman Ožana (http://omdesign.cz).
*
* @author Roman Ožana <ozana@omdesign.cz>
* @link http://omdesign.cz
*/
/**
* Synchronize local directories with FTP
*
* @see http://stackoverflow.com/questions/3321547/help-using-regexiterator-in-php
*/
class FtpSync {
/**
* FTP string
* ftp://username:password@sld.domain.tld:21/path1/path2/
*
* @var string
*/
private $ftp;
/**
* Remoote root dir
*
* @var string
*/
private $root;
/**
* Skip files or dirs
*
* @var array
*/
private $skip = array();
/**
* FTP cnnection
*
* @var int|mixed
*/
private $connection = null;
/**
* List of current local files
*
* @var array
*/
private $currentList = array();
/**
* List of preview local files status
*
* @var array
*/
private $previewList = array();
/**
* Repeat error action next time
*/
private $repeat = false;
/**
* Create new ftp sync object
* ftp://username:password@sld.domain.tld:21/path1/path2/
*
* @param string $ftp
*/
function __construct($ftp) {
$this->ftp = $ftp;
}
/**
* Close connection, if exists
*/
public function __destruct() {
if (!is_null($this->connection)) ftp_close($this->connection);
}
/**
* Create new connection or use existing
*
* @return int|mixed|null
*/
protected function connection() {
if (is_null($this->connection)) {
$ftp = parse_url($this->ftp);
if ($ftp['scheme'] === 'ftp') {
$connection = ftp_connect($ftp['host'], isset($ftp['port']) ? $ftp['port'] : 21) or die("Couldn't connect to " . $ftp['host']);
if (ftp_login($connection, isset($ftp['user']) ? $ftp['user'] : '', isset($ftp['pass']) ? $ftp['pass'] : '')) {
if (isset($ftp['path'])) ftp_chdir($connection, $ftp['path']);
$this->root = rtrim($ftp['path'], '/') . '/';
$this->connection = $connection;
} else {
$this->connection = null;
}
}
}
return $this->connection;
}
/**
* Upload list of files to FTP
*
* @param string $dir
* @param array $files
* @return bool
*/
protected function upload($dir, $files) {
if (!@ftp_chdir($this->connection(), $this->root)) return false;
foreach ($files as $file => $hash) {
$toDir = $this->root;
$dirname = dirname($file);
if (!@ftp_chdir($this->connection(), $toDir . $dirname)) {
ftp_chdir($this->connection(), $toDir);
$toDir = strtok($dirname, '/\\');
do {
if ($toDir == '') continue;
@ftp_mkdir($this->connection(), $toDir);
ftp_chdir($this->connection(), $toDir);
} while ($toDir = strtok('/\\'));
}
if (ftp_put($this->connection(), basename($file), $dir . '/' . $file, FTP_BINARY)) {
echo 'Upload [' . $file . ']' . PHP_EOL;
} else {
echo 'ERROR Upload [' . $file . ']' . PHP_EOL;
if ($this->repeat) unset($this->currentList[$file]); // repeat action
}
}
}
/**
* Delete remoote files
*
* @param array $files
*/
protected function delete($files) {
$dirs = array();
foreach ($files as $file => $hash) {
$file = str_replace('\\', '/', $file);
if (@ftp_delete($this->connection(), $this->root . $file)) {
echo 'Delete [' . $file . ']' . PHP_EOL;
} else {
echo 'ERROR Delete [' . $file . ']' . PHP_EOL;
if ($this->repeat) $this->currentList[$file] = $hash; // repeat action
}
// prepare dir list
$dirs[dirname($this->root . $file)] = true;
}
// remove empty dirs afrer files deletion
foreach (array_keys($dirs) as $dir) {
if (@ftp_nlist($this->connection(), $dir) === array()) {
if (@ftp_rmdir($this->connection(), $dir)) {
echo 'Remove [' . $dir . ']' . PHP_EOL;
} else {
echo 'ERROR remove [' . $dir . ']' . PHP_EOL;
}
}
}
}
/**
* Create file list without empty directories
*
* @param string $localDir
* @return array
*/
protected function createFileList($localDir) {
$filesList = new RecursiveIteratorIterator(
new Nette\Iterators\RecursiveFilter(
new RecursiveDirectoryIterator(
$localDir,
\RecursiveDirectoryIterator::FOLLOW_SYMLINKS
),
array($this, 'fileFilter')
)
);
$files = array();
foreach ($filesList as $file) {
if ($file->isDir()) continue; // skip directories
$fileName = substr($file->__toString(), strlen($localDir) + 1);
$files[$fileName] = sha1_file($file);
}
return $files;
}
/**
* Make full sync job on local dir
*
* @param string $dir
* @param array $skip
*/
public function fullSync($dir, $skip = array()) {
$ftpsyncFile = $dir . DIRECTORY_SEPARATOR . '.ftpsync';
// check preview status, if exists
if (file_exists($ftpsyncFile)) {
$this->previewList = (array)json_decode(file_get_contents($ftpsyncFile));
}
// prepare lists
$this->currentList = $this->createFileList($dir, $skip);
$uploadFiles = array_diff($this->currentList, $this->previewList);
$deleteFiles = array_diff_key($this->previewList, $this->currentList);
// check what need to be upload ?
if (!empty($uploadFiles)) $this->upload($dir, $uploadFiles);
// check what need to be deleted on server ?
if (!empty($deleteFiles)) $this->delete($deleteFiles);
echo 'Total: ' . count($this->currentList) . ' files synced' . PHP_EOL;
// save current status to json
file_put_contents($ftpsyncFile, json_encode($this->currentList));
}
/**
* Set list of jump over filenames
*
* @param array $skip
* @return FtpSync
*/
public function skip(array $skip) {
$this->skip = $skip;
return $this;
}
/**
* Repeat action when error occured
*
* @return FtpSync
*/
public function repeatOnError() {
$this->repeat = true;
return $this;
}
/**
* Filter files bz filename
*
* @param \SplFileObject $file
* @return bool
*/
public function fileFilter($file) {
/* @var $file \SplFileObject */
if (in_array($file->getFilename(), $this->skip)) return false; // jump over files
if (substr($file->getFilename(), 0, 1) == '.') return false; // jump over hidden files
return true;
}
}
<?php
/**
* This file is part of the Nette Framework (http://nette.org)
*
* Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
*
* For the full copyright and license information, please view
* the file license.txt that was distributed with this source code.
*/
namespace Nette\Iterators;
use Nette;
/**
* Callback recursive iterator filter.
*
* @author David Grudl
*/
class RecursiveFilter extends \FilterIterator implements \RecursiveIterator
{
/** @var callback */
private $callback;
/** @var callback */
private $childrenCallback;
/**
* Constructs a filter around another iterator.
* @param
* @param callback
*/
public function __construct(\RecursiveIterator $iterator, $callback, $childrenCallback = NULL)
{
parent::__construct($iterator);
$this->callback = $callback;
$this->childrenCallback = $childrenCallback;
}
public function accept()
{
return $this->callback === NULL || call_user_func($this->callback, $this);
}
public function hasChildren()
{
return $this->getInnerIterator()->hasChildren()
&& ($this->childrenCallback === NULL || call_user_func($this->childrenCallback, $this));
}
public function getChildren()
{
return new static($this->getInnerIterator()->getChildren(), $this->callback, $this->childrenCallback);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.