? usage
Index: release/project-release-serve-history.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/release/project-release-serve-history.php,v
retrieving revision 1.3
diff -u -r1.3 project-release-serve-history.php
--- release/project-release-serve-history.php	18 Jul 2007 06:16:19 -0000	1.3
+++ release/project-release-serve-history.php	18 Jul 2007 22:44:27 -0000
@@ -78,11 +78,29 @@
 
 /**
  * @todo: Record usage statistics?
+ */
 if (isset($_GET['sitekey'])) {
-  if (isset($_GET['version'])) {
+  include_once './includes/bootstrap.inc';
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
+
+  // We can't call module_exists without bootstrapping to a higher level so 
+  // we'll settle on checking that the tables exist.
+  if (db_table_exists('project_usage_day')) {
+    $site_key = $_GET['sitekey'];
+    $project_version = isset($_GET['version']) ? $_GET['version'] : '';
+
+    // Compute a time stamp for the begining of the day.
+    $time_parts = getdate();
+    $timestamp = mktime(0, 0, 0, $time_parts['mon'], $time_parts['mday'], $time_parts['year']);
+
+    if (db_num_rows(db_query("SELECT * FROM {project_usage_day} WHERE project_uri = '%s' AND timestamp = %d AND site_key = '%s'", $project_name, $timestamp, $site_key))) { 
+      db_query("UPDATE {project_usage_day} SET api_version = '%s', project_version = '%s', pid = 0, nid = 0 WHERE project_uri = '%s' AND timestamp = %d AND site_key = '%s'", $api_version, $project_version, $project_name, $timestamp, $site_key);
+    }
+    else {
+      db_query("INSERT INTO {project_usage_day} (project_uri, timestamp, site_key, api_version, project_version) VALUES ('%s', %d, '%s', '%s', '%s')", $project_name, $timestamp, $site_key, $api_version, $project_version);
+    }
   }
 }
-*/
 
 
 /**
--- usage/project_usage.info
+++ usage/project_usage.info
@@ -0,0 +1,5 @@
+; $Id$
+name = Project usage
+description = "Provides data about installed usage of projects (requires update_status.module clients connecting to this site)."
+package = Project
+dependencies = project project_release

--- usage/project_usage.install
+++ usage/project_usage.install
@@ -0,0 +1,53 @@
+<?php
+// $Id$
+// $Name$
+
+function project_usage_install() {
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      db_query("CREATE TABLE IF NOT EXISTS {project_usage_day} (
+          project_uri varchar(50) NOT NULL default '',          
+          timestamp int unsigned NOT NULL default '0',
+          site_key varchar(32) NOT NULL default '',
+          api_version varchar(32) NOT NULL default '',
+          project_version varchar(255) NOT NULL default '',
+          pid int unsigned NOT NULL default '0',
+          nid int unsigned NOT NULL default '0',
+          PRIMARY KEY (project_uri, timestamp, site_key),
+          KEY (pid),
+          KEY (nid),
+          KEY (api_version)
+        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      db_query("CREATE TABLE IF NOT EXISTS {project_usage_week_project} (
+          nid int unsigned NOT NULL default '0',
+          timestamp int unsigned NOT NULL default '0',
+          count int unsigned NOT NULL default '0',
+          PRIMARY KEY (nid, timestamp)
+        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      db_query("CREATE TABLE IF NOT EXISTS {project_usage_week_release} (
+          nid int unsigned NOT NULL default '0',
+          timestamp int unsigned NOT NULL default '0',
+          count int unsigned NOT NULL default '0',
+          PRIMARY KEY (nid, timestamp)
+        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      break;
+  }
+}
+
+function project_usage_uninstall() {
+  $tables = array(
+    'project_usage_day',
+    'project_usage_week_project',
+    'project_usage_week_release',
+  );
+  foreach ($tables as $table) {
+    if (db_table_exists($table)) {
+      db_query("DROP TABLE {$table}");
+    }
+  }
+
+  variable_del('project_usage_last_daily');
+  variable_del('project_usage_last_weekly');
+  variable_del('project_usage_discard_after');
+}

--- usage/project_usage.module
+++ usage/project_usage.module
@@ -0,0 +1,196 @@
+<?php
+// $Id$
+// $Name$
+
+/**
+ * @file
+ *
+ * This module provides logging of the XML-RPC queries that are sent by the
+ * update_status.module (contrib in 5.x) and update.module (core in 6.x) to
+ * the project_release.module on updates.drupal.org. The 
+ * release/project-release-serve-history.php script inserted data into the 
+ * tables created by this module. 
+ * 
+ * This data is then used to compute live usage statistics about all projects 
+ * hosted on drupal.org. In theory, another site could setup
+ * update_status.module-style checking to their own project.module-based
+ * XML-RPC server, in which case, they might want to enable this module.
+ * Otherwise, sites should just leave this disabled.
+ */
+
+/**
+ * Implementation of hook_menu().
+ */
+function project_usage_menu($may_cache) {
+  $items = array();
+  if ($may_cache) {
+    $items[] = array(
+      'path' => 'admin/project/project-usage-settings',
+      'title' => t('Project usage settings'),
+      'callback' => 'drupal_get_form',
+      'callback arguments' => array('project_usage_settings_form'),
+      'description' => t('Configure how long data on usage is retained.'),
+      'weight' => 1,
+    );
+  }
+  return $items;
+}
+
+function project_usage_settings_form() {
+  $times = array(94608000, 63072000, 31536000, 15724800, 7257600, 4838400, 2419200);
+  $ageoptions = drupal_map_assoc($times, 'format_interval');
+  $form['project_usage_discard_after'] = array(
+    '#type' => 'select',
+    '#title' => t('Discard usage data after'),
+    '#default_value' => variable_get('project_usage_discard_after', 31536000),
+    '#options' => $ageoptions,
+    '#description' => t("Discard the usage data after this amount of time has passed. Compiled usage summary will not be deleted."),
+  );
+  return system_settings_form($form);
+}
+
+/**
+ * Implementation of hook_cron().
+ * 
+ * Call the daily and weekly processing tasks as needed.
+ */
+function project_usage_cron() {
+  $cur_time = time();
+
+  // Figure out if it's been 24 hours since our last daily processing.
+#  if (variable_get('project_usage_last_daily', 0) <= ($cur_time - (60 * 60 * 24))) {
+    project_usage_process_day();
+#    variable_set('project_usage_last_daily', $cur_time);
+#  }
+
+  // Figure out if we've processed the last weeks data.
+#  $week = date('Y.W', $cur_time);
+#  if (variable_get('project_usage_last_weekly', 0) <= $week) {
+    project_usage_process_week(time());
+#    variable_set('project_usage_last_weekly', $week);
+#  }
+}
+
+/**
+ * Clean up today's data.
+ */
+function project_usage_process_day() {
+  watchdog('project_usage', t('Starting to process daily usage data.'));
+
+  // Set any missing pid and nids.
+  $query = db_query("SELECT DISTINCT project_uri, project_version, api_version FROM {project_usage_day} WHERE pid = 0 OR nid = 0");
+  while ($row = db_fetch_object($query)) {
+    $release = db_fetch_object(db_query("SELECT prn.pid, prn.nid FROM project_projects pp INNER JOIN project_release_nodes prn ON pp.nid = prn.pid WHERE pp.uri = '%s' AND prn.version = '%s'", $row->project_uri, $row->project_version));
+    if ($release) {
+      db_query("UPDATE {project_usage_day} SET pid = %d, nid = %d WHERE project_uri = '%s' AND project_version = '%s'", $release->pid, $release->nid, $row->project_uri, $row->project_version);
+    }
+  }
+
+  watchdog('project_usage', t('Completed daily usage data processing.'));
+}
+
+/**
+ * Compute the weekly summaries for the previous week.
+ */
+function project_usage_process_week($timestamp) {
+  watchdog('project_usage', t('Starting to process weekly usage data.'));
+
+  // Remove old data. 
+  $since = time() - variable_get('project_usage_discard_after', 31536000); 
+  db_query("DELETE FROM {project_usage_day} WHERE timestamp < %d", $since);
+
+  // Determine the begining and end of the prior week...
+  $range = project_usage_get_week_range($timestamp, 1);
+  // ...remove any existing counts...
+  db_query("DELETE FROM {project_usage_week_project} WHERE timestamp = %d", $range['start']);
+  db_query("DELETE FROM {project_usage_week_release} WHERE timestamp = %d", $range['start']);
+  // ...then recompute the total counts.
+  db_query("INSERT INTO {project_usage_week_project} (nid, timestamp, count) SELECT pid, %d, COUNT(DISTINCT site_key) FROM project_usage_day WHERE pid <> 0 AND timestamp >= %d AND timestamp < %d GROUP BY pid", $range['start'], $range['start'], $range['end']);
+  db_query("INSERT INTO {project_usage_week_release} (nid, timestamp, count) SELECT nid, %d, COUNT(DISTINCT site_key) FROM project_usage_day WHERE nid <> 0 AND timestamp >= %d AND timestamp < %d GROUP BY nid", $range['start'], $range['start'], $range['end']);
+
+  watchdog('project_usage', t('Completed weekly usage data processing.'));
+}
+
+/**
+ * Compute the begining and end a week containing a given timestamp.
+ */
+function project_usage_get_week_range($timestamp, $weeks = 1) {
+  $parts = getdate($timestamp);
+  return array(
+    'start' => mktime(0, 0, 0, $parts['mon'], $parts['mday'] - $parts['wday'] - (7 * $weeks), $parts['year']),
+    'end' => mktime(0, 0, 0, $parts['mon'], $parts['mday'] - $parts['wday'], $parts['year']),
+  );
+}
+
+
+function project_usage_metrics_functions() {
+  return array(
+    '_project_usage_project_weekly_log',
+    '_project_usage_release_weekly_log',
+  );
+}
+
+
+function _project_usage_project_weekly_log($op, $options = NULL, $node = NULL) {
+  switch ($op) {
+    case 'info':
+      return array(
+        'name' => t('Project usage: Project node, weekly usage'),
+        'description' => t("Returns a score based on the number of sites using a project."),
+      );
+
+    case 'compute':
+      $weeks = isset($options['weeks']) ? $options['weeks'] : 1;
+      $range = project_usage_get_week_range(time(), $weeks);
+      $result = db_query("SELECT SUM(count) FROM project_usage_week_project WHERE nid = %d AND timestamp >= %d AND timestamp < %d", $node->nid, $range['start'], $range['end']);
+      if ($count = db_result($result)) {
+        return array(
+          'value' => log($result), 
+          'description' => t('This project is used on @count sites in the last @weeks.', array('@count' => $count, '@weeks' => $weeks)),
+        );
+      }
+      return NULL;
+
+    case 'options':
+      $weeks = drupal_map_assoc(array(1, 2, 3, 4, 6, 8, 10, 20, 26, 52));
+      $form['weeks'] = array(
+        '#type' => 'select',
+        '#title' => t('Weeks'),
+        '#default_value' => isset($options['weeks']) ? $options['weeks'] : 1,
+        '#options' => $weeks,
+      );
+      return $form;
+  }
+}
+
+function _project_usage_release_weekly_log($op, $options = NULL, $node = NULL) {
+  switch ($op) {
+    case 'info':
+      return array(
+        'name' => t('Project usage: Release node, weekly usage'),
+        'description' => t("Returns a score based on the number of sites using a release."),
+      );
+
+    case 'compute':
+      $weeks = isset($options['weeks']) ? $options['weeks'] : 1;
+      $range = project_usage_get_week_range(time(), $weeks);
+      $result = db_query("SELECT SUM(count) FROM project_usage_week_release WHERE nid = %d AND timestamp >= %d AND timestamp < %d", $node->nid, $range['start'], $range['end']);
+      if ($count = db_result($result)) {
+        return array(
+          'value' => log($count), 
+          'description' => t('This project is used on @count sites in the last @weeks weeks.', array('@count' => $count, '@weeks' => $weeks)),
+        );
+      }
+      return NULL;
+
+    case 'options':
+      $weeks = drupal_map_assoc(array(1, 2, 3, 4, 6, 8, 10, 20, 26, 52));
+      $form['weeks'] = array(
+        '#type' => 'select',
+        '#title' => t('Weeks'),
+        '#default_value' => isset($options['weeks']) ? $options['weeks'] : 1,
+        '#options' => $weeks,
+      );
+      return $form;
+  }
+}

