I've got a bunch of sites set up on multi site and I never thought about the location of the files directory until it was too late. Being the neat freak that I am, I'd really like to move them so each /sites/ has its own.

Has anyone done this? What did you all have to change? Obviously the setting, but beyond that? I did a little preliminary poking around and see I have to go into each flexinode record because it's hard coded. What about for regular nodes? I didn't see where that's stored.

Anything else I'm missing?

Thanks,

Michelle

Comments

dopry’s picture

Since the paths that drupal saves to its database are not relative to the files folder but relative from drupal's root or absolute, you have to update all the paths in the {files} table for the affected files.

Its a real pita. but doable with some simple scripting.

something like

  $files = select fid, path from {files}
  foreach $files as $file {
       $file['path'] = str_replace($oldpath, $newpath, $file['path'])
        update {files} set path = $file['path'] where fid=$file['fid'] 
  }
Michelle’s picture

I'll give that a try soon as I get a solid block of free time so I can fix things if I mess it up. ;)

Curious... Why does Drupal hardcode things so that it's such a PITA to move the files dir? Why not store a relative path and just prepend whatever the path to Files currently is?

Michelle

ekes’s picture

I too have this issue (several times over as I collect sites together).

I've found it's not just the files table, but also links in nodes and comments and files in audio... module tables.

My notes including a script with some pointers how to edit for your site can be found at: https://en.wiki.aktivix.org/IMC-Drupal/Upgrade/SingleToMultiSite

greggles’s picture

I see two problems here

1) identifying and moving the files

2) updating the database paths

Here is how I would solve them:

1) get a set of commands to move the files from the database with this query:

SELECT CONCAT('mv ',filepath,' ',REPLACE(filepath,'files/','sites/example.com/files/')) FROM FILES;

this will give you a response like:

mv files/file1.pdf sites/example.com/file2.pdf
mv files/file2.pdf sites/example.com/file2.pdf

Put that into a script file and execute it on the host in a shell session (assuming you don't have shell, copy/paste it into your ftp buffer)

2) run a single query to update the paths:

UPDATE FILES SET filepath = REPLACE(filepath,'files/','sites/example.com/files/');

I didn't test this, it's just theory and if you are using a non-standard upload method then YMMV in exactly what you have to do, but it should be easy enough to alter the queries to get your desired results.

Of course, I'd recommend doing this for a single file and/or on a test site before running it live. My paths may not fit your situation, but that seems like a trivial adjustment for your situation.

Whatever method you provide - I'd suggest contributing your final answer to the handbook.

--
Growing Venture Solutions
Drupal Implementation and Support in Denver, CO

Michelle’s picture

#1 isn't an issue for me... They're all in /files so I'll just move them all to /sites/site/files.

#2 is where things are complicated since they're hardcoded in the database. I don't know enough about this to understand why you have a single query whereas dopry has a whole script. What's he doing that you're not?

Whichever way I go, I'll definitely backup first. :)

Thanks,

Michelle

greggles’s picture

I come from a data analysis background and am usually the "db guy" on projects. If possible, single queries are generally MUCH faster than looping over a recordset so I got in that habit and stick with it.

I believe we're both doing the same things, it's just "more than one way to skin the cat..."

Also, note that his is pseudo-code and not actual PHP. It gives a hint of what you need to do, but not a complete solution.

--
Growing Venture Solutions
Drupal Implementation and Support in Denver, CO

dopry’s picture

greggles background in db stuff is much better than mine. I'm not really familiar with string manipulation in the DB... I'll have to keep this in mind.. Thanks greggles.

greggles’s picture

It's probably worthwhile paying special attention if you use any other modules like the image module. I found that many (but not all) of my thumbnails got "lost" in the process of moving. I'm not sure why, really. Anyway, there is an issue and patch which fixes the problem even though it doesn't seem to fix them in the "right" way: http://drupal.org/node/80237

--
Growing Venture Solutions
Drupal Implementation and Support in Denver, CO

ezra-g’s picture

Core user avatars are stored in users.picture, so be sure to update that column as well.

Another handy tip is when changing from a relative filepath to a full one is to take note of the new filepath pattern and be sure you've got it correct before you do the replace ;). It might help to upload a new file after making Drupal file-system config changes, and compare the new filepath with the old ones in the database.

Ezra Barnett Gildesgame

jschumann’s picture

I love you. :-) Thanks for the one-liner.

mikegull’s picture

Ran across the same issue for flexinode_file.inc, but it saves the hardcoded file in serialized_data, which is a pain. I did not want to do it by node, as I needed the node modification dates preserved, so whipped up the following. Used with the "execute PHP code" option in the devel block.

$sql = "select * from flexinode_data where field_id = 1";  //your field_id may vary
//print "SQL: '$sql'<BR />\n";  
$res = db_query($sql);
print "Number of hits: " . db_num_rows($res) . "<BR />\n";
while ($f = db_fetch_object($res)) {
    $s1 = unserialize($f->serialized_data);
    $s = str_replace('/old/path/to/files', '/new/path/to/files', $s1->filepath);
    print "OLD: '$s1->filepath' -> NEW: '$s'<BR />\n";
    $s1->filepath = $s;
    $sql = "UPDATE {flexinode_data} SET serialized_data = '%s' WHERE nid = %d and field_id = %d";
//print "SQL: '$sql'<BR />\n";    
    db_query($sql, serialize($s1), $f->nid, $f->field_id);
}
print 'DONE';
aristedes’s picture

Clearly Drupal is doing the wrong thing here and that is because of its history as a single site only system. Everything should be easily portable between sites with none of these paths in the database.

One question is, how does Drupal move forward? Using relative paths is the sensible thing (so that Drupal looks for a file under /sites/xyz/files if that has been specified by $file_directory_path in settings.php). But that will break all existing installations when they upgrade.

Is there some place (like http://api.drupal.org/api/HEAD/function/file_create_url) where we can strip the "/file/" or "/sites/xzy/file/" prefix and replace it with $file_directory_path?

Success’s picture

My Domain B..... to be under a multi site config in Domain A

Can the files directory for Domain B be inside

DomainB.com/files/

instead of

domainA.com/sites/domainB.com/files

?

shusheer’s picture

The suggestion above using a single SQL statement does _most_ of the job:

UPDATE FILES SET filepath = REPLACE(filepath,'files/','sites/example.com/files/');

However, be aware that (particularly in the variables table) there are also many links that need replacing. Furthermore, because of the way strings are stored in this table, you will need to not only alter the string, but also the recorded length of the string. It's painful on a big site (but not quite painful enough for me to actually write a script to do it...).

Just a pointer for whoever notices that e.g. their theme disappears when they move the files location, and perform the SQL query above, and it's still not working ;)

Mark Nielsen’s picture

I've just done this by creating an SQL dump of my database (with drush 4.4), using a search and replace function in my text editor (vim) to replace all the hardcoded strings in the database, and then restoring the database (again, using drush). Here's how:

  • I wanted to change my files directory from 'sites/default/files' to 'sites/example.com/files'.
  • So, I first created the SQL dump file: drush -l example.com sql-dump --result-file=example.com.sql
  • Then I created a copy of my dump file, in case I messed up: cp example.com.sql example.com.sql.bak
  • Then I edited the dump file (vim example.com.sql) and ran the following commands:
    :%s/sites\/default\/files\//sites\/example.com\/files\//g
    :%s/http:\/\/www.example.com\/sites\/default\/files\//http:\/\/www.example.com\/sites\/example.com\/files/g (this replaced inline links within the site's content.)
  • Next, after saving the edited file (:wq), I re-imported the database from the edited dump file:
    drush sql-cli -l example.com < example.com.sql
  • And finally, I cleared all caches to clean up: drush -l example.com cc all

Of course, although I use example.com in this write-up, I didn't do this on a live site! Also, take great care writing the vim commands above, because it's easy to make a mistake with all those escaped slashes.

rurri’s picture

I use a similar method to the sql dump and restore, and found that there were many times where these paths were being deserialized from the database, which meant that each reference also had a character count associated with it.

This would have been a huge pain to fix by hand. So my fix was to pick a new path that had the same number of characters as the original path and then make a symbolic link.

Ex: replace default/files with df_exam/files
Then create a symbolic link in the sites folder
ln -s example.com df_exam

This solved the issue, and now allows me to merge a bunch of separate sites into a single drupal installation.