i haven't been able to find any references to 'file_link' in the new migrate. so far this works, but how to I make it a link instead of copying the file?

$this->addFieldMapping('field_migrate_example_image', 'url');
$this->addFieldMapping('field_migrate_example_image:file_replace')
->defaultValue(FILE_EXISTS_REPLACE);
$this->addFieldMapping('field_migrate_example_image:alt', 'image_alt');
$this->addFieldMapping('field_migrate_example_image:title', 'image_title');

Comments

mikeryan’s picture

Status: Active » Postponed (maintainer needs more info)

Try

$this->addFieldMapping('field_migrate_example_image:file_replace')
     ->defaultValue(MigrateFile::FILE_EXISTS_REUSE);
farse’s picture

sorry, that didn't work. let me explain a little better

all our source images are stored in public://rightmove/rm_images/unique folder for each collection of images/image00.jpg

where $image_path = unique folder/image.jpg

this code:

$arguments = MigrateFileFieldHandler::arguments(NULL, 'file_link', FILE_EXISTS_RENAME);
    $this->addFieldMapping('field_prop_images', 'images');

along with prepareRow() worked to create a link to the source images without the need to copy them (as we have A LOT of images [500GB])

since I couldn't find any reference to 'file_link' in the new version I was trying to get around this by setting the source path to the destination path still preserving the original file structure.

I have gotten to the point of:

   $this->addFieldMapping('field_prop_images', 'image_path');
    $this->addFieldMapping('field_prop_images:source_dir')
		->defaultValue('public://rightmove/rm_images');
	$this->addFieldMapping('field_prop_images:destination_dir')
		->defaultValue('public://rightmove/rm_images');
	$this->addFieldMapping('field_prop_images:destination_file', 'image_path');

    $this->addFieldMapping('field_prop_images:file_replace')
         ->defaultValue(MigrateFile::FILE_EXISTS_REUSE);

but the 'destination_file' doesn't seem to work (the destination_dir is only returned) and when I take it out, it doesn't preserve the original file structure (which is the whole point of what I'm trying to do to not create two copies of every image).

tried all sorts of permutations of the above, but nothing seems to get it as it worked before!

thanks in advance for help

farse’s picture

Status: Postponed (maintainer needs more info) » Active
farse’s picture

Priority: Normal » Major
joeyda3rd’s picture

I'm having difficulty figuring this out too.

Would like to just link to existing picture in a directory (or even external URL for that matter) for displaying an image rather than copying the image. Purpose is to save time on migrate and file space due to massive amounts of images.

Do you have any examples of this for 2.4?

Edit:
I found the following in http://drupal.org/node/1540106

preserve_files - Normally, importing and rolling back a migration involving file imports will delete the files on rollback. Sometimes this is not desirable - for example, one approach to file migration may be to simply copy all the files into their desired location in the Drupal installation and use FILE_EXISTS_REUSE to just link to them, or you may have a symbolic link to a readonly mounted filesystem containing the files.

@farse: 'public://rightmove/rm_images' is a symlink? Have you tried setting preserve_files to true? Not sure if that would help.

Also, did you have your 'image_path' relative to the 'source_dir'?

Also from http://drupal.org/node/1540106

destination_file - This is the filename relative to destination_dir at which to store the file. If omitted, the filename portion of the primary value will be used. If you have a hierarchical structure you wish to preserve, pass the same value here as you do for the primary value. For example, suppose you implement a migration given physical files at:

Maybe try removing the mapping for the file value:

$this->addFieldMapping('field_prop_images:destination_file', 'image_path');

and see if it will use the primary value from 'image_path'

Let me know if any of this works

farse’s picture

my example is from 2.4, the snippet before that (with file_link) was what was working in 2.3

I am wanting to do the same thing to just link to files in my existing directory , but thought a way around this would be to just copy (actually not copying, by setting $this->addFieldMapping('field_migrate_example_image:file_replace')
->defaultValue(MigrateFile::FILE_EXISTS_REUSE); just reuses the existing file there) the files setting source = destination.

and yes i also tried using preserve_files, but it didn't really do anything other than not delete the files on rollback (which was ok because it copied the file into the wrong place so don't matter if it got deleted on rollback).

and yes, image_path is relative to source_dir, getting the images isn't a problem.

and if i get rid of destination_dir and just leave destination_file it doesn't pick up the unique folder that i want to save it in either way, it just takes the filename and puts in in the default public://, which is not what I want.

I tried picking through http://drupal.org/node/1540106 all day last week to figure out some way to get it to work, but no luck yet.

mikeryan’s picture

Priority: Major » Normal

Sorry for the silence so far - it's tough to see why it's not working without a good test bed - a real-life file migration to work with. As it happens, I'm just starting a new Drupal-to-Drupal migration migration project, and now that I have my source file dump which I'll be symlinking from the destination files directory, I'll be ready to implement my file migration - I should have an answer (whether it's a better set of parameters, or a code fix) within a few days.

Thanks.

joeyda3rd’s picture

@mikeryan: Thanks for looking into this for us!

@farse: In the mean time, could you maybe try hard coding in the directory structure you want to keep?

 $this->addFieldMapping('field_prop_images:destination_dir')
        ->defaultValue('public://rightmove/rm_images/'.$this->unique_dir.'/');

function prepareRow(){
  $this->unique_dir = $uid;
}
farse’s picture

thanks mikeryan, looking forward to your reply

and thanks joeyda3rd, I tried a slight variation of that:

$this->addFieldMapping('field_prop_images:destination_dir')
		->defaultValue('public://rightmove/rm_images/'.$current_row->image_dir.'/');

public function prepareRow($current_row) {
	  
$current_row->image_dir = $current_row->IMAGE_FOLDER;

}

just results in a directory of 'public://rightmove/rm_images///' (as the variable doesn't seem to be there outside the function, when it does exist in prepareRow())....

mikeryan’s picture

Component: Code » Documentation
Status: Active » Postponed (maintainer needs more info)

OK, here's my scenario and how I configured the migration to make it work:

  • My legacy files are at /data/dumps/files (copy of sites/default/files from a source Drupal site).
  • I linked them into my target site, while in sites/mysite/files I did: ln -s /data/dumps/files/ legacy

My migration (D7-to-D7, so source fields are from the file_managed table) looks more-or-less like:

    $this->destination = new MigrateDestinationFile();
    // Edited: should be stream wrapped parent directory
    $this->addFieldMapping('destination_dir')
         ->defaultValue('public://legacy');
    // ...while this is relative to Drupal root (should fetch file_public_path)
    // Edit: But, doesn't matter - see later comment
    $this->addFieldMapping('source_dir')
         ->defaultValue('sites/mysite/files/legacy');
    $this->addFieldMapping('timestamp', 'timestamp');
    $this->addFieldMapping('file_replace')
         ->defaultValue(MigrateFile::FILE_EXISTS_REUSE);
    $this->addFieldmapping('preserve_files')
          ->defaultValue(TRUE);
    $this->addFieldMapping('value', 'uri')
         ->callbacks(array($this, 'fixUri'));
    $this->addFieldMapping('destination_file', 'uri')
         ->callbacks(array($this, 'fixUri'));
...
  // Because we're coming from D7, which has public:// prepended to the path within the files dir
  protected function fixUri($uri) {
    return str_replace('public://', '', $uri);
  }

Is this helpful? If so I'll add an example to the documentation.

mikeryan’s picture

I should point out that source_dir only matters if the file is actually copied, in this scenario it's irrelevant - the incoming uri to appended to the destination_dir, and since destination_dir/uri exists, all that's done is the creation of the file entity, no copy is attempted.

mikeryan’s picture

Component: Documentation » Code
Status: Postponed (maintainer needs more info) » Active

No, wait a minute, it's not working right, got a bogus uri in the destination file_managed table (I was looking at the wrong db), continuing to investigate...

mikeryan’s picture

OK, I thought there was something wrong with setting destination_dir to legacy instead of public://legacy... The latter is right, the problem is file_exists() on the filespec with the stream wrapper (public://) fails when the files are symbolically linked - when I made a parallel legacy2 directory with a copy of my physical target file, that worked. Trying various solutions involving is_file(), drupal_realpath(), etc., but still can't verify the existence of the linked file.

mikeryan’s picture

Category: support » bug

See #1008402: Allow the use of symlinks within the files directory. - I find I'm already following that issue, so I guess I've run into this before;).

What I need to do here to support symbolic links is to reproduce the logic of DrupalLocalStreamWrapper::getLocalPath(), without the bit that bails on symlinks... Then the resulting path should pass file_exists with flying colors.

mikeryan’s picture

No luck on that - or rather, I solved the immediate (file_exists) problem, but ran into more trouble deep within the File API (i.e., getting filesize on the linked files from file_save()).

For now, I've confirmed that the patch at http://drupal.org/node/1008402#comment-6118996 works. But, not wanting to kill any kittens, I'm replacing the symlink with a mount:

rm legacy
mkdir legacy
sudo mount --rbind /data/dumps/files/ legacy
farse’s picture

so will/should

$this->addFieldMapping('destination_file', 'uri')
         ->callbacks(array($this, 'fixUri'));

work the same as

 $this->addFieldMapping('field_prop_images:destination_file', 'uri')
         ->callbacks(array($this, 'fixUri'));

with the added 'field_prop_images:'?

and does your uri have a folder inside and is this file structure preserved? and i need to install the patch for this to work (it says its for core v8?? )?

the main problem with me is that when i use 'destination_file' the file structure isn't preserved and just returns the filename.

if so, maybe be on a way to a solution!

mikeryan’s picture

Yes, the field version should treat destination_file the same - if your uri has a full path, that should be preserved.

The issue is for Drupal 8, but the patch I pointed to is for Drupal 7. An alternative to the patch, as I pointed out, is using mount --rlink instead of symlinks to tie the files into your files directory.

farse’s picture

for example, this is what i am getting in my file_managed folder:

filename uri
36516_61014_IMG_01.jpg public://rightmove/rm_images/36516_61014_IMG_01.jpg
36516_61014_IMG_00.jpg public://rightmove/rm_images/36516_61014_IMG_00.jpg
36516_187881_IMG_00.jpg public://rightmove/rm_images/36516_187881_IMG_00.jpg
36516_187881_IMG_01.jpg public://rightmove/rm_images/36516_187881_IMG_01.jpg

when i do


$this->addFieldMapping('field_prop_images:destination_dir')
->defaultValue('public://rightmove/rm_images');
$this->addFieldMapping('destination_file', 'image_path');

where image_path=36516_61014/36516_61014_IMG_01.jpg, etc...

where I want it as:

36516_61014_IMG_01.jpg public://rightmove/rm_images/36516_61014/36516_61014_IMG_01.jpg
36516_61014_IMG_00.jpg public://rightmove/rm_images/36516_61014/36516_61014_IMG_00.jpg
36516_187881_IMG_00.jpg public://rightmove/rm_images/36516_1878811/36516_187881_IMG_00.jpg
36516_187881_IMG_01.jpg public://rightmove/rm_images/36516_187881/36516_187881_IMG_01.jpg

which would be equal to my source directory.

and when i try:

$this->addFieldMapping('field_prop_images:destination_file', 'image_path');

the migration doesn't seem to pick it up as it tries to put 'rm_images' as the filename and 'public://rightmove/' as the uri...

farse’s picture

not sure if the mount solves any of my problems as my files are already where i want them, just trying to solve the problem above. will apply the patch and see what happens..

installed the patch, same thing happened...

mikeryan’s picture

You mean your files are physically under the files folder, not linked in? OK, most of what I've been talking about is irrelevant to you, but the basic setup in comment #10 should work, something like:

    $this->addFieldMapping('field_prop_images', 'image_path');
    $this->addFieldMapping('field_prop_images:destination_file', 'image_path');
    $this->addFieldMapping('field_prop_images:destination_dir')
         ->defaultValue('public://rightmove/rm_images');
    // Doesn't really matter if we're not copying, but let's provide the "correct" value
    $this->addFieldMapping('field_prop_images:source_dir')
         ->defaultValue('sites/mysite/files/rightmove/rm_images');
    $this->addFieldMapping('field_prop_images:file_replace')
         ->defaultValue(MigrateFile::FILE_EXISTS_REUSE);
    $this->addFieldmapping('field_prop_images:preserve_files')
          ->defaultValue(TRUE);

If you can't tweak that to work, all I can suggest doing is what I did - debug MigrateFileUri::processFile() and MigrateFile::processFile(), printing out the key paths as they're constructed and deconstructed...

farse’s picture

i have all the above and

$this->addFieldMapping('field_prop_images:destination_file', 'image_path');

seems to be the only part that is causing me trouble, as it seems to ignore it and just returns the destination_dir. i'll have a look at the two classes and see if i notice anything else.

i just noticed something in my code that isn't quite the same as yours..

i have

$this->source = new MigrateSourceSQL($query, array(), $count_query);
$this->destination = new MigrateDestinationNode('property');

instead of

$this->destination = new MigrateDestinationFile();

as I have other fields I am mapping to the same node as in 'WineWineMigration', in case that makes any difference.

farse’s picture

one step closer...

in

MigrateFile::processFile() i printed out

$this->destinationDir . $this->destinationFile 

before 

$destination = file_stream_wrapper_uri_normalize(
      $this->destinationDir . '/' .
      ltrim($this->destinationFile, "/\\"));

to see what was happening.
turns out that $this->destinationFile is an array (of which I already knew), it printed 'public://rightmove/rm_imagesArray', but thought that was ok since i'm dealing with an array of images for the node and $this->addFieldMapping('field_prop_images', 'image_path'); seems to be ok with arrays. now the next question. how would i deal with a destination file array as the source file seems to be ok with them?

as the winewinemigration example also uses image arrays, but has 'field_migrate_example_image:source_dir', 'field_migrate_example_image:destination_dir',
'field_migrate_example_image:destination_file' as unmigrated destinations, so not able to figure out what to do!

mikeryan’s picture

With the new array angle here, cross-referencing #1673956: Migrating multiple files.

mikeryan’s picture

A note on your observation that I'm using MigrateDestinationFile while you're using MigrateDestinationNode: you can migrate image fields in two ways - directly into the field as you are doing, or you can migrate the files separately (MigrateDestinationFile) then map the results of that migration to your image field using MigrateFileFid. I prefer the latter - I find it cleaner to have the file migration separate from the node migration. And, it probably works better in the array case...

mikeryan’s picture

OK, your scenario is definitely not going to work - the loop in MigrateFileFieldBaseHandler that iterates over the image values does not iterate over destinationFile in parallel, and there isn't a way to pass distinct destinationFile values to processFile(). Trying to think of a non-hacky way to deal with this...

mikeryan’s picture

Status: Active » Closed (won't fix)

Reviewing this, the "bug" remaining is that the linking behavior doesn't work when files are symbolically linked into the Drupal files directory - this is due to core issue #1008402: Allow the use of symlinks within the files directory., and I'm not going to pursue working around it in Migrate.

mikeryan’s picture

Issue summary: View changes

forgot to include intro text