? .DS_Store
? .buildpath
? .cache
? .git
? .project
? .settings
? 30520-empty-upload-error-D7_0.patch
? 361514-file-save-upload-errors-D7.patch
? 361514-file-save-upload-errors-D7.patch.1
? before.sql
? empty
? file_353458.patch
? file_353458_1.patch
? junk.patch
? logs
? test.php
? user_357403_10.patch
? user_357403_5.patch
? user_load_multiple_9.patch
? modules/file
? profiles/default/translations
? sites/.DS_Store
? sites/all/.DS_Store
? sites/all/modules
? sites/default/.DS_Store
? sites/default/files
? sites/default/settings.php
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.153
diff -u -p -r1.153 file.inc
--- includes/file.inc	20 Jan 2009 02:56:04 -0000	1.153
+++ includes/file.inc	20 Jan 2009 21:04:12 -0000
@@ -349,6 +349,93 @@ function file_save($file) {
 }
 
 /**
+ * Determine where a file is used.
+ *
+ * @param $file
+ *   A file object.
+ * @return
+ *   An nested array of with usage data. The first level is keyed by module
+ *   name, the second by table name, the third has 'id' and 'count' keys.
+ *
+ * @see file_add_usage()
+ * @see file_remove_usage()
+ */
+function file_get_usage($file) {
+  $file = (object)$file;
+
+  $result = db_select('file_usage', 'f')->fields('f', array('module', 'type', 'id', 'count'))
+    ->condition('f.fid', $file->fid)->condition('f.count', 0, '>')
+    ->execute();
+
+  $return = array();
+  foreach ($result as $usage) {
+    $return[$usage->module][$usage->type] = array('id' => $usage->id, 'count' => $usage->count);
+  }
+
+  return $return;
+}
+
+/**
+ * Inform Drupal that a module is using a file.
+ *
+ * This usage information will be queried during file_delete().
+ *
+ * Examples:
+ * - The upload module that associates files with node revisions so the
+ *   $type would be 'node_revision' and $id would be the node's vid.
+ * - The user module associates an image with a user so the $type would be
+ *   'user' and the $id would be the user's id.
+ *
+ * @param $file
+ *   A file object.
+ * @param $module
+ *   The name of the module using the file.
+ * @param $type
+ *   The name of the table where the file is referenced.
+ * @param $id
+ *   The id of the row in the table.
+ *
+ * @see file_get_usage()
+ * @see file_remove_usage()
+ */
+function file_add_usage($file, $module, $type, $id) {
+  db_merge('file_usage')
+    ->key(array(
+      'fid' => $file->fid,
+      'module' => $module,
+      'type' => $type,
+      'id' => $id,
+    ))
+    ->fields(array('count' => 1))
+    ->expression('count', 'count + 1')
+    ->execute();
+}
+
+/**
+ * Inform Drupal that a module is no longer using a file.
+ *
+ * @param $file
+ *   A file object.
+ * @param $module
+ *   The name of the module using the file.
+ * @param $type
+ *   The name of the table where the file is referenced.
+ * @param $id
+ *   The id of the row in the table.
+ *
+ * @see file_add_usage()
+ * @see file_get_usage()
+ */
+function file_remove_usage($file, $module, $type, $id) {
+  db_update('file_usage')->expression('count', 'count - 1')
+    ->condition('fid', $file->fid)
+    ->condition('module', $module)
+    ->condition('type', $type)
+    ->condition('id', $id)
+    ->execute();
+}
+
+/**
  * Copy a file to a new location and adds a file record to the database.
  *
  * This function should be used when manipulating files that have records
@@ -728,9 +815,9 @@ function file_create_filename($basename,
 /**
  * Delete a file and its database record.
  *
- * If the $force parameter is not TRUE hook_file_references() will be called
- * to determine if the file is being used by any modules. If the file is being
- * used is the delete will be canceled.
+ * If the $force parameter is FALSE then file_get_usage() will be called to
+ * determine if the file is being used by any modules. If the file is in use
+ * the file will not be deleted and the usage data will be returned.
  *
  * @param $file
  *   A file object.
@@ -738,22 +825,24 @@ function file_create_filename($basename,
  *   Boolean indicating that the file should be deleted even if
  *   hook_file_references() reports that the file is in use.
  * @return mixed
- *   TRUE for success, FALSE in the event of an error, or an array if the file
- *   is being used by another module. The array keys are the module's name and
- *   the values are the number of references.
+ *   TRUE for success, FALSE in the event of an error, or if the file is being
+ *   and $force is FALSE an array of usage data from file_get_usage().
  *
  * @see file_unmanaged_delete()
- * @see hook_file_references()
+ * @see file_get_usage()
  * @see hook_file_delete()
  */
 function file_delete($file, $force = FALSE) {
   $file = (object)$file;
 
-  // If any module returns a value from the reference hook, the file will not
-  // be deleted from Drupal, but file_delete will return a populated array that
-  // tests as TRUE.
-  if (!$force && ($references = module_invoke_all('file_references', $file))) {
-    return $references;
+  // If file is still in use and $force is FALSE, the file will not be deleted
+  // from Drupal, but file_delete will return a populated array that tests as
+  // TRUE.
+  if (!$force) {
+    $references = file_get_usage($file);
+    if (count($references)) {
+      return $references;
+    }
   }
 
   // Let other modules clean up any references to the deleted file.
@@ -763,6 +852,7 @@ function file_delete($file, $force = FAL
   // database, so UIs can still find the file in the database.
   if (file_unmanaged_delete($file->filepath)) {
     db_delete('files')->condition('fid', $file->fid)->execute();
+    db_delete('file_usage')->condition('fid', $file->fid)->execute();
     return TRUE;
   }
   return FALSE;
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.21
diff -u -p -r1.21 file.test
--- modules/simpletest/tests/file.test	20 Jan 2009 02:56:05 -0000	1.21
+++ modules/simpletest/tests/file.test	20 Jan 2009 21:04:12 -0000
@@ -1023,18 +1023,38 @@ class FileDeleteTest extends FileHookTes
   /**
    * Try deleting a normal file (as opposed to a directory, symlink, etc).
    */
-  function testNormal() {
+  function testUnused() {
     $file = $this->createFile();
 
     // Check that deletion removes the file and database record.
     $this->assertTrue(is_file($file->filepath), t("File exists."));
     $this->assertIdentical(file_delete($file), TRUE, t("Delete worked."));
-    $this->assertFileHooksCalled(array('references', 'delete'));
+    $this->assertFileHooksCalled(array('delete'));
     $this->assertFalse(file_exists($file->filepath), t("Test file has actually been deleted."));
     $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
+    $this->assertFalse(file_get_usage($file), t('File usage data was removed.'));
+  }
+
+  /**
+   * Try deleting a file that is in use.
+   */
+  function testInUse() {
+    $file = $this->createFile();
+    file_add_usage($file, 'testing', 'test', 1);
+
+    $usage = file_delete($file, FALSE);
+    $this->assertEqual($usage['testing']['test'], array('id' => 1, 'count' => 1), t("Delete failed and returned usage data."));
+    $this->assertFileHooksCalled(array());
+
+    $this->assertTrue(file_exists($file->filepath), t("Test file still exists on the disk."));
+    $this->assertTrue(file_load($file->fid), t('File still exists in the database.'));
+    $this->assertTrue(file_get_usage($file), t('File usage data still exists.'));
 
-    // TODO: implement hook_file_references() in file_test.module and report a
-    // file in use and test the $force parameter.
+    $this->assertIdentical(file_delete($file, TRUE), TRUE, t("Delete worked."));
+    $this->assertFileHooksCalled(array('delete'));
+    $this->assertFalse(file_exists($file->filepath), t("Test file has actually been deleted."));
+    $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
+    $this->assertFalse(file_get_usage($file), t('File usage data was removed.'));
   }
 }
 
@@ -1498,6 +1518,91 @@ class FileSaveTest extends FileHookTestC
   }
 }
 
+/**
+ * Tests the file_get_usage(), file_add_usage() and file_remove_usage()
+ * functions.
+ */
+class FileUsageTest extends FileTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('File usage'),
+      'description' => t('Tests the file usage functions.'),
+      'group' => t('File'),
+    );
+  }
+
+  /**
+   * Test the file_get_usage() function.
+   */
+  function testGetUsage() {
+    $file = $this->createFile();
+    db_insert('file_usage')->fields(array(
+      'fid' => $file->fid,
+      'module' => 'testing',
+      'type' => 'foo',
+      'id' => 1,
+      'count' => 1
+    ))->execute();
+    db_insert('file_usage')->fields(array(
+      'fid' => $file->fid,
+      'module' => 'testing',
+      'type' => 'bar',
+      'id' => 2,
+      'count' => 2
+    ))->execute();
+
+    $usage = file_get_usage($file);
+
+    $this->assertEqual(count($usage['testing']), 2, t('Returned the correct number of items.'));
+    $this->assertEqual($usage['testing']['foo']['id'], 1, t('Returned the correct id.'));
+    $this->assertEqual($usage['testing']['bar']['id'], 2, t('Returned the correct id.'));
+    $this->assertEqual($usage['testing']['foo']['count'], 1, t('Returned the correct count.'));
+    $this->assertEqual($usage['testing']['bar']['count'], 2, t('Returned the correct count.'));
+  }
+
+  /**
+   * Test the file_add_usage() function.
+   */
+  function testAddUsage() {
+    $file = $this->createFile();
+    file_add_usage($file, 'testing', 'foo', 1);
+    // Add the file twice to ensure that the count is incremented rather than
+    // creating additional records.
+    file_add_usage($file, 'testing', 'bar', 2);
+    file_add_usage($file, 'testing', 'bar', 2);
+
+    $usage = db_select('file_usage', 'f')->fields('f')->condition('f.fid', $file->fid)->execute()->fetchAllAssoc('id');
+    $this->assertEqual(count($usage), 2, t('Created two records'));
+    $this->assertEqual($usage[1]->module, 'testing', t('Correct module'));
+    $this->assertEqual($usage[2]->module, 'testing', t('Correct module'));
+    $this->assertEqual($usage[1]->type, 'foo', t('Correct type'));
+    $this->assertEqual($usage[2]->type, 'bar', t('Correct type'));
+    $this->assertEqual($usage[1]->count, 1, t('Correct count'));
+    $this->assertEqual($usage[2]->count, 2, t('Correct count'));
+  }
+
+  /**
+   * Test the file_remove_usage() function.
+   */
+  function testRemoveUsage() {
+    $file = $this->createFile();
+    db_insert('file_usage')->fields(array(
+      'fid' => $file->fid,
+      'module' => 'testing',
+      'type' => 'bar',
+      'id' => 2,
+      'count' => 2
+    ))->execute();
+
+    file_remove_usage($file, 'testing', 'bar', 2);
+    $count = db_select('file_usage', 'f')->fields('f', array('count'))->condition('f.fid', $file->fid)->execute()->fetchField();
+    $this->assertEqual(1, $count, t('The count was decremented correctly.'));
+
+    file_remove_usage($file, 'testing', 'bar', 2);
+    $count = db_select('file_usage', 'f')->fields('f', array('count'))->condition('f.fid', $file->fid)->execute()->fetchField();
+    $this->assertEqual(0, $count, t('The count was decremented correctly.'));
+  }
+}
 
 /**
  * Tests the file_validate() function..
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.8
diff -u -p -r1.8 file_test.module
--- modules/simpletest/tests/file_test.module	20 Jan 2009 02:56:05 -0000	1.8
+++ modules/simpletest/tests/file_test.module	20 Jan 2009 21:04:12 -0000
@@ -78,7 +78,6 @@ function file_test_reset() {
     'load' => array(),
     'validate' => array(),
     'download' => array(),
-    'references' => array(),
     'insert' => array(),
     'update' => array(),
     'copy' => array(),
@@ -91,7 +90,6 @@ function file_test_reset() {
   $return = array(
     'validate' => array(),
     'download' => NULL,
-    'references' => NULL,
   );
   variable_set('file_test_return', $return);
 }
@@ -102,7 +100,7 @@ function file_test_reset() {
  *
  * @param $op
  *   One of the hook_file_* operations: 'load', 'validate', 'download',
- *   'references', 'insert', 'update', 'copy', 'move', 'delete'.
+ *   'insert', 'update', 'copy', 'move', 'delete'.
  * @returns
  *   Array of the parameters passed to each call.
  * @see _file_test_log_call() and file_test_reset()
@@ -129,7 +127,7 @@ function file_test_get_all_calls() {
  *
  * @param $op
  *   One of the hook_file_* operations: 'load', 'validate', 'download',
- *   'references', 'insert', 'update', 'copy', 'move', 'delete'.
+ *   'insert', 'update', 'copy', 'move', 'delete'.
  * @param $args
  *   Values passed to hook.
  * @see file_test_get_calls() and file_test_reset()
@@ -144,7 +142,7 @@ function _file_test_log_call($op, $args)
  * Load the appropriate return value.
  *
  * @param $op
- *   One of the hook_file_[validate,download,references] operations.
+ *   One of the hook_file_[validate,download] operations.
  * @return
  *   Value set by file_test_set_return().
 * @see file_test_set_return() and file_test_reset().
@@ -158,7 +156,7 @@ function _file_test_get_return($op) {
  * Assign a return value for a given operation.
  *
  * @param $op
- *   One of the hook_file_[validate,download,references] operations.
+ *   One of the hook_file_[validate,download] operations.
  * @param $value
  *   Value for the hook to return.
  * @see _file_test_get_return() and file_test_reset().
@@ -198,14 +196,6 @@ function file_test_file_download($file) 
 }
 
 /**
- * Implementation of hook_file_references().
- */
-function file_test_file_references($file) {
-  _file_test_log_call('references', array($file));
-  return _file_test_get_return('references');
-}
-
-/**
  * Implementation of hook_file_insert().
  */
 function file_test_file_insert($file) {
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.13
diff -u -p -r1.13 system.api.php
--- modules/system/system.api.php	17 Jan 2009 20:28:46 -0000	1.13
+++ modules/system/system.api.php	20 Jan 2009 21:04:12 -0000
@@ -1098,31 +1098,6 @@ function hook_file_move($file, $source) 
 }
 
 /**
- * Report the number of times a file is referenced by a module.
- *
- * This hook is called to determine if a files is in use. Multiple modules may
- * be referencing the same file and to prevent one from deleting a file used by
- * another this hook is called.
- *
- * @param $file
- *   The file object being checked for references.
- * @return
- *   If the module uses this file return an array with the module name as the
- *   key and the value the number of times the file is used.
- *
- * @see file_delete()
- * @see upload_file_references()
- */
-function hook_file_references($file) {
-  // If upload.module is still using a file, do not let other modules delete it.
-  $count = db_query('SELECT COUNT(*) FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid))->fetchField();
-  if ($count) {
-    // Return the name of the module and how many references it has to the file.
-    return array('upload' => $count);
-  }
-}
-
-/**
  * Respond to a file being deleted.
  *
  * @param $file
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.300
diff -u -p -r1.300 system.install
--- modules/system/system.install	19 Jan 2009 10:46:51 -0000	1.300
+++ modules/system/system.install	20 Jan 2009 21:04:13 -0000
@@ -293,7 +293,7 @@ function system_install() {
   if (db_driver() == 'pgsql') {
     // We create some functions using global names instead of prefixing them
     // like we do with table names. If this function is ever called again (for
-    // example, by the test framework when creating prefixed test databases), 
+    // example, by the test framework when creating prefixed test databases),
     // the global names will already exist. We therefore avoid trying to create
     // them again in that case.
 
@@ -667,6 +667,50 @@ function system_schema() {
     'primary key' => array('fid'),
   );
 
+  $schema['file_usage'] = array(
+    'description' => 'Track where a file is used.',
+    'fields' => array(
+      'fid' => array(
+        'description' => 'File ID.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'module' => array(
+        'description' => 'The name of the module that is using the file.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'type' => array(
+        'description' => 'The name of the table where the file is used.',
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'id' => array(
+        'description' => 'The primary key of the object using the file.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'count' => array(
+        'description' => 'The number of times this file is used by this object.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('fid', 'type', 'id', 'module'),
+    'indexes' => array(
+      'type_id' => array('type', 'id'),
+    ),
+  );
+
   $schema['flood'] = array(
     'description' => 'Flood controls the threshold of events, such as the number of contact attempts.',
     'fields' => array(
@@ -3140,11 +3184,11 @@ function system_update_7016() {
   $ret = array();
   // Only run these queries if the driver used is pgsql.
   if (db_driver() == 'pgsql') {
-    $result = db_query("SELECT c.relname AS table, a.attname AS field, 
-                        pg_catalog.format_type(a.atttypid, a.atttypmod) AS type 
-                        FROM pg_catalog.pg_attribute a 
-                        LEFT JOIN pg_class c ON (c.oid =  a.attrelid) 
-                        WHERE pg_catalog.pg_table_is_visible(c.oid) AND c.relkind = 'r' 
+    $result = db_query("SELECT c.relname AS table, a.attname AS field,
+                        pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
+                        FROM pg_catalog.pg_attribute a
+                        LEFT JOIN pg_class c ON (c.oid =  a.attrelid)
+                        WHERE pg_catalog.pg_table_is_visible(c.oid) AND c.relkind = 'r'
                         AND pg_catalog.format_type(a.atttypid, a.atttypmod) LIKE '%unsigned%'");
     while ($row = db_fetch_object($result)) {
       switch ($row->type) {
@@ -3157,7 +3201,7 @@ function system_update_7016() {
           $datatype = 'bigint';
           break;
       }
-      $ret[] = update_sql('ALTER TABLE ' . $row->table . ' ALTER COLUMN ' . $row->field . ' TYPE ' . $datatype); 
+      $ret[] = update_sql('ALTER TABLE ' . $row->table . ' ALTER COLUMN ' . $row->field . ' TYPE ' . $datatype);
       $ret[] = update_sql('ALTER TABLE ' . $row->table . ' ADD CHECK (' . $row->field . ' >= 0)');
     }
     $ret[] = update_sql('DROP DOMAIN smallint_unsigned');
@@ -3197,6 +3241,59 @@ function system_update_7017() {
 }
 
 /**
+ * Create the file_usage table.
+ */
+function system_update_7018() {
+  $schema['file_usage'] = array(
+    'description' => 'Track where a file is used.',
+    'fields' => array(
+      'fid' => array(
+        'description' => 'File ID.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'module' => array(
+        'description' => 'The name of the module that is using the file.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'type' => array(
+        'description' => 'The name of the table where the file is used.',
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'id' => array(
+        'description' => 'The primary key of the object using the file.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'count' => array(
+        'description' => 'The number of times this file is used by this object.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('fid', 'type', 'id', 'module'),
+    'indexes' => array(
+      'type_id' => array('type', 'id'),
+    ),
+  );
+
+  $ret = array();
+  db_create_table($ret, 'file_usage', $schema['file_usage']);
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/upload/upload.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.install,v
retrieving revision 1.8
diff -u -p -r1.8 upload.install
--- modules/upload/upload.install	15 Nov 2008 13:01:11 -0000	1.8
+++ modules/upload/upload.install	20 Jan 2009 21:04:13 -0000
@@ -82,4 +82,11 @@ function upload_schema() {
   return $schema;
 }
 
-
+/**
+ * Create file_usage records for our files.
+ */
+function upload_update_7000() {
+  $ret = array();
+  $ret[] = update_sql("INSERT INTO {file_usage} (fid, module, type, id, count) SELECT fid, 'upload', 'node_revision', vid, 1 FROM {upload}");
+  return $ret;
+}
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.225
diff -u -p -r1.225 upload.module
--- modules/upload/upload.module	12 Jan 2009 08:29:09 -0000	1.225
+++ modules/upload/upload.module	20 Jan 2009 21:04:13 -0000
@@ -280,18 +280,6 @@ function upload_file_load($files) {
 }
 
 /**
- * Implementation of hook_file_references().
- */
-function upload_file_references(&$file) {
-  // If upload.module is still using a file, do not let other modules delete it.
-  $count = db_query('SELECT COUNT(*) FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid))->fetchField();
-  if ($count) {
-    // Return the name of the module and how many references it has to the file.
-    return array('upload' => $count);
-  }
-}
-
-/**
  * Implementation of hook_file_delete().
  */
 function upload_file_delete(&$file) {
@@ -494,6 +482,7 @@ function upload_save(&$node) {
     if (!empty($file->remove)) {
       // Remove the reference from this revision.
       db_delete('upload')->condition('fid', $file->fid)->condition('vid', $node->vid)->execute();
+      file_remove_usage($file, 'upload', 'node_revision', $node->vid);
       // Try a soft delete, if the file isn't used elsewhere it'll be deleted.
       file_delete($file);
       // Remove it from the session in the case of new uploads,
@@ -530,6 +519,8 @@ function upload_save(&$node) {
     }
     $file->status |= FILE_STATUS_PERMANENT;
     $file = file_save($file);
+
+    file_add_usage($file, 'upload', 'node_revision', $node->vid);
   }
 }
 
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.956
diff -u -p -r1.956 user.module
--- modules/user/user.module	20 Jan 2009 03:10:00 -0000	1.956
+++ modules/user/user.module	20 Jan 2009 21:04:13 -0000
@@ -274,6 +274,7 @@ function user_save($account, $edit = arr
         if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) {
           $picture->status |= FILE_STATUS_PERMANENT;
           $edit['picture'] = file_save($picture);
+          file_add_usage($file, 'user', 'user', $account->uid);
         }
       }
     }
@@ -292,6 +293,7 @@ function user_save($account, $edit = arr
     // to occur after updating the {users} record so that user_file_references()
     // doesn't report it in use and block the deletion.
     if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) {
+      file_remove_usage($file, 'user', 'user', $account->uid);
       file_delete($account->picture);
     }
 
