'lftp the Drupal tree to/from another server.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'arguments' => array( 'source' => 'May be lftp path or site alias. See lftp documentation and example.aliases.drushrc.php.', 'destination' => 'May be lftp path or site alias. See lftp documentation and example.aliases.drushrc.php.', ), 'options' => array( 'mode' => 'The unary flags to pass to rsync; --mode=rultz implies rsync -rultz. Default is -az.', 'LFTP-FLAG' => 'Most lftp flags passed to drush sync will be passed on to rsync. See rsync documentation.', 'exclude-conf' => 'Excludes settings.php from being rsynced. Default.', 'include-conf' => 'Allow settings.php to be rsynced', 'exclude-files' => 'Exclude the files directory.', 'exclude-sites' => 'Exclude all directories in "sites/" except for "sites/all".', 'exclude-other-sites' => 'Exclude all directories in "sites/" except for "sites/all" and the site directory for the site being synced. Note: if the site directory is different between the source and destination, use --exclude-sites followed by "drush rsync @from:%site @to:%site"', 'exclude-paths' => 'List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).', 'include-paths' => 'List of paths to include, seperated by : (Unix-based systems) or ; (Windows).', ), 'examples' => array( 'drush lsync @dev @stage' => 'lftp Drupal root from dev to stage (one of which must be local).', 'drush lsync ./ @stage:%files/img' => 'lftp all files in the current directory to the \'img\' directory in the file storage folder on stage.', ), 'aliases' => array('lsync'), 'deprecated-aliases' => array('sync'), 'topics' => array('docs-aliases'), ); return $items; } function drush_lftp_sync($source, $destination, $additional_options = array()) { // @todo Preflight destination in case it defines aliases used by the source //_drush_sitealias_preflight_path($destination); // After preflight, evaluate file paths $source_settings = drush_lftp_sitealias_evaluate_path($source, $additional_options); $destination_settings = drush_lftp_sitealias_evaluate_path($destination, $additional_options); foreach(array('source', 'destination') as $location) { if(isset(${$location . '_settings'}['lftp'])) { $lftp = ${$location . '_settings'}['lftp']; ${$location . '_path_show'} = $lftp['protocol'] . $lftp['host'] . str_replace('\'', '', ${$location . '_settings'}['path']); ${$location . '_path'} = str_replace('\'', '', ${$location . '_settings'}['path']); } else { ${$location . '_path'} = str_replace('\'', '', ${$location . '_settings'}['evaluated-path']); ${$location . '_path_show'} = str_replace('\'', '', ${$location . '_settings'}['evaluated-path']); } } if (!isset($source_settings)) { return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate source path !path.', array('!path' => $source))); } if (!isset($destination_settings)) { return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate destination path !path.', array('!path' => $destination))); } // @todo Check to see if this is an rsync multiple command (multiple sources and multiple destinations) $is_multiple = FALSE; if ($is_multiple === FALSE) { // If the user path is the same for the source and the destination, then // always add a slash to the end of the source. If the user path is not // the same in the source and the destination, then you need to know how // rsync paths work, and put on the trailing '/' if you want it. if ($source_settings['user-path'] == $destination_settings['user-path']) { $source_path .= '/'; } // Prompt for confirmation. This is destructive. if (!drush_get_context('DRUSH_SIMULATE')) { drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source_path_show, '!target' => $destination_path_show))); if (!drush_confirm(dt('Do you really want to continue?'))) { // was: return drush_set_error('CORE_SYNC_ABORT', 'Aborting.'); return drush_user_abort(); } } // Exclude settings is the default only when both the source and // the destination are aliases or site names. Therefore, include // settings will be the default whenever either the source or the // destination contains a : or a /. $include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE); // Go ahead and call rsync with the paths we determined return drush_lftp_call_lftp($lftp, $source_path, $destination_path, $additional_options, $include_settings_is_default); } } function drush_lftp_call_lftp($lftp, $source, $destination, $additional_options = array(), $include_settings_is_default = TRUE, $live_output = TRUE) { $available_options = array( 'dry-run' ); $options = ''; foreach ($available_options as $available_option) { $value = drush_get_option_override($additional_options, $available_option); if ($value == '1') { $options .= " --$available_option"; } else if (isset($value)){ $options .= " --$available_option=" . escapeshellarg($value); } } $lftp_user = $lftp['user']; $lftp_pass = $lftp['pass']; $lftp_host = $lftp['host']; $lftp_protocol = $lftp['protocol']; $command = "lftp -u $lftp_user,$lftp_pass -e 'mirror -v $options $source $destination; exit;' $lftp_protocol$lftp_host"; $live_output = TRUE; if ($live_output) { $exec_result = drush_op_system($command); $result = ($exec_result == 0); } else { $result = drush_shell_exec($command); } if($result) { drush_log(dt("Synced from '!src' to '!dest'", array('!src' => $source, '!dest' => $destination)), 'success'); return TRUE; } else { return drush_set_error('DRUSH_LFTP_CANT_SYNC', dt("Could not sync from '!src' to '!dest'", array('!src' => $source, '!dest' => $destination))); } } function drush_lftp_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE) { $site_alias_settings = array(); $path_aliases = array(); $remote_user = ''; $os = NULL; $preflight = _drush_sitealias_preflight_path($path); if (!isset($preflight)) { return NULL; } $alias = $preflight['alias']; $path = $preflight['path']; $machine = $preflight['machine']; if (isset($alias)) { $site_alias_settings = drush_sitealias_get_record($alias); $os = drush_os($site_alias_settings); } if (!empty($site_alias_settings)) { if ($local_only && array_key_exists('remote-host', $site_alias_settings)) { return drush_set_error('DRUSH_REMOTE_SITE_IN_LOCAL_CONTEXT', dt("A remote site alias was used in a context where only a local alias is appropriate.")); } // Apply any options from this alias that might affect our rsync drush_sitealias_set_alias_context($site_alias_settings); // Use 'remote-host' from settings if available; otherwise site is local if (array_key_exists('remote-host', $site_alias_settings) && !drush_is_local_host($site_alias_settings['remote-host'])) { if (array_key_exists('remote-user', $site_alias_settings)) { $remote_user = $site_alias_settings['remote-user'] . '@'; } $machine = $remote_user . $site_alias_settings['remote-host']; } // lftp else if (array_key_exists('lftp', $site_alias_settings)) { $lftp = $site_alias_settings['lftp']; if (array_key_exists('url', $lftp)) { $remote_user = $lftp['user'] . '@'; } $machine = $remote_user . $lftp['host']; } else { $machine = ''; } } else { // Strip the machine portion of the path if the // alias points to the local machine. if (drush_is_local_host($machine)) { $machine = ''; } else { $machine = "$remote_user$machine"; } } // If the --exclude-other-sites option is specified, then // convert that into --include-path='%site' and --exclude-sites. if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_option_override($additional_options, 'exclude-other-sites-processed', FALSE, 'process')) { $additional_options['include-path'] = '%site,' . drush_get_option_override($additional_options, 'include-path', ''); $additional_options['exclude-sites'] = TRUE; $additional_options['exclude-other-sites-processed'] = TRUE; } // If the --exclude-files option is specified, then // convert that into --exclude-path='%files'. if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) { $additional_options['exclude-path'] = '%files,' . drush_get_option_override($additional_options, 'exclude-path', ''); $additional_options['exclude-files-processed'] = TRUE; } // If there was no site specification given, and the // machine is local, then try to look // up an alias record for the default drush site. if (empty($site_alias_settings) && empty($machine)) { $drush_uri = drush_bootstrap_value('drush_uri', drush_get_option(array('l', 'uri'), 'default')); $site_alias_settings = drush_sitealias_get_record($drush_uri); } // Always add transient defaults _drush_sitealias_add_transient_defaults($site_alias_settings); // The $resolve_path variable is used by drush_sitealias_resolve_path_references // to test to see if there are any path references such as %site or %files // in it, so that resolution is only done if the path alias is referenced. // Therefore, we can concatenate without worrying too much about the structure of // this variable's contents. $include_path = drush_get_option_override($additional_options, 'include-path', ''); $exclude_path = drush_get_option_override($additional_options, 'exclude-path', ''); $resolve_path = $path . $include_path . $exclude_path; // Resolve path aliases such as %files, if any exist in the path if (!empty($resolve_path)) { drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path); } if (array_key_exists('path-aliases', $site_alias_settings)) { $path_aliases = $site_alias_settings['path-aliases']; } // Get the 'root' setting from the alias; if it does not // exist, then get the root from the bootstrapped site. if (array_key_exists('root', $site_alias_settings)) { $drupal_root = $site_alias_settings['root']; } else { drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); } if (empty($drupal_root)) { $drupal_root = ''; } // Add a slash to the end of the drupal root, as below. elseif ($drupal_root[strlen($drupal_root)-1] != '/') { $drupal_root = $drupal_root . '/'; } $full_path_aliases = $path_aliases; foreach ($full_path_aliases as $key => $value) { // Expand all relative path aliases to be based off of the Drupal root if ((substr($value, 0, 1) != '/') && ($key != '%root')) { $full_path_aliases[$key] = $drupal_root . $value; } // We do not want slashes on the end of our path aliases. if (substr($value, 0, -1) == '/') { $full_path_aliases[$key] = substr($full_path_aliases[$key], -1); } } // Fill in path aliases in the path, the include path and the exclude path. $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path); if (!empty($include_path)) { drush_set_option('include-path', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path)); } if (!empty($exclude_path)) { drush_set_option('exclude-path', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path)); } // The path component is just the path part of the full // machine:path specification (including the colon). $path_component = (!empty($path) ? ':' . $path : ''); // Next make the rsync path, which includes the machine // and path components together. // First make empty paths or relative paths start from the drupal root. if (empty($path) || ($path[0] != '/')) { $path = $drupal_root . $path; } // Escape the path $path = drush_escapeshellarg($path, $os); // If there is a $machine component, to the path, then // add it to the beginning $evaluated_path = $path; if (!empty($machine)) { $evaluated_path = $machine . ':' . $path; } // // Add our result paths: // // evaluated-path: machine:/path // server-component: machine // path-component: :/path // path: /path // user-path: path (as specified in input parameter) // $site_alias_settings['evaluated-path'] = $evaluated_path; if (!empty($machine)) { $site_alias_settings['server-component'] = $machine; } $site_alias_settings['path-component'] = $path_component; $site_alias_settings['path'] = $path; $site_alias_settings['user-path'] = $preflight['path']; return $site_alias_settings; }