Importing from a file defined as a URI, but from an existing D6 site, so we already have a fid that we want to keep.

In MigrateDestinationFile->import() there is a line,

$file = $source->processFile($file->value, $file->uid);

$file here already has a fid, but this call eventually also calls file_save which returns a $file with a new fid, breaking our migration.

Comments

muhleder’s picture

So it looks like core file_save() sort of prevents this possibility, since it uses drupal_write_record to do either an update or insert based upon whether or not $file->fid is set.

If we set $file->fid, file_save() will try to update a row and we get nothing inserted.

mikeryan’s picture

Category: bug » support
Status: Active » Postponed (maintainer needs more info)

Are you saying that you're trying to use the same fid in Drupal 7 as you did in Drupal 6, and are explicitly passing that? That's not going to work, as you point out - core does not support adding a new file entity with an explicit ID. Nodes and users have the "is_new" support for doing this, but they're the only core entities that support it.

The real question is, why do you want to keep the fid? If you're expecting that you need the same fid to maintain relationships to, say, file fields, it isn't necessary - the sourceMigration() support in Migrate will rewrite old fids to new fids so relationships are not broken.

muhleder’s picture

For completeness I guess, eg there might be a token in a text area linking to a specific file id, or a custom module might provide zipped downloads based upon a path including a file id eg /downloadzipped/$fid

So core file_save is quite a short function and what I've done for my case is provide an alternate version in the class to save with the file id.

  /**
   * Clone of core file_save allowing us to upsert based on file id.
   *
   * @param $file
   * @return $file
   */
  protected  function file_save($file) {
    $file->timestamp = REQUEST_TIME;
    $file->filesize = filesize($file->uri);

    // Load the stored entity, if any.
    if (!empty($file->fid) && !isset($file->original)) {
      $file->original = entity_load_unchanged('file', $file->fid);
    }

    module_invoke_all('file_presave', $file);
    module_invoke_all('entity_presave', $file, 'file');

    // Convert the $file object to an array for db_merge
    $file_fields = array();
    $file_schema = drupal_get_schema('file_managed');
    foreach ($file_schema['fields'] as $name => $properties) {
      if (isset($file->{$name})) $file_fields[$name] = $file->{$name};
    }
    $fid = (isset($file_fields['fid']))? $file_fields['fid'] : null;

    // Upsert the file record
    $status = db_merge('file_managed')
      ->key(array('fid' => $fid ))
      ->fields( $file_fields)->execute();
    switch ($status) {
      // Record inserted
      case (STATUS_INSERT):
        $file->fid = db_query('SELECT LAST_INSERT_ID();')->fetchField();
        module_invoke_all('file_insert', $file);
        module_invoke_all('entity_insert', $file, 'file');
        break;
      // Record updated
      case (STATUS_UPDATE):
        module_invoke_all('file_update', $file);
        module_invoke_all('entity_update', $file, 'file');
        break;
      default:
        watchdog('file', 'Error upserting file record for uri', $file_fields);
    }

    unset($file->original);
    return $file;
  }

Ugly but working. I've also altered $source->processFile($file->value, $file->uid) to pass in the file object as well of course. Don't have a patch for this to hand, figured it would be too brittle to get in.

mikeryan’s picture

The nice thing about the new file handling is that you can define your own file class - one that calls your custom file_save() in place of core's, treats $value as a file object, etc.

mikeryan’s picture

Status: Postponed (maintainer needs more info) » Closed (works as designed)