Index: l10n_community.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.admin.inc,v
retrieving revision 1.1.2.2
diff -u -p -r1.1.2.2 l10n_community.admin.inc
--- l10n_community.admin.inc	15 Oct 2008 23:56:38 -0000	1.1.2.2
+++ l10n_community.admin.inc	25 Oct 2008 21:11:19 -0000
@@ -35,6 +35,14 @@ function l10n_community_settings_form() 
     '#options' => drupal_map_assoc(array(4, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40)),
     '#default_value' => variable_get('l10n_community_project_per_page', 10),
   );
+  $form['l10n_community_stats_cache_lifetime'] = array(
+    '#type' => 'select',
+    '#title' => t('Statistics cache lifetime'),
+    '#default_value' => variable_get('l10n_community_stats_cache_lifetime', 60),
+    '#options' => drupal_map_assoc(array(60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval'),
+    '#description' => t('Statistics at Translate interface screen are cached for performance reasons. Statistics cache lifetime is the minimum amount of time that will elapse before the cache is emptied and recreated. A larger minimum cache lifetime offers better performance but users will not see new statistics for a longer period of time. Websites with huge translations database should set this number high enough.'),
+  );
+
   return system_settings_form($form);
 }
 
Index: l10n_community.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.module,v
retrieving revision 1.1.2.23.2.7
diff -u -p -r1.1.2.23.2.7 l10n_community.module
--- l10n_community.module	24 Oct 2008 18:04:28 -0000	1.1.2.23.2.7
+++ l10n_community.module	25 Oct 2008 21:11:20 -0000
@@ -1022,8 +1022,8 @@ function l10n_community_format_text($str
 function l10n_community_get_stats($langcode = NULL) {
   if (!empty($langcode)) {
     // Compute based on langcode.
-    if ($stats = cache_get('l10n:stats:'. $langcode, 'cache')) {
-      return unserialize($stats->data);
+    if ($stats = cache_get('l10n:stats:overall:'. $langcode, 'cache')) {
+      return $stats->data;
     }
     else {
       $stats = array();
@@ -1034,14 +1034,14 @@ function l10n_community_get_stats($langc
       
       // Cache results for next time. Not setting a timestamp as cache validity
       // time, we would like to retain control of recalculating these values.
-      cache_set('l10n:stats:'. $langcode, 'cache', serialize($stats), CACHE_PERMANENT);
+      cache_set('l10n:stats:overall:'. $langcode, $stats, 'cache', CACHE_PERMANENT);
       return $stats;
     }
   }
   else {
     // General community statistics.
-    if ($stats = cache_get('l10n:stats', 'cache')) {
-      return unserialize($stats->data);
+    if ($stats = cache_get('l10n:stats:overall:all', 'cache')) {
+      return $stats->data;
     }
     else {
       $stats = array();
@@ -1054,7 +1054,7 @@ function l10n_community_get_stats($langc
 
       // Cache results for next time. Not setting a timestamp as cache validity
       // time, we would like to retain control of recalculating these values.
-      cache_set('l10n:stats', 'cache', serialize($stats), CACHE_PERMANENT);
+      cache_set('l10n:stats:overall:all', $stats, 'cache', CACHE_PERMANENT);
       return $stats;
     }
   }
@@ -1063,12 +1063,39 @@ function l10n_community_get_stats($langc
 /**
  * Implementation of hook_cron().
  *
- * Clear project and language stats every hour.
+ * Clear project and language stats every defined interval. Recalculate
+ * statistics.
+ *
  */
 function l10n_community_cron() {
   $lastrun = variable_get('l10n_cron_stats', 1);
-  if (($_SERVER['REQUEST_TIME'] - $lastrun) > 3600) {
-    cache_clear_all('l10n:stats', 'cache', TRUE);
+  if (($_SERVER['REQUEST_TIME'] - $lastrun) > variable_get('l10n_community_stats_cache_lifetime', 60)) {
+    // If not in safe mode, increase time limit. Recalculating stats can take a
+    // serious amount of time but it's still better to recalculate it here than
+    // let some users wait because they are the victims of our cache needs.
+    if (!ini_get('safe_mode')) {
+      set_time_limit(1000);
+    }
+
+    // Hit the cache so users doesn't have to wait for it.
+    // Quick stats and Progress status.
+    cache_clear_all('l10n:stats:overall', 'cache', TRUE);
+    l10n_community_get_stats();
+
+    // Statistics of Projects/Languages and their translated strings count.
+    include_once drupal_get_path('module', 'l10n_community') .'/pages.inc';
+
+    // First calculate statistics, then clear the cache and set cache again to
+    // minimize time when any user may hit a page without computed statistics.
+    $language_sums = l10n_community_get_string_count_languages();
+    cache_clear_all('l10n:stats:languages', 'cache', TRUE);
+    cache_set('l10n:stats:languages', $language_sums, 'cache', CACHE_PERMANENT);
+
+    $projects_sums = l10n_community_get_string_count_projects();
+    print_r($projects_sums);
+    cache_clear_all('l10n:stats:projects', 'cache', TRUE);
+    cache_set('l10n:stats:projects', $projects_sums, 'cache', CACHE_PERMANENT);
+
     variable_set('l10n_cron_stats', $_SERVER['REQUEST_TIME']);
   }
 }
Index: pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/pages.inc,v
retrieving revision 1.1.2.20.2.2
diff -u -p -r1.1.2.20.2.2 pages.inc
--- pages.inc	24 Oct 2008 18:04:28 -0000	1.1.2.20.2.2
+++ pages.inc	25 Oct 2008 21:11:21 -0000
@@ -400,6 +400,8 @@ function theme_l10n_community_table($hea
 
 /**
  * Get string counts for summaries.
+ * The most expensive queries for projects and languages are cached into Drupal
+ * cache table. This cache is cleared and possibly renewed using cron.
  *
  * @param $type
  *   Type of string count to return:
@@ -416,10 +418,6 @@ function theme_l10n_community_table($hea
  *   id represents the identifier (pid or langcode) of the restricting item.
  *   For the 'all' type, this value is discarded.
  *
- * @todo
- *   These queries are *slooow*. The query cache helps a lot with caching the
- *   result, so the slowness only shows for the first run, but still it would
- *   be good to look into optimizing these.
  */
 function l10n_community_get_string_count($type, $id = NULL) {
   switch ($type) {
@@ -432,53 +430,23 @@ function l10n_community_get_string_count
       return db_result(db_query('SELECT COUNT(DISTINCT s.sid) FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid WHERE r.pid = %d', $id));
     
     case 'languages':
-      // Summeries based on language codes, restricted to a specific project if $id is set.
-      $sums = array();
-      if (!isset($id)) {
-        // Simple count query if we are not filtering by project.
-        $count_sql = "SELECT COUNT(DISTINCT t.sid) translation_count, t.language, t.is_suggestion FROM {l10n_community_string} s LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE t.is_active = 1 AND t.translation != '' GROUP BY t.language, t.is_suggestion";
-      }
-      else {
-        // Rather complex join if we also need to factor the project in.
-        $count_sql = "SELECT COUNT(DISTINCT t.sid) translation_count, t.language, t.is_suggestion FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE r.pid = %d AND t.is_active = 1 AND t.translation != '' GROUP BY t.language, t.is_suggestion";
-      }
-      $result = db_query($count_sql, $id);
-      while ($row = db_fetch_object($result)) {
-        if (!isset($sums[$row->language])) {
-          // Set default zeroes for summaries.
-          $sums[$row->language] = array(0, 0);
-        }
-        // Fill in the 0 or 1 element based on whether we have a result
-        // for suggestions or translations.
-        $sums[$row->language][(int) $row->is_suggestion] = $row->translation_count;
+      if ($stats = cache_get('l10n:stats:languages', 'cache')) {
+        return $stats->data;
       }
+      $sums = l10n_community_get_string_count_languages();
+      cache_set('l10n:stats:languages', $sums, 'cache', CACHE_PERMANENT);
       return $sums;
       break;
     
     case 'projects':
       // Get summaries by projects. Restricted to a specific language, if $id is set.
-      
-      // First get the count of strings available for translation.
-      $sums = $count_args = array();
-      $result = db_query("SELECT COUNT(DISTINCT s.sid) string_count, p.pid, p.title, p.uri FROM {l10n_community_project} p INNER JOIN {l10n_community_release} r ON p.pid = r.pid INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid GROUP BY p.pid;");
-      while ($row = db_fetch_object($result)) {
-        // Initialize remaining fields to zeroes too.
-        $sums[$row->pid] = array($row->title, $row->uri, $row->string_count, 0, 0);
-      }
-      // Get the count of distinct strings translated and suggestions per project.
-      $count_sql = "SELECT COUNT(DISTINCT t.sid) translation_count, r.pid, t.is_suggestion FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE t.is_active = 1 AND t.translation != '' ";
-      if (isset($id)) {
-        // Limit to language if desired.
-        $count_sql .= "AND t.language = '%s' ";
-        $count_args[] = $id;
-      }
-      $count_sql .= 'GROUP BY r.pid, t.is_suggestion ';
-      $result = db_query($count_sql, $count_args);
-      while ($row = db_fetch_object($result)) {
-        // Fill up the zero spots we added above with real data.
-        $sums[$row->pid][((int) $row->is_suggestion) + 3] = $row->translation_count;
+      if ($stats = cache_get('l10n:stats:projects'. $id, 'cache')) {
+        return $stats->data;
       }
+      $sums = l10n_community_get_string_count_projects($id);
+      cache_set('l10n:stats:projects'. $id, $sums, 'cache', CACHE_PERMANENT);
       return $sums;
+      break;
 
     case 'top-people':
       // Get summaries of people having most active translations per language.
@@ -490,3 +458,64 @@ function l10n_community_get_string_count
       return $accounts;
   }
 }
+
+
+/*
+ * Calculate statistics for all projects, limited to language if $id is present.
+ *
+ * @param $id
+ *   Language to limit
+ */
+function l10n_community_get_string_count_projects($id = NULL) {
+  // Get summaries by projects. Restricted to a specific language, if $id is set.
+  // First get the count of strings available for translation.
+  $sums = $count_args = array();
+  $result = db_query("SELECT COUNT(DISTINCT s.sid) string_count, p.pid, p.title, p.uri FROM {l10n_community_project} p INNER JOIN {l10n_community_release} r ON p.pid = r.pid INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid GROUP BY p.pid;");
+  while ($row = db_fetch_object($result)) {
+    // Initialize remaining fields to zeroes too.
+    $sums[$row->pid] = array($row->title, $row->uri, $row->string_count, 0, 0);
+  }
+
+  // Get the count of distinct strings translated and suggestions per project.
+  $count_sql = "SELECT COUNT(DISTINCT t.sid) translation_count, r.pid, t.is_suggestion FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE t.is_active = 1 AND t.translation != '' ";
+  if (isset($id) && !empty($id)) {
+    // Limit to language if desired.
+    $count_sql .= "AND t.language = '%s' ";
+    $count_args[] = $id;
+  }
+  $count_sql .= 'GROUP BY r.pid, t.is_suggestion ';
+  $result = db_query($count_sql, $count_args);
+  while ($row = db_fetch_object($result)) {
+    // Fill up the zero spots we added above with real data.
+    $sums[$row->pid][((int) $row->is_suggestion) + 3] = $row->translation_count;
+  }
+  return $sums;
+}
+
+/*
+ * Calculate statistics for all languages.
+ */
+function l10n_community_get_string_count_languages() {
+  // Summaries based on language codes, restricted to a specific project if $id is set.
+  $sums = array();
+  if (!isset($id)) {
+    // Simple count query if we are not filtering by project.
+    $count_sql = "SELECT COUNT(DISTINCT t.sid) translation_count, t.language, t.is_suggestion FROM {l10n_community_string} s LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE t.is_active = 1 AND t.translation != '' GROUP BY t.language, t.is_suggestion";
+  }
+  else {
+    // Rather complex join if we also need to factor the project in.
+    $count_sql = "SELECT COUNT(DISTINCT t.sid) translation_count, t.language, t.is_suggestion FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE r.pid = %d AND t.is_active = 1 AND t.translation != '' GROUP BY t.language, t.is_suggestion";
+  }
+  $result = db_query($count_sql, $id);
+  while ($row = db_fetch_object($result)) {
+    if (!isset($sums[$row->language])) {
+      // Set default zeroes for summaries.
+      $sums[$row->language] = array(0, 0);
+    }
+    // Fill in the 0 or 1 element based on whether we have a result
+    // for suggestions or translations.
+    $sums[$row->language][(int) $row->is_suggestion] = $row->translation_count;
+  }
+  return $sums;
+}
+
