Index: l10n_packager/l10n_packager.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_packager/Attic/l10n_packager.module,v
retrieving revision 1.1.2.2
diff -u -r1.1.2.2 l10n_packager.module
--- l10n_packager/l10n_packager.module 5 Sep 2009 17:15:58 -0000 1.1.2.2
+++ l10n_packager/l10n_packager.module 2 Oct 2009 20:57:32 -0000
@@ -6,10 +6,40 @@
* Localization packager module for localization server.
*/
+// Release packager status: do not repackage anymore
+define('L10N_PACKAGER_DISABLED', 0);
+// Release packager status: keep repackaging
+define('L10N_PACKAGER_ACTIVE', 1);
+// Release packager status: error
+define('L10N_PACKAGER_ERROR', 2);
+
/**
* Implementation of hook_menu().
*/
function l10n_packager_menu() {
+ // Settings and administration
+ $items['admin/l10n_server/packager'] = array(
+ 'title' => 'Packaging tools',
+ 'description' => 'Configure automatic packaging for translations.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('l10n_packager_settings_form'),
+ 'file' => 'l10n_packager.admin.inc',
+ 'access arguments' => array('administer localization community'),
+ );
+ $items['admin/l10n_server/packager/configure'] = array(
+ 'title' => 'Configure',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['admin/l10n_server/packager/repackage'] = array(
+ 'title' => 'Repackage',
+ 'description' => 'Select project, releases and languages to repackage inmediately',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('l10n_packager_admin_repackage_form'),
+ 'file' => 'l10n_packager.admin.inc',
+ 'access arguments' => array('administer localization community'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ // Refresh language list
$items['admin/settings/language/packager'] = array(
'title' => 'Export language list',
'page callback' => 'l10n_packager_page',
@@ -21,6 +51,27 @@
}
/**
+ * Implementation of hook_cron()
+ */
+function l10n_packager_cron() {
+ if ($interval = variable_get('l10n_packager_update', 0)) {
+ module_load_include('inc', 'l10n_packager');
+ $timestamp = time() - $interval;
+ $file_limit = variable_get('l10n_packager_file_limit', 1);
+ $count_files = $count_check = 0;
+
+ // Go for it: check releases for repackaging. We need project_uri for later
+ $result = db_query_range("SELECT r.rid, r.pid, r.title, pr.checked, pr.updated, pr.package, p.uri FROM {l10n_community_release} r INNER JOIN {l10n_community_project} p ON r.pid = p.pid LEFT JOIN {l10n_packager_release} pr ON pr.rid = r.rid WHERE pr.package IS NULL OR (pr.package = %d AND (pr.checked < %d OR pr.updated < %d)) ORDER BY pr.checked", L10N_PACKAGER_ACTIVE, $timestamp, $timestamp, 0, variable_get('l10n_packager_check', 10));
+
+ while ((!$file_limit || $file_limit > $count_files) && ($release = db_fetch_object($result))) {
+ $updates = l10n_packager_release_check($release, FALSE, $file_limit - $count_files, NULL, TRUE);
+ $count_files += count($updates);
+ $count_check++;
+ }
+ watchdog('l10n_packager', 'Checked %checked releases. Repackaged %repack translations.', array('%checked' => $count_check, '%repack' => $count_files));
+ }
+}
+/**
* Implementation of hook_form_alter().
*/
function l10n_packager_form_alter(&$form, $form_state, $form_id) {
@@ -49,7 +100,7 @@
$item->addChild($key, $language->$key);
}
}
- $xml_path = file_create_path('l10n_packager');
+ $xml_path = file_create_path(variable_get('l10n_packager_directory', 'l10n_packager'));
file_check_directory($xml_path, FILE_CREATE_DIRECTORY);
if ($xml->asXML($xml_path .'/languages.xml')) {
drupal_set_message(t('Languages XML exported.'));
Index: l10n_packager/l10n_packager.install
===================================================================
RCS file: l10n_packager/l10n_packager.install
diff -N l10n_packager/l10n_packager.install
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ l10n_packager/l10n_packager.install 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,93 @@
+ 'Information about repackaging for each release',
+ 'fields' => array(
+ 'rid' => array(
+ 'description' => 'Reference to {l10n_community_release}.rid',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'unsigned' => TRUE,
+ ),
+ 'package' => array(
+ 'description' => 'Packaging status for this release.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default value' => 1,
+ 'disp-width' => '11'
+ ),
+ 'checked' => array(
+ 'description' => 'Unix timestamp of last time this release was checked.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'disp-width' => '11'
+ ),
+ 'updated' => array(
+ 'description' => 'Unix timestamp of last time this release files were updated.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'disp-width' => '11'
+ ),
+ ),
+ 'primary key' => array('rid'),
+ );
+ $schema['l10n_packager_file'] = array(
+ 'description' => 'Links releases and languages to files',
+ 'fields' => array(
+ 'drid' => array(
+ 'description' => 'Internal numeric identifier for a release file.',
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ ),
+ 'rid' => array(
+ 'description' => 'Reference to {l10n_community_release}.rid',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'unsigned' => TRUE,
+ ),
+ 'language' => array(
+ 'description' => 'Reference to the {languages}.language for the translation package',
+ 'type' => 'varchar',
+ 'length' => '12',
+ 'not null' => TRUE
+ ),
+ 'fid' => array(
+ 'description' => 'Reference to {files}.fid',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'unsigned' => TRUE,
+ ),
+ 'checked' => array(
+ 'description' => 'Unix timestamp of last time translation for this language was checked.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'disp-width' => '11'
+ ),
+ ),
+ 'primary key' => array('drid'),
+ );
+ return $schema;
+}
+
+/**
+ * Implementation of hook_install()
+ */
+function l10n_packager_install() {
+ drupal_install_schema('l10n_packager');
+}
+
+/**
+ * Implementation of hook_uninstall()
+ */
+function l10n_packager_uninstall() {
+ drupal_uninstall_schema('l10n_packager');
+}
Index: l10n_packager/l10n_packager.inc
===================================================================
RCS file: l10n_packager/l10n_packager.inc
diff -N l10n_packager/l10n_packager.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ l10n_packager/l10n_packager.inc 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,256 @@
+uri . '-' . $release->title;
+ }
+ else {
+ return '';
+ }
+}
+
+/**
+ * Check release translations and repackage if needed
+ *
+ * For each release we store packaging data in {l10n_packager_release}
+ * - The 'checked' timestamp is the last time all languages for this release were checked
+ * - The 'updated' timestamp is the last time a file was updated for this release
+ *
+ * We don't updated the 'checked' field untile we've checked all the languages for this release
+ * so we can keep track of releases and files and package a few languages on every cron
+ *
+ * @param $release
+ * Release object
+ * @param $force
+ * Force repackage even when strings not updated
+ * @param $limit
+ * Maximum number of files to create
+ * @param $language
+ * Optional language object to check only this one
+ * @param $cron
+ * This is a cron run, a release may be packaged partially, for some languages
+ */
+function l10n_packager_release_check($release, $force = FALSE, $limit = 0, $language = NULL, $cron = FALSE) {
+ $check_languages = $language ? array($language->language => $language) : l10n_community_get_languages();
+ $updated = array();
+ // We get update time before creating files so the release checked time is <= file timestamp
+ $timestamp = time();
+
+ $files = l10n_packager_release_get_files($release->rid);
+ $last_updated = l10n_packager_translation_last_updated($release->rid);
+
+ // Get only the languages we have translations for, that need updating
+ $languages = array();
+ foreach($check_languages as $lang => $language) {
+ if (!empty($last_updated[$lang]) && ($force || empty($downloads[$lang]) || $last_updated[$lang] > $files[$lang]->checked)) {
+ $languages[$lang] = $language;
+ }
+ }
+
+ // For this special case we check we didn't stop before in the middle of a release
+ // Otherwise it could stick on a release for ever when forcing
+ if ($force && $cron && $release->checked < $release->updated) {
+ foreach($files as $lang => $file) {
+ if (!empty($file->checked) && $file->checked > $release->checked) {
+ unset($languages[$lang]);
+ }
+ }
+ }
+
+ // Repackage this relase for the remaining language list
+ while((!$limit || $limit > count($updated)) && ($language = array_shift($languages))) {
+ $lang = $language->language;
+ // Warning: this may be upload release data with or wthout file
+ $existing = !empty($files[$lang]) ? $files[$lang] : NULL;
+ if ($file = l10n_packager_release_package($release, $language, $existing, $timestamp)) {
+ $updated[$lang] = $file;
+ }
+ else {
+ $updated[$lang] = FALSE;
+ }
+ }
+
+ // Update the release data. We just mark it as checked if there are no languages left
+ if (!count($languages)) {
+ $release->checked = $timestamp;
+ }
+ if ($updated) {
+ $release->updated = $timestamp;
+ }
+ // If it is the first time we check this release, we need to create the record
+ if (isset($release->package)) {
+ drupal_write_record('l10n_packager_release', $release, 'rid');
+ }
+ else {
+ $release->package = L10N_PACKAGER_ACTIVE;
+ drupal_write_record('l10n_packager_release', $release);
+ }
+ return $updated;
+}
+
+/**
+ * Generate a new file for a given release or update an existing one
+ *
+ * @param $release
+ * Release object with uri and rid properties
+ * @param $language
+ * Language object
+ * @param $file
+ * Release file object to be updated
+ * @param $timestamp
+ * Timestamp to mark the files, for it to be consistent across tables
+ * @return File object
+ */
+function l10n_packager_release_package($release, $language, $file = NULL, $timestamp = NULL) {
+ $timestamp = $timestamp ? $timestamp : time();
+ $variables = array('%release' => l10n_packager_release_name($release), '%language' => $language->name);
+ if (!$file) {
+ $file = new stdClass();
+ $file->rid = $release->rid;
+ $file->language = $language->language;
+ }
+ // Generate tarball or PO file and get file name.
+ $export_result = l10n_community_export($release->uri, $release->rid, $language, FALSE, variable_get('l10n_packager_format', 'all-in-one'));
+ if (!empty($export_result) && is_array($export_result)) {
+ // If we got an array back from the export build, tear that into pieces.
+ list($mime_type, $file_name, $serve_name) = $export_result;
+
+ // Now build the Drupal file object or update old one
+ if (!empty($file->fid) && !empty($file->filepath)) {
+ file_delete($file->filepath);
+ }
+ // Check / upate / create all file data
+ $file->status = FILE_STATUS_PERMANENT;
+ $file->timestamp = $file->checked = $timestamp;
+ $file->filename = $serve_name;
+ $file->filemime = $mime_type;
+ $file->filepath = file_create_path(variable_get('l10n_packager_directory', 'l10n_packager') . '/' . $serve_name);
+ file_move($file_name, $file->filepath, FILE_EXISTS_REPLACE);
+ $file->filesize = filesize($file->filepath);
+ // Create / update file record and link to release
+ drupal_write_record('files', $file, !empty($file->fid) ? 'fid' : array());
+ drupal_write_record('l10n_packager_file', $file, !empty($file->drid) ? 'drid' : array());
+ $variables['%filename'] = $file->filename;
+ watchdog('l10n_packager', 'Packaged release %release for language %language, created file %filename', $variables);
+ return $file;
+ }
+ else {
+ watchdog('l10n_packager', 'Failed packaging release %release for language %language', $variables);
+ return FALSE;
+ }
+}
+
+/**
+ * Get timestamp of the last updated string for a release, for each language
+ */
+function l10n_packager_translation_last_updated($rid) {
+ $updated = array();
+ $result = db_query("SELECT t.language, MAX(t.time_entered) AS entered, MAX(t.time_approved) AS approved FROM {l10n_community_translation} t INNER JOIN {l10n_community_line} l ON t.sid = l.sid INNER JOIN {l10n_community_file} f ON f.fid = l.fid WHERE t.is_active = 1 AND f.rid = %d GROUP BY t.language", $rid);
+ while ($latest = db_fetch_object($result)) {
+ $updated[$latest->language] = max($latest->entered, $latest->approved);
+ }
+ return $updated;
+}
+
+/**
+ * Get files for a release, indexed by language
+ */
+function l10n_packager_release_get_files($rid) {
+ $files = array();
+ $result = db_query('SELECT * FROM {l10n_packager_file} r LEFT JOIN {files} f ON r.fid = f.fid WHERE r.rid = %d', $rid);
+ while ($file = db_fetch_object($result)) {
+ $files[$file->language] = $file;
+ }
+ return $files;
+}
+
+/**
+ * Batch callback: repackage project
+ *
+ * @param $release
+ * Release id or release object
+ */
+function _l10n_packager_batch_repackage($rid, $langcode) {
+ if ($release = l10n_packager_get_release($rid)) {
+ $languages = l10n_community_get_languages();
+ $language = $languages[$langcode];
+ $updates = l10n_packager_release_check($release, TRUE, 0, $language);
+ if ($file = current($updates) ) {
+ drupal_set_message(t("Repackaged release %release for %language. Created file %filename.", array('%release' => l10n_packager_release_name($release), '%filename' => $file->filename, '%language' => $language->name)));
+ }
+ }
+}
+
+/**
+ * Create batch for repackaging all releases for a project
+ */
+function l10n_packager_project_batch($pid, $languages = NULL) {
+ $result = db_query("SELECT rid FROM {l10n_community_release} WHERE pid = %d", $pid);
+ while ($release = db_fetch_object($result)) {
+ $rids[] = $release->rid;
+ }
+ if (!empty($rids)) {
+ return l10n_packager_release_batch($rids, $languages);
+ }
+}
+
+/**
+ * Create batch for repackaging releases
+ *
+ * @param $rid
+ * Release id or array of release ids
+ * @param $languages
+ * Array of language codes to repackage or none
+ * @return unknown_type
+ */
+function l10n_packager_release_batch($rid, $languages = NULL) {
+ $rids = is_array($rid) ? $rid : array($rid);
+ // All languages if no languages passed
+ $languages = $languages ? $languages : array_keys(l10n_community_get_languages());
+ foreach ($rids as $rid) {
+ foreach ($languages as $lang) {
+ $operations[] = array('_l10n_packager_batch_repackage', array($rid, $lang));
+ }
+ }
+ if (!empty($operations)) {
+ return _l10n_packager_build_batch($operations, t('Repackaging translation files.'));
+ }
+ break;
+}
+
+
+/**
+ * Get batch stub
+ */
+function _l10n_packager_build_batch($operations = array(), $title = '') {
+ $batch = array(
+ 'title' => $title ? $title : t('Translations packager.'),
+ 'operations' => $operations,
+ // tell the batch engine which file to load before calling them.
+ 'file' => drupal_get_path('module', 'l10n_packager') .'/l10n_packager.inc',
+ );
+ return $batch;
+}
Index: l10n_packager/l10n_packager.admin.inc
===================================================================
RCS file: l10n_packager/l10n_packager.admin.inc
diff -N l10n_packager/l10n_packager.admin.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ l10n_packager/l10n_packager.admin.inc 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,171 @@
+ t('Directory for generated packages'),
+ '#description' => t('The directory on the local file system to store and download packages. Either relative to the Drupal site files or an absolute path on your file system. Drupal should have read and write access to the files and directories found there.'),
+ '#type' => 'textfield',
+ '#required' => TRUE,
+ '#default_value' => variable_get('l10n_packager_directory', 'l10n_packager'),
+ '#after_build' => array('l10n_packager_admin_check_directory'),
+ );
+ $form['l10n_packager_format'] = array(
+ '#title' => t('Package format'),
+ '#type' => 'select',
+ '#options' => array('drupal-6' => t('Drupal 6 package format'), 'drupal-5' => t('Drupal 5 package format for autolocale module'), 'flat-package' => t('Flat package for CVS commit'), 'all-in-one' => t('All in one file')),
+ '#default_value' => variable_get('l10n_packager_format', 'all-in-one'),
+ '#description' => t("Drupal 5's autolocale module and Drupal 6 use different conventions for directory naming in packages. Temporarily, as long as you need to use CVS to distribute translations the flat package helps you generate a package for committing to Drupal.org. Additionally, for testing, spell checking and smaller modules, you can also export in one file without packaging as well. It is not advised to use big .po files for importing in Drupal though, as it might lead to incomplete imports. Use packages for usage with Drupal importing."),
+ );
+ $form['l10n_packager_check'] = array(
+ '#title' => t('Number of releases to check at once'),
+ '#description' => t('The number of releases to check on a manual or cron run.'),
+ '#type' => 'select',
+ '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30, 40, 50)),
+ '#default_value' => variable_get('l10n_packager_check', 10),
+ );
+ $form['l10n_packager_file_limit'] = array(
+ '#title' => t('Maximum number of files to package at once'),
+ '#description' => t('The number of files to package on a manual or cron run.'),
+ '#type' => 'select',
+ '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30, 40, 50)),
+ '#default_value' => variable_get('l10n_packager_file_limit', 1),
+ );
+ // Logging settings
+ $period = array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+ $form['l10n_packager_update'] = array(
+ '#title' => t('Interval for repackaging'),
+ '#type' => 'select',
+ '#options' => $period,
+ '#default_value' => variable_get('l10n_packager_update', 0),
+ '#description' => t('Time interval for the translations to be automatically repackaged on cron.'),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Check / create directory
+ * @param unknown_type $form_element
+ * @return unknown_type
+ */
+function l10n_packager_admin_check_directory($form_element) {
+ file_check_directory(file_create_path($form_element['#value']), FILE_CREATE_DIRECTORY, $form_element['#parents'][0]);
+ return $form_element;
+}
+
+/**
+ * Menu callback. Repackage translations manually
+ */
+function l10n_packager_admin_repackage_form() {
+ $form['help']['#value'] = t('Repackage all translations or a single project or release.');
+ $form['project'] = array(
+ '#title' => t('Project'),
+ '#type' => 'textfield',
+ '#autocomplete_path' => 'translate/projects/autocomplete',
+ '#description' => t('Type a project URI. Example: drupal'),
+ );
+ $form['release'] = array(
+ '#title' => t('Release'),
+ '#type' => 'textfield',
+ //'#autocomplete_path' => 'translate/project_releases/autocomplete',
+ '#description' => t('Optionally select a release name like 6.x-1.0-beta1 or a partial release name like 6.x%.'),
+ );
+ $form['lang-wrapper'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Restrict languages'),
+ '#collapsible' => TRUE, '#collapsed' => TRUE,
+ '#description' => t('Select none for all languages. Otherwise check the languages you want repackaged.'),
+ );
+ $form['lang-wrapper']['languages'] = array(
+ '#type' => 'checkboxes',
+ '#default_value' => array(),
+ '#options' => l10n_community_get_languages('name'),
+ );
+
+ $form['buttons']['repackage'] = array('#type' => 'submit', '#value' => t('Repackage now'));
+ $form['buttons']['mark'] = array('#type' => 'submit', '#value' => t('Mark for repackaging'));
+ return $form;
+}
+
+/**
+ * Repackage form validation
+ */
+function l10n_packager_admin_repackage_form_validate($form, &$form_state) {
+ if (!empty($form_state['values']['project'])) {
+ if ($project = l10n_community_get_projects(array('uri' => $form_state['values']['project']))) {
+ $form_state['values']['pid'] = $project->pid;
+ }
+ else {
+ form_set_error('project', t('Invalid project name.'));
+ }
+ }
+ if (!empty($form_state['values']['release'])) {
+ $query = "SELECT COUNT(rid) FROM {l10n_community_release} WHERE title LIKE '%s'";
+ $args = array($form_state['values']['release']);
+ if ($project) {
+ $query .= " AND pid = %d";
+ $args[] = $project->pid;
+ }
+ if (!db_result(db_query($query, $args))) {
+ form_set_error('release', t('Invalid release name'));
+ }
+ }
+ if (empty($form_state['values']['project']) && empty($form_state['values']['release'])) {
+ form_set_error('', t('You must select some projects and/or releases.'));
+ }
+}
+
+/**
+ * Repackage form submission
+ */
+function l10n_packager_admin_repackage_form_submit($form, $form_state) {
+ module_load_include('inc', 'l10n_packager');
+
+ $op = $form_state['values']['op'];
+ $languages = array_filter($form_state['values']['languages']);
+ $pid = !empty($form_state['values']['pid']) ? $form_state['values']['pid'] : NULL;
+ $release = !empty($form_state['values']['release']) ? $form_state['values']['release'] : NULL;
+
+ // Prepare search parameters
+ $where = $args = array();
+ if ($pid) {
+ $where[] = "pid = %d";
+ $args[] = $pid;
+ }
+ if ($release) {
+ $where[] = "title LIKE '%s'";
+ $args[] = $release;
+ }
+ // Build the query that will be used in different ways depending on the operation
+ $query = "SELECT rid FROM {l10n_community_release} WHERE " . implode(' AND ', $where);
+
+ // Now check results and run operations
+ if ($op == t('Repackage now')) {
+ $result = db_query($query, $args);
+ while ($release = db_fetch_object($result)) {
+ $rids[] = $release->rid;
+ }
+ if (!empty($rids)) {
+ $batch = l10n_packager_release_batch($rids, $languages);
+ batch_set($batch);
+ }
+ else {
+ drupal_set_message(t('No releases found for repackaging.'), 'error');
+ }
+ }
+ elseif ($op == t('Mark for repackaging')) {
+ $args = array_merge(array(L10N_PACKAGER_ACTIVE), $args);
+ db_query("UPDATE {l10n_packager_release} SET updated = 0, checked = 0, package = %d WHERE rid IN ($query)", $args);
+ drupal_set_message(t("Marked %count releases for repackaging.", array('%count' => db_affected_rows())));
+ }
+}
+