Remove upload module and provide upgrade path to file: http://drupal.org/node/563000

From: andrew morton <graveltech@gmail.com>


---

 modules/field_ui/field_ui.admin.inc |    2 
 modules/system/system.install       |  300 +++++++++++++++++++++++++++++++++--
 modules/upload/upload.install       |   57 -------
 3 files changed, 282 insertions(+), 77 deletions(-)


diff --git modules/field_ui/field_ui.admin.inc modules/field_ui/field_ui.admin.inc
index 792b642..72cd112 100644
--- modules/field_ui/field_ui.admin.inc
+++ modules/field_ui/field_ui.admin.inc
@@ -839,7 +839,7 @@ function field_ui_field_settings_form($form, &$form_state, $obj_type, $bundle, $
     '#options' => array(TRUE => t('Translatable field'), FALSE => t('Language neutral field')),
     '#default_value' => $field['translatable'],
     '#description' => t("Translatable fields can have a different value for each available language. An example of a translatable field is an article's <em>body</em>. Language neutral fields will retain the same value across all translations. An example of a language neutral field is a user profile's <em>first name</em>."),
-  ); 
+  );
 
   // Add settings provided by the field module.
   $form['field']['settings'] = array();
diff --git modules/system/system.install modules/system/system.install
index 1242daa..e61ea10 100644
--- modules/system/system.install
+++ modules/system/system.install
@@ -2431,28 +2431,74 @@ function system_update_7034() {
 /**
  * Migrate upload module files to the new {file} table.
  */
-function system_update_7035() {
-  $ret = array();
+function system_update_7035(&$context) {
+  $ret = array('#finished' => 0);
+
+  // Check for the existance of the table rather than using module_exists()
+  // so we can migrate even if the module is disabled.
   if (!db_table_exists('upload')) {
-    return $ret;
+    return array(
+      '#finished' => 1,
+      array('success' => TRUE, 'query' => "No upload module data to migrate."),
+    );
+  }
+
+
+  if (!isset($context['total'])) {
+    // Initial invocation.
+
+    // Initialize state for future calls.
+    $context['last'] = 0;
+    $context['count'] = 0;
+
+    // Find a total number of records. We're joining to the node table here
+    // to ensure the count matches up later when we repeat the query.
+    $query = db_select('upload', 'u');
+    $query->innerJoin('file', 'n', 'u.fid = f.fid');
+    $query->addExpression('COUNT(u.fid)');
+    $context['total'] = $query->execute()->fetchField();
   }
-  // The old {files} tables still exists.  We migrate core data from upload
-  // module, but any contrib module using it will need to do its own update.
-  $result = db_query('SELECT fid, uid, filename, filepath AS uri, filemime, filesize, status, timestamp FROM {files} f INNER JOIN {upload} u ON u.fid = f.fid', array(), array('fetch' => PDO::FETCH_ASSOC));
-
-  // We will convert filepaths to uri using the default schmeme
-  // and stripping off the existing file directory path.
-  $basename = variable_get('file_directory_path', conf_path() . '/files');
-  $scheme = variable_get('file_default_scheme', 'public') . '://';
-  $fids = array();
-  // TODO: does this function need to run in batch mode?
-  foreach ($result as $file) {
-    $file['uri'] = $scheme . str_replace($basename, '', $file['uri']);
-    $file['uri'] = file_stream_wrapper_uri_normalize($file['uri']);
-    db_insert('file')->fields($file)->execute();
-    $fids[] = $file['fid'];
+  else {
+    // Subsequent invocations.
+
+    if ($context['total']) {
+      $batch_size = 100;
+
+      // The old {files} tables still exists. We migrate core data from upload
+      // module, but any contrib module using it will need to do its own update.
+      $query = db_select('file', 'f');
+      $query->innerJoin('upload', 'u', 'f.fid = u.fid');
+      $result = $query
+        ->fields('f', array('fid', 'uid', 'filename', 'filepath', 'filemime', 'filesize', 'status', 'timestamp'))
+        ->range($context['last'], $batch_size)
+        ->orderBy('fid', 'ASC')
+        ->execute();
+
+      // We will convert filepath to uri using the default schmeme and stripping
+      // off the existing file directory path.
+      $basename = variable_get('file_directory_path', conf_path() . '/files');
+      $scheme = variable_get('file_default_scheme', 'public') . '://';
+
+      foreach ($result as $file) {
+        $file->uri = file_stream_wrapper_uri_normalize($scheme . str_replace($basename, '', $file->filepath));
+        unset($file->filepath);
+        db_insert('file')->fields((array) $file)->execute();
+
+        $context['last'] = $file->fid;
+        $context['count'] += 1;
+      }
+
+      $ret['#finished'] = min(0.99, $context['count'] / $context['total']);
+    }
+    else {
+      // All rows are processed.
+      $ret[] = array('success' => TRUE, 'query' => "{$context['total']} upload records migrated from 'files' table to 'file' table.");
+
+      // We're done.
+      $ret['#finished'] = 1;
+    }
   }
-  // TODO: delete the found fids from {files}?
+
   return $ret;
 }
 
@@ -2493,6 +2539,222 @@ function system_update_7037() {
 }
 
 /**
+ * Migrate the upload module's data to a file field.
+ *
+ * TODO: Test the translateabiltiy
+ */
+function system_update_7038(&$context) {
+  $ret = array('#finished' => 0);
+
+  // Check for the existance of the table rather than using module_exists()
+  // so we can migrate even if the module is disabled.
+  if (!db_table_exists('upload')) {
+    return array(
+      '#finished' => 1,
+      array('success' => TRUE, 'query' => "No upload module data to migrate."),
+    );
+  }
+
+  if (!isset($context['total'])) {
+    // Initial invocation.
+
+    // Install the file module if it isn't already.
+    if (!module_exists('file')) {
+      drupal_install_modules(array('file'));
+    }
+
+    // Initialize state for future calls.
+    $context['last'] = 0;
+    $context['count'] = 0;
+
+    // Find a total number of records. We're joining to the node table here
+    // to ensure the count matches up later when match to get the node type.
+    $query = db_select('upload', 'u');
+    $query->innerJoin('node', 'n', 'u.nid = n.nid');
+    $query->addExpression('COUNT(DISTINCT u.vid)', 'vid_count');
+    $context['total'] = $query->execute()->fetchField();
+
+
+    // Check which node types have upload.module attachements enabled.
+    $context['types'] = array();
+    foreach (node_type_get_types() as $node_type => $node_info) {
+      if (variable_get("upload_$node_type", TRUE)) {
+        $context['types'][$node_type] = $node_type;
+      }
+    }
+    // The {upload} table will be deleted when this update is complete so we
+    // want to be careful to migrate all the data, even for node types that
+    // may have had attachments disabled after files were uploaded. Look for
+    // any other node types referenced by the upload records and add those to
+    // the list. The admin can always remove the field later.
+    $query = db_select('node', 'n')->distinct(TRUE);
+    $query->innerJoin('upload', 'u', 'n.vid = u.vid');
+    $results = $query->fields('n', array('type'))->execute();
+    foreach ($results as $row) {
+      $context['types'][$row->type] = $row->type;
+    }
+
+    // If there are any any node types then create the file field and an
+    // instance for each type.
+    if (count($context['types'])) {
+      module_load_include('inc', 'field', 'field.crud');
+
+      $field = array(
+        'field_name' => 'field_upload',
+        'type' => 'file',
+        'locked' => FALSE,
+        'module' => 'file',
+        'cardinality' => '-1',
+        'translatable' => FALSE,
+        'settings' => array(
+          'display_field' => 1,
+          'display_default' => 1,
+          'uri_scheme' => variable_get('file_default_scheme', 'public'),
+          'default_file' => 0,
+        ),
+      );
+
+      $instance = array(
+        'field_name' => 'field_upload',
+        'bundle' => NULL,
+        'label' => 'File attachments',
+        'required' => 0,
+        'description' => '',
+        'widget' => array(
+          'weight' => '1',
+          'module' => 'file',
+          'active' => '1',
+          'settings' => array(
+            'progress_indicator' => 'throbber',
+            'description_field' => 1,
+          ),
+          'type' => 'file_generic',
+        ),
+        'display' => array(
+          'full' => array(
+            'label' => 'above',
+            'type' => 'file_table',
+            'settings' => array(),
+            'weight' => 0,
+            'module' => 'file',
+          ),
+          'teaser' => array(
+            'label' => 'hidden',
+            'type' => 'hidden',
+            'settings' => array(),
+            'weight' => 0,
+            'module' => NULL,
+          ),
+          'rss' => array(
+            'label' => 'hidden',
+            'type' => 'file_table',
+            'settings' => array(),
+            'weight' => 0,
+            'module' => 'file',
+          ),
+        ),
+        'settings' => array(
+          'max_filesize' => variable_get('upload_uploadsize_default', 1),
+          'file_extensions' => variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'),
+          'file_directory' => '',
+        ),
+      );
+
+      try {
+        // Create the field.
+        field_create_field($field);
+        $ret[] = array('success' => TRUE, 'query' => "Created 'attached files' field.");
+
+        // Create the instances.
+        foreach ($context['types'] as $bundle) {
+          $instance['bundle'] = $bundle;
+          field_create_instance($instance);
+          $ret[] = array('success' => TRUE, 'query' => "Added 'attached files' field to " . check_plain($bundle) . " node type.");
+        }
+      }
+      catch (FieldException $e) {
+        $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
+        return $ret;
+      }
+    }
+  }
+  else {
+    // Subsequent invocations.
+
+    $batch_size = 50;
+    $vids = array();
+    if ($context['total']) {
+      // Each revision can have multiple files, all of which need to be
+      // assigned to the node object before we write the record. To account
+      // for this we grab a number of revisions and process all of their files.
+      $vids = db_select('upload', 'u')
+        ->distinct()
+        ->fields('u', array('vid'))
+        ->range($context['last'], $batch_size)
+        ->orderBy('u.vid', 'ASC')
+        ->execute()
+        ->fetchAllKeyed(0, 0);
+    }
+
+    if (count($vids)) {
+      $query = db_select('upload', 'u');
+      $n = $query->innerJoin('node', 'n', 'u.nid = n.nid');
+      $results = $query
+        ->fields('u', array('nid', 'vid', 'fid', 'description', 'list', 'weight'))
+        ->fields($n, array('type'))
+        ->condition('u.vid', $vids, 'IN')
+        ->orderBy('u.vid', 'ASC')
+        ->orderBy('u.weight', 'ASC')
+        ->orderBy('u.fid', 'ASC')
+        ->execute();
+
+      // Group the upload records by revision.
+      $nodes = array();
+      foreach ($results as $row) {
+        $nodes[$row->vid]['nid'] = $row->nid;
+        $nodes[$row->vid]['vid'] = $row->vid;
+        $nodes[$row->vid]['type'] = $row->type;
+        $nodes[$row->vid]['field_upload'][FIELD_LANGUAGE_NONE][] = array(
+          'fid' => $row->fid,
+          'data' => serialize(array('description' => $row->description)),
+          'display' => $row->list,
+        );
+      }
+
+      // Then store each revision in the database.
+      foreach ($nodes as $vid => $node) {
+        $found = TRUE;
+        $node = (object) $node;
+
+        // This is a core update and no contrib modules are enabled yet, so
+        // we can assume default field storage for a faster update.
+        field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array());
+
+        $context['last'] = $vid;
+        $context['count'] += 1;
+      }
+
+      $ret['#finished'] = min(0.99, $context['count'] / $context['total']);
+    }
+    else {
+      // All rows are processed.
+      $ret[] = array('success' => TRUE, 'query' => "{$context['total']} upload records migrated to the 'attached files' field.");
+
+      // Uninstall the upload module.
+      #TODO: Commenting this out because it removes the tables but doesn't
+      # change the module system.status to 0 so upload_file_load() still gets
+      # called resulting in fatal errors.
+#      drupal_uninstall_modules(array('upload'));
+
+      // We're done.
+      $ret['#finished'] = 1;
+    }
+  }
+
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
diff --git modules/upload/upload.install modules/upload/upload.install
index e0f1ee8..ae18c21 100644
--- modules/upload/upload.install
+++ modules/upload/upload.install
@@ -76,60 +76,3 @@ function upload_schema() {
 
   return $schema;
 }
-
-
-/**
- * Migrate upload module files from {files} to {file}.
- */
-function upload_update_7000(&$sandbox) {
-  $ret = array();
-
-  /*
-    TODO: Fix the updates. This is broken. See http://drupal.org/node/329301#comment-1404336
-    Also note new DB structure http://drupal.org/node/227232#comment-1683976
-  */
-
-  if (!isset($sandbox['progress'])) {
-    // Initialize batch update information.
-    $sandbox['progress'] = 0;
-    $sandbox['last_fid_processed'] = -1;
-    $sandbox['max'] = db_query("SELECT COUNT(DISTINCT u.fid) FROM {upload} u")->fetchField();
-  }
-
-  // As a batch operation move records from {files} into the {file} table.
-  $limit = 500;
-  $result = db_query_range("SELECT DISTINCT u.fid FROM {upload} u ORDER BY u.vid", array(), 0, $limit);
-  foreach ($result as $record) {
-    $old_file = db_query('SELECT f.* FROM {files} f WHERE f.fid = :fid', array(':fid' => $record->fid))->fetch(PDO::FETCH_OBJ);
-    if (!$old_file) {
-      continue;
-    }
-
-    $new_file = db_query('SELECT f.* FROM {files} f WHERE f.filepath = :filepath', array(':filepath' => $old_file->uri))->fetch(PDO::FETCH_OBJ);
-    if (!$new_file) {
-      // Re-save the file into the new {file} table.
-      $new_file = clone $old_file;
-      drupal_write_record('file', $new_file);
-    }
-
-    // If the fid has changed we need to update the {upload} record to use the
-    // new id.
-    if (!empty($new_file->fid) && ($new_file->fid != $old_file->fid)) {
-      db_update('upload')
-        ->fields(array('fid' => $new_file->fid))
-        ->condition('fid', $old_file->fid)
-        ->execute();
-    }
-
-    // Update our progress information for the batch update.
-    $sandbox['progress']++;
-    $sandbox['last_fid_processed'] = $old_file->fid;
-  }
-
-  // Indicate our current progress to the batch update system. If there's no
-  // max value then there's nothing to update and we're finished.
-  $ret['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
-
-  return $ret;
-}
-
