#!/usr/local/bin/php <? /* * lazyftp.php - Keeps remote FTP files in sync with local files. * * Copyright (c) 2003 by Ryan Grove (ryan@wonko.com). * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * *************************************************************************** * LazyFTP is a relatively straightforward PHP script that keeps remote * files in sync with local files via FTP. It does this by monitoring the * local directory tree for additions, changes, and deletions, and uploading * or deleting files on the FTP server when necessary. * * This version of LazyFTP does not create or delete remote directories, so * you should make sure your remote tree is an exact mirror of your local * tree before you run LazyFTP. * * You can read more about LazyFTP at http://wonko.com/lazyftp/. */ ############################################################################ # User-configurable settings ############################ /** Local directory to monitor for changes. */ $localdir = ""; /** FTP server hostname or IP address. */ $ftpserver = ""; /** FTP server port. */ $ftpport = 21; /** FTP username. */ $ftpuser = "anonymous"; /** FTP password. */ $ftppass = "anon@anon.com"; /** FTP directory to which changed files should be uploaded. */ $ftpdirectory = ""; /** Whether or not to use passive mode for file transfers. */ $passive = false; /** Whether or not to connect using SSL-FTP. */ $ssl = false; /** Whether or not to output verbose status messages. */ $verbose = false; /** Interval at which files are checked for changes (in seconds). */ $interval = 5; ############################################################################ # End of user-configurable settings ################################### set_time_limit(0); define("VERSION", "1.0.0"); define("_DEBUG", false); /** FTP class. */ $ftp = new ftp; /** Recursive list of files and directories under $localdir. */ $files = array(); // Parse commandline arguments. parseArgs(); // Say hello. echo "\n"; echo "LazyFTP v".VERSION."\n"; echo "Copyright (c) 2003 Ryan Grove. All rights reserved.\n\n"; // Make sure $localdir is valid. if (!is_dir($localdir)) error("Not a valid directory: $localdir"); message("Monitoring $localdir."); // Main program loop. while(1 == 1) { $oldfiles = $files; $files = array(); $changed = array(); $deleted = array(); clearstatcache(); getFileList($localdir); // Discover added and changed files. foreach($files as $file) { if (!in_array($file, $oldfiles)) { $changed[] = $file['name']; // Update the $oldfiles array so it doesn't think this file was // deleted as a result of the different 'modified' time. $oldfiles[$file['name']] = $file; } } // Discover deleted files. foreach($oldfiles as $oldfile) { if (!in_array($oldfile, $files)) $deleted[] = $oldfile['name']; } // Upload changed files. if (count($oldfiles) > 0 && (count($changed) > 0 || count($deleted) > 0)) { // Make sure we're connected to the FTP server. if ($ftp->connect($ftpserver, $ftpport, $ftpuser, $ftppass, $ssl, $passive)) { // Upload each changed file. foreach($changed as $file) { $remotedir = $ftpdirectory.str_replace($localdir, "", dirname($file)); $remotefile = escapeshellcmd(basename($file)); if ($ftp->chdir($remotedir)) { if ($ftp->uploadFile($file, $remotefile)) message("Uploaded $file."); else message("Error: Could not upload $file to $remotedir/$remotefile."); } else { message("Error: Could not change remote directory to $remotedir."); } } // Remove each deleted file. foreach($deleted as $file) { $remotedir = $ftpdirectory.str_replace($localdir, "", dirname($file)); $remotefile = escapeshellcmd(basename($file)); if ($ftp->chdir($remotedir)) { if ($ftp->delete($remotefile)) message("Deleted $remotedir/$remotefile."); else message("Error: Could not delete $remotedir/$remotefile."); } else { message("Error: Could not change remote directory to $remotedir."); } } } else { message("Error: Could not establish FTP connection."); } } sleep($interval); } ############################################################################ # Functions ########### /** * Prints an error message to standard output, then exits. Kinda like * message() only less friendly. * * @param message Error message to print. * @see message() */ function error($message) { echo "[".date("m/d/Y H:i:s")."] Fatal Error: $message\n"; exit; } /** * Prints a message to standard output if verbose mode is on. Prefaces the * message with a timestamp and puts a newline at the end if <i>break</i> is * true. * * @param message Message to print. * @param break Whether or not to add a newline at the end. * @see error() */ function message($message, $break = true) { global $verbose; echo $verbose ? "[".date("m/d/Y H:i:s")."] $message\n" : ""; } /** * Parses commandline arguments. */ function parseArgs() { global $localdir, $ftpserver, $ftpport, $ftpuser, $ftppass, $ftpdirectory, $passive, $verbose; for ($i = 1; $i < $_SERVER["argc"]; $i++) { switch($_SERVER["argv"][$i]) { case "-i": case "--interval": $i++; $interval = $_SERVER["argv"][$i]; break; case "-l": case "--localdir": $i++; $localdir = $_SERVER["argv"][$i]; break; case "-p": case "--pass": case "--password": case "--ftppass": case "--ftppassword": $i++; $ftppass = $_SERVER["argv"][$i]; break; case "-P": case "--passive": case "--passivemode": case "--pasv": $passive = true; break; case "-r": case "--remotedir": case "--remotedirectory": case "--ftpdir": case "--ftpdirectory": $i++; $ftpdirectory = $_SERVER["argv"][$i]; break; case "-s": case "--server": case "--ftpserver": case "--host": $i++; $ftpserver = $_SERVER["argv"][$i]; break; case "-S": case "--ssl": case "--secure": $ssl = true; break; case "-u": case "--user": case "--username": case "--ftpuser": case "--ftpusername": $i++; $ftpuser = $_SERVER["argv"][$i]; break; case "-v": case "--verbose": $verbose = true; break; case "-?": case "-h": case "--help": echo "Usage: lazyftp [option <parameter>...]\n"; echo "\n"; echo " -i <interval> Interval at which files are checked for changes (in seconds).\n"; echo " -l <localdir> Local directory to monitor for changes.\n"; echo " -p <pass> FTP password.\n"; echo " -P Use passive mode for file transfers.\n"; echo " -r <remotedir> FTP directory to which changed files should be uploaded.\n"; echo " -s FTP server hostname or IP address.\n"; echo " -S Connect using SSL-FTP.\n"; echo " -u <user> FTP username (if not specified, login will be anonymous).\n"; echo " -v Enable verbose status messages.\n"; echo "\n"; echo "Example:\n"; echo " lazyftp.php -l /my/files -s ftp.pants.com -r /home/monkey -v\n"; echo "\n"; echo "These options can be set permanently by editing lazyftp.php.\n"; exit; break; default: } } } /** * Iterates through a directory tree recursively and builds an array of * files and their last modified time. * * @param dirname Directory to iterate through. */ function getFileList($dirname) { global $files, $localdir; $d = dir($dirname); while($entry = $d->read()) { if ($entry != '.' && $entry != '..') { if (is_dir($dirname.'/'.$entry)) { getFileList($dirname.'/'.$entry); } else { $file = $dirname.'/'.$entry; $files[$file] = array('name' => $file, 'modified' => filemtime($file)); } } } $d->close(); } ############################################################################ # Classes ######### class ftp { /** <i>true</i> when connected to a server, <i>false</i> otherwise. */ var $connected; /** Resource handle for the current connection (if any). */ var $conn; /** System type of the remote server (if supported). */ var $systype; function ftp() { $this->connected = false; $this->ssl = false; } /** * Establishes a connection with an FTP server if one is not already * open. * * @param server Server to connect to. * @param port Server port to connect to. * @param user Login username (leave empty for anonymous). * @param pass Login password (leave empty for anonymous). * @param ssl Set to <i>true</i> to attempt an SSL-FTP connection. * Default is <i>false</i>. * @param passive Set to <i>true</i> to turn passive mode on. Default is * <i>false</i>. * @param timeout Timeout (in seconds) for all network operations on * this connection. Default is 90. * @return <i>true</i> on success (or if a connection is already open), * <i>false</i> on failure or error. */ function connect($server, $port = 21, $user = "anonymous", $pass = "anon@anon.com", $ssl = false, $passive = false, $timeout = 90) { // Already connected. if ($this->connected) return true; if ($user == "") $user = "anonymous"; if ($pass == "") $pass = "anon@anon.com"; // Attempt to connect. $ssl ? $this->conn = @ftp_ssl_connect($server, $port, $timeout) : $this->conn = @ftp_connect($server, $port, $timeout); if ($this->conn === false) { // Couldn't connect. $this->connected = false; echo _DEBUG ? "Could not connect to $server on port $port.<br />\n" : ""; return false; } else { // Connected, now let's try logging in. if (@ftp_login($this->conn, $user, $pass)) { if ($passive) @ftp_pasv($this->conn, true); $this->systype = @ftp_systype($this->conn); $this->connected = true; return true; } else { $this->connected = false; echo _DEBUG ? "Invalid username or password.<br />\n" : ""; return false; } } } /** * Deletes the specified file from the server. * * @param filename File to delete. * @return <i>true</i> on success, <i>false</i> on failure. */ function delete($filename) { if ($this->connected === false) { echo _DEBUG ? "Can't delete file when no connection is open.<br />\n" : ""; return false; } if (@ftp_delete($this->conn, $filename)) { return true; } else { echo _DEBUG ? "Could not delete file $filename.<br />\n" : ""; return false; } } /** * Disconnects from the server and closes the current connection (if * any). * * @return <i>true</i> on success, <i>false</i> on failure. */ function disconnect() { if ($this->connected) { @ftp_quit($this->conn); return true; } else { echo _DEBUG ? "Cannot disconnect when not connected.<br />\n" : ""; return false; } } /** * Changes to the specified directory. * * @param directory Directory to change to. * @return <i>true</i> on success, <i>false</i> on failure. */ function chdir($directory) { if ($this->connected === false) { echo _DEBUG ? "Can't change directory when no connection is open.<br />\n" : ""; return false; } if (@ftp_chdir($this->conn, $directory)) { return true; } else { echo _DEBUG ? "Could not change directory to $directory.<br />\n" : ""; return false; } } /** * Returns the name of the current working directory. * * @return Name of current directory, or <i>false</i> on failure. */ function pwd() { if ($this->connected === false) { echo _DEBUG ? "Can't get working directory name when no connection is open.<br />\n" : ""; return false; } if ($pwd = @ftp_pwd($this->conn)) { return $pwd; } else { echo _DEBUG ? "Could not get working directory name.<br />\n" : ""; return false; } } /** * Uploads a file to the server in the current directory. * * @param localfile Filename of the file to upload on the local machine. * @param remotefile Filename of the file on the remote machine. * @param mode File mode (one of the constants FTP_ASCII or FTP_BINARY). * @return <i>true</i> on success, <i>false</i> on failure. */ function uploadFile($localfile, $remotefile, $mode = FTP_BINARY) { if ($this->connected === false) { echo _DEBUG ? "Can't upload file when no connection is open.<br />\n" : ""; return false; } if (@ftp_put($this->conn, $remotefile, $localfile, $mode)) { return true; } else { echo _DEBUG ? "Upload failed.<br />\n" : ""; return false; } } } ?>