diff --git a/file_entity.file.inc b/file_entity.file.inc
index 1d4703d..bc9ff7c 100644
--- a/file_entity.file.inc
+++ b/file_entity.file.inc
@@ -34,7 +34,9 @@ function file_entity_file_insert($file) {
   cache_clear_all();
 
   // Get and store image dimensions.
-  file_entity_image_dimensions($file, TRUE);
+  if ($file->type == 'image') {
+    file_entity_image_dimensions($file, TRUE);
+  }
 }
 
 /**
@@ -50,8 +52,21 @@ function file_entity_file_update($file) {
   // Clear the page and block caches.
   cache_clear_all();
 
-  // Get and store image dimensions.
-  file_entity_image_dimensions($file, TRUE);
+  if ($file->type == 'image') {
+    // Get and store image dimensions.
+    file_entity_image_dimensions($file, TRUE);
+
+    if (module_exists('image')) {
+      // If the image dimensions have changed, update any image field references
+      // to this file and flush image style derivatives.
+      if ($file->image_dimensions != $file->original->image_dimensions) {
+        _file_entity_update_image_field_dimensions($file);
+      }
+
+      // Flush image style derivatives whenever an image is updated.
+      image_path_flush($file->uri);
+    }
+  }
 }
 
 /**
@@ -194,3 +209,45 @@ function file_entity_image_dimensions($file, $force = FALSE) {
     );
   }
 }
+
+/**
+ * Update the image dimensions stored in any image fields for a file.
+ *
+ * @param object $file
+ *   A file object that is an image.
+ *
+ * @see http://drupal.org/node/1448124
+ */
+function _file_entity_update_image_field_dimensions($file) {
+  // Find all image field enabled on the site.
+  $image_fields = array();
+  foreach (field_info_fields() as $field) {
+    if ($field['type'] == 'image') {
+      $image_fields[] = $field['field_name'];
+    }
+  }
+
+  foreach ($image_fields as $image_field) {
+    $query = new EntityFieldQuery();
+    $query->fieldCondition($image_field, 'fid', $file->fid);
+    $results = $query->execute();
+
+    foreach ($results as $entity_type => $entities) {
+      $entities = entity_load($entity_type, array_keys($entities));
+      foreach ($entities as $entity) {
+        foreach ($entity->{$image_field} as $langcode => $items) {
+          foreach ($items as $delta => $item) {
+            if ($item['fid'] == $file->fid) {
+              $entity->{$image_field}[$langcode][$delta]['width'] = $file->image_dimensions['width'];
+              $entity->{$image_field}[$langcode][$delta]['height'] = $file->image_dimensions['height'];
+            }
+          }
+        }
+
+        // Ensure that file_field_update() will not trigger additional usage.
+        unset($entity->revision);
+        field_attach_update($entity_type, $entity);
+      }
+    }
+  }
+}
diff --git a/file_entity.module b/file_entity.module
index d6fef2d..bcc61fa 100644
--- a/file_entity.module
+++ b/file_entity.module
@@ -906,3 +906,18 @@ function file_entity_file_is_local($file) {
   $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
   return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
 }
+
+/**
+ * Pre-render callback for adding validation descriptions to managed file elements.
+ */
+function file_entity_upload_validators_pre_render($element) {
+  if (!empty($element['#upload_validators'])) {
+    if (!isset($element['#description'])) {
+      $element['#description'] = '';
+    }
+    if ($element['#description'] !== FALSE) {
+      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
+    }
+  }
+  return $element;
+}
diff --git a/file_entity.pages.inc b/file_entity.pages.inc
index 8281afc..8bd169e 100644
--- a/file_entity.pages.inc
+++ b/file_entity.pages.inc
@@ -36,43 +36,14 @@ function file_entity_view_page($file) {
  * @todo: should use the AJAX uploader
  */
 function file_entity_add_upload($form, &$form_state, array $options = array()) {
-  // Set up file upload validators.
-  $validators = array();
-
-  // Validate file extensions. If there are no file extensions in $params and
-  // there are no Media defaults, there is no file extension validation.
-  if (!empty($options['file_extensions'])) {
-    $validators['file_validate_extensions'] = array($options['file_extensions']);
-  }
-  else {
-    $validators['file_validate_extensions'] = array(FILE_DEFAULT_ALLOWED_EXTENSIONS);
-  }
-
-  // Validate file size but do not allow anything higher than file_upload_max_size().
-  $max_filesize = file_upload_max_size();
-  if (!empty($options['max_filesize']) && $options['max_filesize'] < $max_filesize) {
-    $validators['file_validate_size'] = array(parse_size($options['max_filesize']));
-  }
-  else {
-    $validators['file_validate_size'] = array($max_filesize);
-  }
-
-  // Add image validators.
-  $options += array('min_resolution' => 0, 'max_resolution' => 0);
-  if ($options['min_resolution'] || $options['max_resolution']) {
-    $validators['file_validate_image_resolution'] = array($options['max_resolution'], $options['min_resolution']);
-  }
-
-  $form['#validators'] = $validators;
-
   $form['upload'] = array(
     '#type' => 'managed_file',
     '#title' => t('Upload a new file'),
     '#upload_location' => file_entity_upload_destination_uri($options),
-    '#upload_validators' => $validators,
+    '#upload_validators' => file_entity_get_upload_validators($options),
     '#progress_indicator' => 'bar',
     '#required' => TRUE,
-    '#pre_render' => array('file_managed_file_pre_render', 'file_entity_managed_file_pre_render'),
+    '#pre_render' => array('file_managed_file_pre_render', 'file_entity_upload_validators_pre_render'),
   );
 
   $form['actions'] = array('#type' => 'actions');
@@ -87,21 +58,6 @@ function file_entity_add_upload($form, &$form_state, array $options = array()) {
 }
 
 /**
- * Pre-render callback for adding validation descriptions to managed file elements.
- */
-function file_entity_managed_file_pre_render($element) {
-  if (!empty($element['#upload_validators'])) {
-    if (!isset($element['#description'])) {
-      $element['#description'] = '';
-    }
-    if ($element['#description'] !== FALSE) {
-      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
-    }
-  }
-  return $element;
-}
-
-/**
  * Upload a file.
  */
 function file_entity_add_upload_submit($form, &$form_state) {
@@ -258,6 +214,23 @@ function file_entity_edit($form, &$form_state, $file) {
     '#weight' => -10,
   );
 
+  // Add a 'replace this file' upload field if the file is a local file only.
+  if (file_entity_file_is_local($file)) {
+
+    // The replacement file must have the same extension as the original file.
+    $replacement_options = array();
+    $replacement_options['file_extensions'] = pathinfo($file->uri, PATHINFO_EXTENSION);
+
+    // Add the upload widget.
+    $form['replace_upload'] = array(
+      '#type' => 'file',
+      '#title' => t('Replace file'),
+      '#description' => t('This file will replace the current file. The old file will be will no longer be available.'),
+      '#upload_validators' => file_entity_get_upload_validators($replacement_options),
+      '#pre_render' => array('file_entity_upload_validators_pre_render'),
+    );
+  }
+
   $form['preview'] = array(
     '#theme' => 'file_link',
     '#file' => $file,
@@ -294,6 +267,21 @@ function file_entity_edit($form, &$form_state, $file) {
  * Form validation handler for file_entity_edit().
  */
 function file_entity_edit_validate($form, &$form_state) {
+  // Handle the replacement file if uploaded.
+  if (isset($form_state['values']['replace_upload'])) {
+    // Save the file as a temporary file.
+    $file = file_save_upload('replace_upload', $form['replace_upload']['#upload_validators']);
+    if (!empty($file)) {
+      // Put the temporary file in form_values so we can save it on submit.
+      $form_state['values']['replace_upload'] = $file;
+    }
+    elseif ($file === FALSE) {
+      // File uploaded failed.
+      form_set_error('replace_upload', t('The replacement file could not be uploaded.'));
+    }
+  }
+
+  // Run entity form validation.
   entity_form_field_validate('file', $form, $form_state);
 }
 
@@ -302,8 +290,25 @@ function file_entity_edit_validate($form, &$form_state) {
  */
 function file_entity_edit_submit($form, &$form_state) {
   $file = $form_state['file'];
+
+  // Check if a replacement file has been uploaded.
+  if (!empty($form_state['values']['replace_upload'])) {
+    $replacement = $form_state['values']['replace_upload'];
+    // Move file from temp to permanent home.
+    file_unmanaged_copy($replacement->uri, $file->uri, FILE_EXISTS_REPLACE);
+  }
+
+  // Run entity form submit handling and save the file.
   entity_form_submit_build_entity('file', $file, $form, $form_state);
   file_save($file);
+
+  $args = array(
+    '@type' => file_type_get_name($file),
+    '%title' => entity_label('file', $file),
+  );
+  watchdog('file', '@type: updated %title.', $args);
+  drupal_set_message(t('@type %title has been updated.', $args));
+
   $form_state['redirect'] = 'file/' . $file->fid;
 }
 
@@ -361,6 +366,7 @@ function file_delete_form_submit($form, &$form_state) {
     // Use file_delete_multiple() rather than file_delete() since we want to
     // avoid unwanted validation and usage checking.
     file_delete_multiple(array($file->fid));
+
     $args = array(
       '@type' => file_type_get_name($file),
       '%title' => entity_label('file', $file),
@@ -458,3 +464,44 @@ function file_entity_page_edit($file) {
 function file_entity_page_delete($file) {
   return drupal_get_form('file_delete_form');
 }
+
+/**
+ * Retrieves the upload validators for a file.
+ *
+ * @param array $options
+ *   (optional) An array of options for file validation.
+ *
+ * @return array
+ *   An array suitable for passing to file_save_upload() or for a managed_file
+ *   or upload element's '#upload_validators' property.
+ */
+function file_entity_get_upload_validators(array $options = array()) {
+  // Set up file upload validators.
+  $validators = array();
+
+  // Validate file extensions. If there are no file extensions in $params and
+  // there are no Media defaults, there is no file extension validation.
+  if (!empty($options['file_extensions'])) {
+    $validators['file_validate_extensions'] = array($options['file_extensions']);
+  }
+  else {
+    $validators['file_validate_extensions'] = array(FILE_DEFAULT_ALLOWED_EXTENSIONS);
+  }
+
+  // Validate file size but do not allow anything higher than file_upload_max_size().
+  $max_filesize = file_upload_max_size();
+  if (!empty($options['max_filesize']) && $options['max_filesize'] < $max_filesize) {
+    $validators['file_validate_size'] = array(parse_size($options['max_filesize']));
+  }
+  else {
+    $validators['file_validate_size'] = array($max_filesize);
+  }
+
+  // Add image validators.
+  $options += array('min_resolution' => 0, 'max_resolution' => 0);
+  if ($options['min_resolution'] || $options['max_resolution']) {
+    $validators['file_validate_image_resolution'] = array($options['max_resolution'], $options['min_resolution']);
+  }
+
+  return $validators;
+}
diff --git a/tests/file_entity.test b/tests/file_entity.test
index 6401743..6ea1745 100644
--- a/tests/file_entity.test
+++ b/tests/file_entity.test
@@ -105,7 +105,87 @@ class FileEntityUnitTestCase extends FileEntityTestHelper {
 
     //Test hook_file_delete().
     file_delete($file, TRUE);
-    $this->assertFalse(db_query('SELECT count(*) FROM {image_dimensions} WHERE fid = :fid', array(':fid' => 'fid'))->fetchField(), 'Row deleted in {file_dimensions} on file_delete().');
+    $this->assertFalse(db_query('SELECT COUNT(*) FROM {image_dimensions} WHERE fid = :fid', array(':fid' => 'fid'))->fetchField(), 'Row deleted in {file_dimensions} on file_delete().');
+  }
+}
+
+class FileEntityReplaceTestCase extends FileEntityTestHelper {
+  public static function getInfo() {
+    return array(
+      'name' => 'File replacement',
+      'description' => 'Test file replace functionality.',
+      'group' => 'File entity',
+    );
+  }
+
+  function testReplaceFile() {
+    // Select the first text test file to use.
+    $file = reset($this->files['text']);
+
+    // Create a user with file edit permissions.
+    $user = $this->drupalCreateUser(array('edit file'));
+    $this->drupalLogin($user);
+
+    // Test that the Upload widget appears for a local file.
+    $this->drupalGet('file/' . $file->fid . '/edit');
+    $this->assertFieldByName('files[replace_upload]');
+
+    // Edit local file from above.
+    $edit = array();
+    $edit['filename'] = $file->filename;
+    $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+    // Test that file saves without uploading a file.
+    $this->assertText(t('File @file has been updated.', array('@file' => $edit['filename'])), 'File was updated without file upload.');
+
+    // Get the next text file to use as a replacement.
+    $original = clone $file;
+    $replacement = next($this->files['text']);
+    $edit['files[replace_upload]'] = drupal_realpath($replacement);
+    $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+    // Test that file saves with uploading a file.
+    $this->assertText(t('File @file has been updated.', array('@file' => $edit['filename'])), 'File was updated with file upload.');
+
+    // Re-load the file from the database.
+    $file = file_load($file->fid);
+    $file->original = $original;
+
+    // Test that file has same name as previously.
+    $this->assertEqual($file->filename, $file->original->filename, 'Updated file name did not change.');
+
+    // Test that file did change, via filesize.
+    $this->assertNotEqual($file->filesize, $file->original->filesize, 'Updated file size changed from previous file.');
+
+    // Test that database has filesize of uploaded file.
+    $this->assertEqual($file->filesize, $replacement->filesize, 'Updated file size matches uploaded file.');
+
+    // Test the the contents of file and uploaded file are the same.
+    $this->assertEqual(file_get_contents($replacement->uri), file_get_contents($file->uri), 'Updated file contents matches uploaded file.');
+
+    // Get an image file.
+    $image = reset($this->drupalGetTestFiles('image'));
+    $edit['files[replace_upload]'] = drupal_realpath($image->uri);
+
+    // Test that validation works.
+    $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
+    $this->assertRaw(t('The specified file %file could not be uploaded. Only files with the following extensions are allowed:', array('%file' => $image->filename)), 'File validation works, upload failed correctly.');
+
+    // Create a non-local file record.
+    $file2 = new stdClass();
+    $file2->uri = 'oembed://' . $this->randomName();
+    $file2->filename = drupal_basename($file2->uri);
+    $file2->filemime = 'image/oembed';
+    $file2->type = 'image';
+    $file2->uid = 1;
+    $file2->timestamp = REQUEST_TIME;
+    $file2->filesize = 0;
+    $file2->status = 0;
+    // Write the record directly rather than calling file_save() so we don't
+    // invoke the hooks.
+    $this->assertTrue(drupal_write_record('file_managed', $file2), 'Non-local file was added to the database.');
+
+    // Test that Upload widget does not appear for non-local file.
+    $this->drupalGet('file/' . $file2->fid . '/edit');
+    $this->assertNoFieldByName('files[replace_upload]');
   }
 }
 
