bloodhound-trac / trac / upgrades /

Full commit
# -*- coding: utf-8 -*-
# Copyright (C) 2012 Edgewall Software
# All rights reserved.
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at

import os.path

from trac.attachment import Attachment
from trac.util.text import exception_to_unicode, printerr, unicode_quote
from trac.util.translation import _

def do_upgrade(env, version, cursor):
    """Move attachments from the `attachments` directory into `files`, hashing
    the filenames in the process."""
    path = env.path
    old_dir = os.path.join(path, 'attachments')
    if not os.path.exists(old_dir):
    old_stat = os.stat(old_dir)
    new_dir = os.path.join(path, 'files', 'attachments')
    if not os.path.exists(new_dir):

        SELECT type, id, filename FROM attachment ORDER BY type, id
    for row in cursor:
        move_attachment_file(env, *row)

    # Try to preserve permissions and ownerships of the attachments
    # directory for $ENV/files
    for dir, dirs, files in os.walk(os.path.join(path, 'files')):
            if hasattr(os, 'chmod'):
                os.chmod(dir, old_stat.st_mode)
            if hasattr(os, 'chflags') and hasattr(old_stat, 'st_flags'):
                os.chflags(dir, old_stat.st_flags)
            if hasattr(os, 'chown'):
                os.chown(dir, old_stat.st_uid, old_stat.st_gid)
        except OSError:

    # Remove empty directory hierarchy
        for dir, dirs, files in os.walk(old_dir, topdown=False):
    except OSError, e:
        env.log.warning("Can't delete old attachments directory %s: %s",
                         old_dir, exception_to_unicode(e))
        # TRANSLATOR: Wrap message to 80 columns
The upgrade of attachments was successful, but the old attachments directory:


couldn't be removed, possibly due to the presence of files that weren't
referenced in the database. The error was:

This error can be ignored, but for keeping your environment clean you should
backup any remaining files in that directory and remove it manually.
""", src_dir=old_dir, exception=exception_to_unicode(e)))

def move_attachment_file(env, parent_realm, parent_id, filename):
    old_path = os.path.join(env.path, 'attachments', parent_realm,
    if filename:
        old_path = os.path.join(old_path, unicode_quote(filename))
    old_path = os.path.normpath(old_path)
    if os.path.isfile(old_path):
        new_path = Attachment._get_path(env.path, parent_realm, parent_id,
            os.renames(old_path, new_path)
        except OSError:
            printerr(_("Unable to move attachment from:\n\n"
                       "  %(old_path)s\n\nto:\n\n  %(new_path)s\n",
                       old_path=old_path, new_path=new_path))
        env.log.warning("Can't find file for 'attachment:%s:%s:%s', ignoring",
                        filename, parent_realm, parent_id)