diff --git a/libraries.api.php b/libraries.api.php index 9606185..71e90e0 100644 --- a/libraries.api.php +++ b/libraries.api.php @@ -16,6 +16,10 @@ * - name: The official, human-readable name of the library. * - vendor url: The URL of the homepage of the library. * - download url: The URL of a web page on which the library can be obtained. + * - download file url: (optional) The URL where the latest version of the + * library can be downloaded. In case such a static URL exists the library + * can be downloaded automatically via Drush. Run + * 'drush help libraries-download' in the command-line for more information. * - path: (optional) A relative path from the directory of the library to the * actual library. Only required if the extracted download package contains * the actual library files in a sub-directory. @@ -211,6 +215,9 @@ function hook_libraries_info() { 'name' => 'Example library', 'vendor url' => 'http://example.com', 'download url' => 'http://example.com/download', + // It is important that this URL does not include the actual version to + // download. Not all libraries provide such a static URL. + 'download file url' => 'http://example.com/latest.tar.gz', // Optional: If, after extraction, the actual library files are contained in // 'sites/all/libraries/example/lib', specify the relative path here. 'path' => 'lib', @@ -343,6 +350,9 @@ function hook_libraries_info() { 'name' => 'Simple library', 'vendor url' => 'http://example.com/simple', 'download url' => 'http://example.com/simple', + // The download file URL can also point to a single file (instead of an + // archive). + 'download file url' => 'http://example.com/latest/simple.js', 'version arguments' => array( 'file' => 'readme.txt', // Best practice: Document the actual version strings for later reference. diff --git a/libraries.drush.inc b/libraries.drush.inc index 190bb31..3258713 100644 --- a/libraries.drush.inc +++ b/libraries.drush.inc @@ -16,6 +16,16 @@ function libraries_drush_command() { 'aliases' => array('lls', 'lib-list'), ); + $items['libraries-download'] = array( + 'description' => dt('Download library files of registered libraries.'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + 'aliases' => array('ldl', 'lib-download'), + 'arguments' => array( + 'libraries' => 'A comma delimited list of library machine names.', + ), + 'required-arguments' => TRUE, + ); + return $items; } @@ -87,3 +97,133 @@ function drush_libraries_list() { drush_print_table($rows, TRUE, $widths); } } + +/** + * Command callback. Downloads a library. + * + * Only libraries that provide a download file URL can be downloaded. + * + * @see hook_libraries_info() + * @see drush_pm_download() + */ +function drush_libraries_download() { + drush_command_include('pm-download'); + + $libraries = libraries_info(); + + // @todo Consider supporting downloading all downloadable libraries. + // @todo Consider offering a selection if no library is specified. + foreach (pm_parse_arguments(func_get_args(), FALSE) as $machine_name) { + if (!isset($libraries[$machine_name])) { + $message = dt("The !library library is not registered with Libraries API.\n", array('!library' => $machine_name)); + $message .= dt("Provide an info file for it or implement hook_libraries_info().\n"); + $message .= dt("See hook_libraries_info() for more information.\n"); + drush_set_error('DRUSH_LIBRARY_UKNOWN', $message); + continue; + } + $library = $libraries[$machine_name]; + + if (empty($library['download file url'])) { + $message = dt("The !library library cannot be downloaded.\n", array('!library' => $machine_name)); + $message .= dt("Libraries need to specify a download file URL to support being downloaded via Drush.\n"); + $message .= dt("See hook_libraries_info() for more information.\n"); + drush_set_error('DRUSH_LIBRARY_NOT_DOWNLOADABLE', $message); + continue; + } + $download_url = $library['download file url']; + + drush_log(dt('Downloading library !name ...', array('!name' => $machine_name))); + + // @see package_handler_download_project() in wget.inc + // It cannot be used directly because it will always try to extract the + // archive which fails when downloading a single file. + // @todo Modify upstream to be able to use + // package_handler_download_project() directly. + // Prepare download path. On Windows file name cannot contain '?'. + // See http://drupal.org/node/1782444 + $filename = str_replace('?', '_', basename($download_url)); + $download_path = drush_tempdir() . '/' . $filename; + + // Download the tarball. + // Never cache the downloaded file. The downloading relies on the fact that + // different versions of the library are available under the same URL as new + // versions are released. + $download_path = drush_download_file($download_url, $download_path, 0); + if ($download_path || drush_get_context('DRUSH_SIMULATE')) { + drush_log(dt('Downloading !filename was successful.', array('!filename' => $filename))); + } + else { + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Unable to download !project to !path from !url.', array('!project' => $machine_name, '!path' => $download_path, '!url' => $download_url))); + drush_log(dt('Error downloading !name', array('!name' => $machine_name)), 'error'); + continue; + } + + // @todo Suport MD5 file hashing. + + // Extract the tarball in place and return the full path to the untarred directory. + $download_base = dirname($download_path); + if (drush_file_is_tarball($download_path)) { + if (!$tar_file_list = drush_tarball_extract($download_path, $download_base, TRUE)) { + // An error has been logged. + return FALSE; + } + $tar_directory = drush_trim_path($tar_file_list[0]); + $download_path = $download_base . '/' . $tar_directory; + } + else { + $download_path = $download_base; + } + + // Determine the install location for the project. User provided + // --destination has preference. + $destination = drush_get_option('destination'); + if (!empty($destination)) { + if (!file_exists($destination)) { + drush_mkdir($destination); + } + $install_location = realpath($destination); + } + else { + /** @see _pm_download_destination_lookup() */ + // _pm_download_destination_lookup() pluralizes the passed type by + // appending an s. + // This relies on the fact that there is no library named 'contrib'. + // @todo Request that this be turned into a proper API upstream. + $install_location = _pm_download_destination('librarie'); + } + + // @todo Consider invoking a hook similar to + // hook_drush_pm_download_destination_alter(). + + // @todo Consider adding version-control support similar to pm-download. + + $install_location .= '/' . $machine_name; + + // Check if install location already exists. + if (is_dir($install_location)) { + if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $install_location)))) { + drush_delete_dir($install_location, TRUE); + } + else { + drush_log(dt("Skip installation of !project to !dest.", array('!project' => $library['machine name'], '!dest' => $install_location)), 'warning'); + continue; + } + } + + // Copy the project to the install location. + if (drush_op('_drush_recursive_copy', $download_path, $install_location)) { + drush_log(dt("Library !project downloaded to !dest.", array('!project' => $machine_name, '!dest' => $install_location)), 'success'); + + // @todo Consider invoking a hook similar to + // hook_drush_pm_post_download(). + + // @todo Support printing release notes. + } + else { + // We don't `return` here in order to proceed with downloading additional projects. + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project could not be downloaded to !dest.", array('!project' => $machine_name, '!dest' => $install_location))); + } + + // @todo Consider adding notify support. + } +} diff --git a/libraries.module b/libraries.module index 1f5f654..4827525 100644 --- a/libraries.module +++ b/libraries.module @@ -443,6 +443,7 @@ function libraries_info_defaults(array &$library, $name) { 'name' => $name, 'vendor url' => '', 'download url' => '', + 'download file url' => '', 'path' => '', 'library path' => NULL, 'version callback' => 'libraries_get_version',