Index: modules/image/image.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.module,v
retrieving revision 1.43
diff -u -r1.43 image.module
--- modules/image/image.module	31 May 2010 11:42:11 -0000	1.43
+++ modules/image/image.module	3 Jul 2010 05:53:17 -0000
@@ -307,14 +307,6 @@
 }
 
 /**
- * Implements hook_file_references().
- */
-function image_file_references($file) {
-  $count = file_get_file_reference_count($file, NULL, 'image');
-  return $count ? array('image' => $count) : NULL;
-}
-
-/**
  * Implements hook_image_default_styles().
  */
 function image_image_default_styles() {
Index: modules/image/image.field.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.field.inc,v
retrieving revision 1.21
diff -u -r1.21 image.field.inc
--- modules/image/image.field.inc	30 Apr 2010 12:53:47 -0000	1.21
+++ modules/image/image.field.inc	3 Jul 2010 05:53:16 -0000
@@ -233,6 +233,13 @@
 }
 
 /**
+ * Implements hook_field_insert().
+ */
+function image_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  file_field_insert($entity_type, $entity, $field, $instance, $langcode, $items);
+}
+
+/**
  * Implements hook_field_update().
  */
 function image_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.70
diff -u -r1.70 node.api.php
--- modules/node/node.api.php	17 Jun 2010 13:44:45 -0000	1.70
+++ modules/node/node.api.php	3 Jul 2010 05:53:17 -0000
@@ -406,13 +406,9 @@
  * @ingroup node_api_hooks
  */
 function hook_node_revision_delete($node) {
-  db_delete('upload')->condition('vid', $node->vid)->execute();
-  if (!is_array($node->files)) {
-    return;
-  }
-  foreach ($node->files as $file) {
-    file_delete($file);
-  }
+  db_delete('mytable')
+    ->condition('vid', $node->vid)
+    ->execute();
 }
 
 /**
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.58
diff -u -r1.58 file.test
--- modules/simpletest/tests/file.test	30 Jun 2010 22:37:49 -0000	1.58
+++ modules/simpletest/tests/file.test	3 Jul 2010 05:53:18 -0000
@@ -230,13 +230,13 @@
       $this->assertTrue(FALSE, t('Expected hooks %expected to be called but %uncalled was not called.', array('%expected' => implode(', ', $expected), '%uncalled' => implode(', ', $uncalled))));
     }
     else {
-      $this->assertTrue(TRUE, t('All the expected hooks were called: %expected', array('%expected' => implode(', ', $expected))));
+      $this->assertTrue(TRUE, t('All the expected hooks were called: %expected', array('%expected' => empty($expected) ? t('(none)') : implode(', ', $expected))));
     }
 
     // Determine if there were any unexpected calls.
     $unexpected = array_diff($actual, $expected);
     if (count($unexpected)) {
-      $this->assertTrue(FALSE, t('Unexpected hooks were called: %unexpected.', array('%unexpected' => implode(', ', $unexpected))));
+      $this->assertTrue(FALSE, t('Unexpected hooks were called: %unexpected.', array('%unexpected' => empty($unexpected) ? t('(none)') : implode(', ', $unexpected))));
     }
     else {
       $this->assertTrue(TRUE, t('No unexpected hooks were called.'));
@@ -1402,18 +1402,42 @@
   /**
    * 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->uri), t("File exists."));
-    $this->assertIdentical(file_delete($file), TRUE, t("Delete worked."));
-    $this->assertFileHooksCalled(array('references', 'delete'));
-    $this->assertFalse(file_exists($file->uri), t("Test file has actually been deleted."));
+    $this->assertTrue(is_file($file->uri), t('File exists.'));
+    $this->assertIdentical(file_delete($file), TRUE, t('Delete worked.'));
+    $this->assertFileHooksCalled(array('delete'));
+    $this->assertFalse(file_exists($file->uri), t('Test file has actually been deleted.'));
     $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
+  }
+
+  /**
+   * Try deleting a file that is in use.
+   */
+  function testInUse() {
+    $file = $this->createFile();
+    file_usage_add($file, 'testing', 'test', 1);
+    file_usage_add($file, 'testing', 'test', 1);
+
+    file_usage_delete($file, 'testing', 'test', 1);
+    file_delete($file);
+    $usage = file_usage_list($file);
+    $this->assertEqual($usage['testing']['test'], array('id' => 1, 'count' => 1), t('Test file is still in use.'));
+    $this->assertTrue(file_exists($file->uri), t('File still exists on the disk.'));
+    $this->assertTrue(file_load($file->fid), t('File still exists in the database.'));
 
-    // TODO: implement hook_file_references() in file_test.module and report a
-    // file in use and test the $force parameter.
+    // Clear out the call to hook_file_load().
+    file_test_reset();
+
+    file_usage_delete($file, 'testing', 'test', 1);
+    file_delete($file);
+    $usage = file_usage_list($file);
+    $this->assertFileHooksCalled(array('delete'));
+    $this->assertTrue(empty($usage), t('File usage data was removed.'));
+    $this->assertFalse(file_exists($file->uri), t('File has been deleted after its last usage was removed.'));
+    $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
   }
 }
 
@@ -1515,7 +1539,7 @@
     $this->assertTrue($result, t('File moved sucessfully.'));
 
     // Check that the correct hooks were called.
-    $this->assertFileHooksCalled(array('move', 'update', 'delete', 'references', 'load'));
+    $this->assertFileHooksCalled(array('move', 'update', 'delete', 'load'));
 
     // Reload the file from the database and check that the changes were
     // actually saved.
@@ -1864,6 +1888,109 @@
   }
 }
 
+/**
+ * Tests file usage functions.
+ */
+class FileUsageTest extends FileTestCase {
+  function getInfo() {
+    return array(
+      'name' => 'File usage',
+      'description' => 'Tests the file usage functions.',
+      'group' => 'File',
+    );
+  }
+
+  /**
+   * Test file_usage_list().
+   */
+  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_usage_list($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 file_usage_add().
+   */
+  function testAddUsage() {
+    $file = $this->createFile();
+    file_usage_add($file, 'testing', 'foo', 1);
+    // Add the file twice to ensure that the count is incremented rather than
+    // creating additional records.
+    file_usage_add($file, 'testing', 'bar', 2);
+    file_usage_add($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 file_usage_delete().
+   */
+  function testRemoveUsage() {
+    $file = $this->createFile();
+    db_insert('file_usage')
+      ->fields(array(
+        'fid' => $file->fid,
+        'module' => 'testing',
+        'type' => 'bar',
+        'id' => 2,
+        'count' => 2
+      ))
+      ->execute();
+
+    file_usage_delete($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_usage_delete($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.23
diff -u -r1.23 file_test.module
--- modules/simpletest/tests/file_test.module	26 Jun 2010 19:55:47 -0000	1.23
+++ modules/simpletest/tests/file_test.module	3 Jul 2010 05:53:18 -0000
@@ -143,7 +143,6 @@
     'load' => array(),
     'validate' => array(),
     'download' => array(),
-    'references' => array(),
     'insert' => array(),
     'update' => array(),
     'copy' => array(),
@@ -156,7 +155,6 @@
   $return = array(
     'validate' => array(),
     'download' => NULL,
-    'references' => NULL,
   );
   variable_set('file_test_return', $return);
 }
@@ -167,7 +165,7 @@
  *
  * @param $op
  *   One of the hook_file_* operations: 'load', 'validate', 'download',
- *   'references', 'insert', 'update', 'copy', 'move', 'delete'.
+ *   'insert', 'update', 'copy', 'move', 'delete'.
  *
  * @return
  *   Array of the parameters passed to each call.
@@ -184,9 +182,9 @@
  * Get an array with the calls for all hooks.
  *
  * @return
- *   An array keyed by hook name ('load', 'validate', 'download',
- *   'references', 'insert', 'update', 'copy', 'move', 'delete') with values
- *   being arrays of parameters passed to each call.
+ *   An array keyed by hook name ('load', 'validate', 'download', 'insert',
+ *   'update', 'copy', 'move', 'delete') with values being arrays of parameters
+ *   passed to each call.
  */
 function file_test_get_all_calls() {
   return variable_get('file_test_results', array());
@@ -197,7 +195,7 @@
  *
  * @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.
  *
@@ -214,7 +212,7 @@
  * 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().
@@ -231,7 +229,7 @@
  * 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.
  *
@@ -273,14 +271,6 @@
 }
 
 /**
- * Implements hook_file_references().
- */
-function file_test_file_references($file) {
-  _file_test_log_call('references', array($file));
-  return _file_test_get_return('references');
-}
-
-/**
  * Implements hook_file_insert().
  */
 function file_test_file_insert($file) {
Index: modules/file/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/tests/file.test,v
retrieving revision 1.18
diff -u -r1.18 file.test
--- modules/file/tests/file.test	2 Jul 2010 12:37:57 -0000	1.18
+++ modules/file/tests/file.test	3 Jul 2010 05:53:15 -0000
@@ -170,7 +170,7 @@
    * Assert that a file exists in the database.
    */
   function assertFileEntryExists($file, $message = NULL) {
-    drupal_static_reset('file_load_multiple');
+    entity_get_controller('file')->resetCache();
     $db_file = file_load($file->fid);
     $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri));
     $this->assertEqual($db_file->uri, $file->uri, $message);
@@ -188,7 +188,7 @@
    * Assert that a file does not exist in the database.
    */
   function assertFileEntryNotExists($file, $message) {
-    drupal_static_reset('file_load_multiple');
+    entity_get_controller('file')->resetCache();
     $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri));
     $this->assertFalse(file_load($file->fid), $message);
   }
@@ -368,14 +368,17 @@
 
     // Attach the second file to a user.
     $user = $this->drupalCreateUser();
-    $edit = array();
+    $edit = (array) $user;
     $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3;
     user_save($user, $edit);
     $this->drupalGet('user/' . $user->uid . '/edit');
 
     // Delete the third revision and check that the file is not deleted yet.
+    debug(db_select('file_usage', 'f')->fields('f')->condition('fid', $node_file_r3->fid)->execute()->fetchAllAssoc('fid'));
+
     $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete'));
     $this->assertFileExists($node_file_r3, t('Second file is still available after deleting third revision, since it is being used by the user.'));
+    debug(db_select('file_usage', 'f')->fields('f')->condition('fid', $node_file_r3->fid)->execute()->fetchAllAssoc('fid'));
     $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting third revision, since it is being used by the user.'));
     $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting third revision, since it is being used by the user.'));
 
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1179
diff -u -r1.1179 user.module
--- modules/user/user.module	29 Jun 2010 18:24:10 -0000	1.1179
+++ modules/user/user.module	3 Jul 2010 05:53:24 -0000
@@ -439,12 +439,26 @@
           file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
           $destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-' . $account->uid . '.' . $info['extension']);
 
+          // Delete the previous picture. This ensures thumbnails get cleared.
+          // Note that we force this deletion, since the physical file is about
+          // to be replaced by the new file.
+          if ($account->picture) {
+            file_delete($account->picture, TRUE);
+          }
+
+          // Move the temporary file into the final location.
           if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) {
             $picture->status |= FILE_STATUS_PERMANENT;
             $edit['picture'] = file_save($picture);
+            file_usage_add($picture, 'user', 'user', $account->uid);
           }
         }
       }
+      // If the picture existed before and was unset, remove its reference.
+      elseif (!empty($account->picture->fid)) {
+        file_delete($account->picture, TRUE);
+      }
+
       $edit['picture'] = empty($edit['picture']->fid) ? 0 : $edit['picture']->fid;
 
       // Do not allow 'uid' to be changed.
@@ -457,13 +471,6 @@
         return FALSE;
       }
 
-      // If the picture changed or was unset, remove the old one. This step needs
-      // 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_delete($account->picture);
-      }
-
       // Reload user roles if provided.
       if (isset($edit['roles']) && is_array($edit['roles'])) {
         db_delete('users_roles')
@@ -829,15 +836,18 @@
 }
 
 /**
- * Implements hook_file_references().
+ * Implements hook_file_move().
  */
-function user_file_references($file) {
-  // Determine if the file is used by this module.
-  $file_used = (bool) db_query_range('SELECT 1 FROM {users} WHERE picture = :fid', 0, 1, array(':fid' => $file->fid))->fetchField();
-  if ($file_used) {
-    // Return the name of the module and how many references it has to the file.
-    // If file is still used then 1 is enough to indicate this.
-    return array('user' => 1);
+function user_file_move($file, $source) {
+  // If a user's picture is replaced with a new one, update the record in
+  // the users table.
+  if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
+    db_update('users')
+      ->fields(array(
+        'picture' => $file->fid,
+      ))
+      ->condition('picture', $source->fid)
+      ->execute();
   }
 }
 
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.31
diff -u -r1.31 file.module
--- modules/file/file.module	2 Jul 2010 12:37:57 -0000	1.31
+++ modules/file/file.module	3 Jul 2010 05:53:15 -0000
@@ -321,14 +321,6 @@
 }
 
 /**
- * Implements hook_file_references().
- */
-function file_file_references($file) {
-  $count = file_get_file_reference_count($file, NULL, 'file');
-  return $count ? array('file' => $count) : NULL;
-}
-
-/**
  * Implements hook_file_delete().
  */
 function file_file_delete($file) {
@@ -520,11 +512,8 @@
   if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) {
     if ($file = file_load($element['fid']['#value'])) {
       if ($file->status == FILE_STATUS_PERMANENT) {
-        $reference_count = 0;
-        foreach (module_invoke_all('file_references', $file) as $module => $references) {
-          $reference_count += $references;
-        }
-        if ($reference_count == 0) {
+        $references = file_usage_list($file);
+        if (empty($references)) {
           form_error($element, t('Referencing to the file used in the !name field is not allowed.', array('!name' => $element['#title'])));
         }
       }
@@ -929,72 +918,6 @@
  */
 
 /**
- * Count the number of times the file is referenced.
- *
- * @param $file
- *   A file object.
- * @param $field
- *   (optional) A CCK field array or field name as a string. If provided,
- *   limits the reference check to the given field.
- * @param $field_type
- *   (optional) The name of a field type. If provided, limits the reference
- *   check to fields of the given type.
- * @return
- *   An integer value.
- */
-function file_get_file_reference_count($file, $field = NULL, $field_type = NULL) {
-  // Determine the collection of fields to check.
-  if (isset($field)) {
-    // Support $field as 'field name'.
-    if (is_string($field)) {
-      $field = field_info_field($field);
-    }
-    $fields = array($field['field_name'] => $field);
-  }
-  else {
-    $fields = field_info_fields();
-  }
-
-  $types = entity_get_info();
-  $reference_count = 0;
-
-  foreach ($fields as $field) {
-    if (empty($field_type) || $field['type'] == $field_type) {
-      // TODO: Use a more efficient mechanism rather than actually retrieving
-      // all the references themselves, such as using a COUNT() query.
-      $references = file_get_file_references($file, $field, FIELD_LOAD_REVISION, $field_type);
-      foreach ($references as $entity_type => $type_references) {
-        $reference_count += count($type_references);
-      }
-
-      // If a field_name is present in the file object, the file is being deleted
-      // from this field.
-      if (isset($file->file_field_name) && $field['field_name'] == $file->file_field_name) {
-        // If deleting the entire piece of content, decrement references.
-        if (isset($file->file_field_type) && isset($file->file_field_id)) {
-          if ($file->file_field_type == $entity_type) {
-            $info = entity_get_info($entity_type);
-            $id = $types[$entity_type]['entity keys']['id'];
-            foreach ($type_references as $reference) {
-              if ($file->file_field_id == $reference->$id) {
-                $reference_count--;
-              }
-            }
-          }
-        }
-        // Otherwise we're just deleting a single reference in this field.
-        else {
-          $reference_count--;
-        }
-      }
-    }
-  }
-
-  return $reference_count;
-}
-
-
-/**
  * Get a list of references to a file.
  *
  * @param $file
@@ -1007,8 +930,8 @@
  *   FIELD_LOAD_REVISION to retrieve all references within all revisions or
  *   FIELD_LOAD_CURRENT to retrieve references only in the current revisions.
  * @param $field_type
- *   Optional. The name of a field type. If given, limits the reference check to
- *   fields of the given type.
+ *   (optional) The name of a field type. If given, limits the reference check
+ *   to fields of the given type.
  * @return
  *   An integer value.
  */
Index: modules/file/file.field.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.field.inc,v
retrieving revision 1.28
diff -u -r1.28 file.field.inc
--- modules/file/file.field.inc	2 Jul 2010 12:37:57 -0000	1.28
+++ modules/file/file.field.inc	3 Jul 2010 05:53:15 -0000
@@ -263,37 +263,71 @@
 }
 
 /**
+ * Implements hook_field_insert().
+ */
+function file_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+  // Add a new usage of each uploaded file.
+  foreach ($items as $item) {
+    $file = (object) $item;
+    file_usage_add($file, 'file', $entity_type, $id);
+  }
+}
+
+/**
  * Implements hook_field_update().
  *
  * Check for files that have been removed from the object.
  */
 function file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
-  // On new revisions, old files are always maintained in the previous revision.
+  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+  // On new revisions, all files are considered to be a new usage and no
+  // deletion of previous file usages are necessary.
   if (!empty($entity->revision)) {
+    foreach ($items as $item) {
+      $file = (object) $item;
+      file_usage_add($file, 'file', $entity_type, $id);
+    }
     return;
   }
 
   // Build a display of the current FIDs.
-  $fids = array();
+  $current_fids = array();
   foreach ($items as $item) {
-    $fids[] = $item['fid'];
+    $current_fids[] = $item['fid'];
   }
 
-  // Get the current values in the entity, and delete files for removed items.
-  list($id) = entity_extract_ids($entity_type, $entity);
-  $original = clone $entity;
+  // Create a bare-bones entity so that we can load its previous values.
+  $entity_info = entity_get_info($entity_type);
+  $original = new stdClass();
+  foreach ($entity_info['entity keys'] as $key) {
+    if (isset($key) && isset($entity->{$key})) {
+      $original->{$key} = $entity->{$key};
+    }
+  }
   field_attach_load($entity_type, array($id => $original), FIELD_LOAD_CURRENT, array('field_id' => $field['id']));
 
+  // Compare the original field values with the ones that are being saved.
+  $original_fids = array();
   if (!empty($original->{$field['field_name']}[$langcode])) {
     foreach ($original->{$field['field_name']}[$langcode] as $original_item) {
-      if (isset($original_item['fid']) && !in_array($original_item['fid'], $fids)) {
-        // For hook_file_references, remember that this is being deleted.
-        $original_item['file_field_name'] = $field['field_name'];
-        // Delete the file if possible.
-        file_field_delete_file($original_item, $field);
+      $original_fids[] = $original_item['fid'];
+      if (isset($original_item['fid']) && !in_array($original_item['fid'], $current_fids)) {
+        // Decrement the file usage count by 1 and delete the file if possible.
+        file_field_delete_file($original_item, $field, $entity_type, $id);
       }
     }
   }
+
+  // Add new usage entries for newly added files.
+  foreach ($items as $item) {
+    if (!in_array($item['fid'], $original_fids)) {
+      $file = (object) $item;
+      file_usage_add($file, 'file', $entity_type, $id);
+    }
+  }
 }
 
 /**
@@ -301,14 +335,10 @@
  */
 function file_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
   list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+  // Delete all file usages within this entity.
   foreach ($items as $delta => $item) {
-    // For hook_file_references(), remember that this is being deleted.
-    $item['file_field_name'] = $field['field_name'];
-    // Pass in the ID of the object that is being removed so all references can
-    // be counted in hook_file_references().
-    $item['file_field_type'] = $entity_type;
-    $item['file_field_id'] = $id;
-    file_field_delete_file($item, $field);
+    file_field_delete_file($item, $field, $entity_type, $id, 0);
   }
 }
 
@@ -316,32 +346,29 @@
  * Implements hook_field_delete_revision().
  */
 function file_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
   foreach ($items as $delta => $item) {
-    // For hook_file_references, remember that this file is being deleted.
-    $item['file_field_name'] = $field['field_name'];
-    if (file_field_delete_file($item, $field)) {
+    // Decrement the file usage count by 1 and delete the file if possible.
+    if (file_field_delete_file($item, $field, $entity_type, $id)) {
       $items[$delta] = NULL;
     }
   }
 }
 
 /**
- * Check that File controls a file before attempting to delete it.
+ * Decrement a file usage count and attempt to delete it.
+ *
+ * This function only decrements a file usage count and attempts to delete the
+ * file only if the file is controlled by File module.
  */
-function file_field_delete_file($item, $field) {
-  // Remove the file_field_name and file_field_id properties so that references
-  // can be counted including the files to be deleted.
-  $field_name = isset($item['file_field_name']) ? $item['file_field_name'] : NULL;
-  $field_id = isset($item['file_field_id']) ? $item['file_field_id'] : NULL;
-  unset($item['file_field_name'], $item['file_field_id']);
-
+function file_field_delete_file($item, $field, $entity_type, $id, $count = 1) {
   // To prevent the file field from deleting files it doesn't know about, check
   // the file reference count. Temporary files can be deleted because they
   // are not yet associated with any content at all.
   $file = (object) $item;
-  if ($file->status == 0 || file_get_file_reference_count($file, $field) > 0) {
-    $file->file_field_name = $field_name;
-    $file->file_field_id = $field_id;
+  $file_usage = file_usage_list($file);
+  if ($file->status == 0 || !empty($file_usage['file'])) {
+    file_usage_delete($file, 'file', $entity_type, $id, $count);
     return file_delete($file);
   }
 
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.944
diff -u -r1.944 system.module
--- modules/system/system.module	30 Jun 2010 15:51:51 -0000	1.944
+++ modules/system/system.module	3 Jul 2010 05:53:22 -0000
@@ -2799,8 +2799,14 @@
   ));
   foreach ($result as $row) {
     if ($file = file_load($row->fid)) {
-      if (!file_delete($file)) {
-        watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->uri), WATCHDOG_ERROR);
+      $references = file_usage_list($file);
+      if (empty($references)) {
+        if (!file_delete($file)) {
+          watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->uri), WATCHDOG_ERROR);
+        }
+      }
+      else {
+        watchdog('file system', 'Could not delete temporary file "%path" during garbage collection, because it is still in use.', array('%path' => $file->uri), WATCHDOG_ERROR);
       }
     }
   }
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.485
diff -u -r1.485 system.install
--- modules/system/system.install	2 Jul 2010 15:31:10 -0000	1.485
+++ modules/system/system.install	3 Jul 2010 05:53:21 -0000
@@ -832,6 +832,50 @@
     ),
   );
 
+  $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 object type in which 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(
@@ -2583,6 +2627,56 @@
 }
 
 /**
+ * Create the file_usage table.
+ */
+function system_update_7059() {
+  $spec = 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 object type in which 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'),
+    ),
+  );
+  db_create_table('file_usage', $spec);
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.176
diff -u -r1.176 system.api.php
--- modules/system/system.api.php	1 Jul 2010 00:46:57 -0000	1.176
+++ modules/system/system.api.php	3 Jul 2010 05:53:20 -0000
@@ -2303,31 +2303,6 @@
 }
 
 /**
- * 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 user.module is still using a file, do not let other modules delete it.
-  $file_used = (bool) db_query_range('SELECT 1 FROM {user} WHERE pictire = :fid', 0, 1, array(':fid' => $file->fid))->fetchField();
-  if ($file_used) {
-    // Return the name of the module and how many references it has to the file.
-    return array('user' => 1);
-  }
-}
-
-/**
  * Respond to a file being deleted.
  *
  * @param $file
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.217
diff -u -r1.217 file.inc
--- includes/file.inc	1 Jul 2010 00:44:04 -0000	1.217
+++ includes/file.inc	3 Jul 2010 05:53:14 -0000
@@ -531,6 +531,120 @@
 }
 
 /**
+ * Determine where a file is used.
+ *
+ * @param $file
+ *   A file object.
+ * @return
+ *   An nested array 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_usage_add()
+ * @see file_usage_delete()
+ */
+function file_usage_list(stdClass $file) {
+  $result = db_query('SELECT f.module, f.type, f.id, f.count FROM {file_usage} f WHERE f.fid = :fid AND count > 0', array(':fid' => $file->fid));
+  $references = array();
+  foreach ($result as $usage) {
+    $references[$usage->module][$usage->type] = array('id' => $usage->id, 'count' => $usage->count);
+  }
+  return $references;
+}
+
+/**
+ * Record that a module is using a file.
+ *
+ * This usage information will be queried during file_delete().
+ *
+ * Examples:
+ * - A module that associates files with node revisions, so $type would be
+ *   'node' and $id would be the node's vid.
+ * - The User module associates an image with a user, so $type would be 'users'
+ *   and the $id would be the user's uid.
+ *
+ * @param $file
+ *   A file object.
+ * @param $module
+ *   The name of the module using the file.
+ * @param $type
+ *   The name of the object where the file is referenced.
+ * @param $id
+ *   The id of the object containing the file reference.
+ * @param $count
+ *   The number of references to add to the object. Defaults to 1.
+ *
+ * @see file_usage_list()
+ * @see file_usage_delete()
+ */
+function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) {
+  db_merge('file_usage')
+    ->key(array(
+      'fid' => $file->fid,
+      'module' => $module,
+      'type' => $type,
+      'id' => $id,
+    ))
+    ->fields(array('count' => $count))
+    ->expression('count', 'count + ' . intval($count))
+    ->execute();
+}
+
+/**
+ * Remove a record to indicate that a module is no longer using a file.
+ *
+ * If no usage records for a file remain in the database, the file will be
+ * deleted using file_delete().
+ *
+ * @param $file
+ *   A file object.
+ * @param $module
+ *   The name of the module using the file.
+ * @param $object_name
+ *   The name of the object that contains the referenced file.
+ * @param $id
+ *   The unique, numeric ID of the object containing the referenced file.
+ * @param $count
+ *   The number of references to delete from the object. Defaults to 1. 0 may
+ *   be specified to delete all references to the file within a specific object.
+ * @return
+ *   TRUE for success, or FALSE if no usages remain but the file still could
+ *   not be deleted.
+ *
+ * @see file_usage_add()
+ * @see file_usage_list()
+ * @see file_delete()
+ */
+function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $count = 1) {
+  // Delete rows that have an exact count, since we don't want 0 value rows.
+  $query = db_delete('file_usage')
+    ->condition('module', $module)
+    ->condition('fid', $file->fid);
+  if ($type && $id) {
+    $query->condition('type', $type);
+    $query->condition('id', $id);
+  }
+  if ($count) {
+    $query->condition('count', $count);
+  }
+  $result = $query->execute();
+
+  // If the row has more than the specified count decrement it by that number.
+  if ($file && !$result) {
+    $query = db_update('file_usage')
+      ->condition('module', $module)
+      ->condition('fid', $file->fid);
+    if ($type && $id) {
+      $query->condition('type', $type);
+      $query->condition('id', $id);
+    }
+    if ($count) {
+      $query->expression('count', 'count - ' . intval($count));
+    }
+    $query->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
@@ -949,29 +1063,30 @@
 /**
  * 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 not TRUE, file_usage_list() will be called to
+ * determine if the file is being used by any modules. If it is, the delete will
+ * be canceled.
  *
  * @param $file
  *   A file object.
  * @param $force
- *   Boolean indicating that the file should be deleted even if
- *   hook_file_references() reports that the file is in use.
- * @return mixed
+ *   Boolean indicating that the file should be deleted even if the file is
+ *   reported as in use by the file_usage table.
+ *
+ * @return
  *   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.
+ *   is being used by another module.
  *
  * @see file_unmanaged_delete()
- * @see hook_file_references()
+ * @see file_usage_list()
+ * @see file_usage_delete()
  * @see hook_file_delete()
  */
 function file_delete(stdClass $file, $force = FALSE) {
   // 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))) {
+  // be deleted from Drupal, but file_delete() will return a populated array
+  // that tests as TRUE.
+  if (!$force && ($references = file_usage_list($file))) {
     return $references;
   }
 
@@ -982,6 +1097,7 @@
   // database, so UIs can still find the file in the database.
   if (file_unmanaged_delete($file->uri)) {
     db_delete('file_managed')->condition('fid', $file->fid)->execute();
+    db_delete('file_usage')->condition('fid', $file->fid)->execute();
     return TRUE;
   }
   return FALSE;
