Author: astaric
Date: Thu Apr 25 10:21:14 2013
New Revision: 1475690

URL: http://svn.apache.org/r1475690
Log:
Move/copy attachment files when migrating attachments during the upgrade.

Modified:
    bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
    bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py

Modified: bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py?rev=1475690&r1=1475689&r2=1475690&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py (original)
+++ bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py Thu Apr 25 
10:21:14 2013
@@ -20,11 +20,13 @@
 
 import copy
 import os
+import shutil
 
 from genshi.builder import tag, Element
 from genshi.core import escape, Markup, unescape
 
 from pkg_resources import resource_filename
+from trac.attachment import Attachment
 from trac.config import Option, PathOption
 from trac.core import Component, TracError, implements, Interface
 from trac.db import Table, Column, DatabaseManager, Index
@@ -284,6 +286,14 @@ class MultiProductSystem(Component):
                 self.log.info("Migrating tickets w/o product to default 
product")
                 db("""UPDATE ticket SET product='%s'
                       WHERE (product IS NULL OR product='')""" % 
DEFAULT_PRODUCT)
+                self._migrate_attachments(
+                    db("""SELECT a.type, a.id, a.filename
+                            FROM attachment a
+                      INNER JOIN ticket t ON a.id = %(t.id)s
+                           WHERE a.type='ticket'
+                       """ % {'t.id':  db.cast('t.id', 'text')}),
+                    to_product=DEFAULT_PRODUCT
+                )
 
                 self.log.info("Migrating ticket tables to a new schema")
                 for table in TICKET_TABLES:
@@ -370,19 +380,39 @@ class MultiProductSystem(Component):
                                           wiki_name=wiki_name,
                                           old_product=wiki_product,
                                           new_product=product.prefix))
+                            self._migrate_attachments(
+                                db("""SELECT type, id, filename
+                                        FROM attachment
+                                       WHERE type='wiki'
+                                         AND id='%s'
+                                         AND product='%s'
+                                   """ % (wiki_name, DEFAULT_PRODUCT)),
+                                    to_product=DEFAULT_PRODUCT,
+                                    copy=True
+                            )
                     else:
                         self.log.info("Moving wiki page '%s' to default 
product", wiki_name)
                         db("""UPDATE wiki
                               SET product='%s'
-                              WHERE name='%s' AND version=%s AND 
product='%s'""" %
-                              (DEFAULT_PRODUCT,
-                               wiki_name, wiki_version, wiki_product))
+                              WHERE name='%s' AND version=%s AND product='%s'
+                           """ % (DEFAULT_PRODUCT,
+                                  wiki_name, wiki_version, wiki_product))
                         db("""UPDATE attachment
                                  SET product='%s'
                                WHERE type='wiki'
                                  AND id='%s'
                                  AND product='%s'
                            """ % (DEFAULT_PRODUCT, wiki_name, wiki_product))
+                        self._migrate_attachments(
+                            db("""SELECT type, id, filename
+                                    FROM attachment
+                                   WHERE type='wiki'
+                                     AND id='%s'
+                                     AND product='%s'
+                               """ % (wiki_name, DEFAULT_PRODUCT)),
+                            to_product=DEFAULT_PRODUCT,
+                        )
+
                 drop_temp_table(temp_table_name)
 
                 # soft link existing repositories to default product
@@ -434,6 +464,39 @@ class MultiProductSystem(Component):
 
             self.env.enable_multiproduct_schema(True)
 
+    def _migrate_attachments(self, attachments, to_product=None, copy=False):
+        for type, id, filename in attachments:
+            old_path = Attachment._get_path(self.env.path, type, id, filename)
+            new_path = self.env.path
+            if to_product:
+                new_path = os.path.join(new_path, 'products', to_product)
+            new_path = Attachment._get_path(new_path, type, id, filename)
+            dirname = os.path.dirname(new_path)
+            if not os.path.exists(old_path):
+                self.log.warning(
+                    "Missing attachment files for %s:%s/%s",
+                    type, id, filename)
+                continue
+            if os.path.exists(new_path):
+                # TODO: Do we want to overwrite?
+                continue
+            try:
+                if not os.path.exists(dirname):
+                    os.makedirs(dirname)
+                if copy:
+                    if hasattr(os, 'link'):
+                        # TODO: It this safe?
+                        os.link(old_path, new_path)
+                    else:
+                        shutil.copy(old_path, new_path)
+                else:
+                    os.rename(old_path, new_path)
+            except OSError as err:
+                self.log.warning(
+                    "Could not move attachment %s from %s %s to"
+                    "product @ (%s)",
+                    filename, type, id, str(err)
+                )
 
     # IResourceChangeListener methods
     def match_resource(self, resource):

Modified: bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py?rev=1475690&r1=1475689&r2=1475690&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py (original)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py Thu Apr 25 
10:21:14 2013
@@ -19,14 +19,19 @@
 
 from sqlite3 import OperationalError
 from contextlib import contextmanager
+import os
 import tempfile
 import unittest
+import uuid
 
+from trac.attachment import Attachment, AttachmentAdmin
 from trac.core import Component, implements
 from trac.db import DatabaseManager
 from trac.db.schema import Table, Column
 from trac.env import IEnvironmentSetupParticipant
 from trac.test import Environment
+from trac.ticket import Ticket
+from trac.wiki import WikiPage
 
 from multiproduct.env import ProductEnvironment
 from multiproduct.model import Product
@@ -218,6 +223,41 @@ class EnvironmentUpgradeTestCase(unittes
         self._enable_multiproduct()
         self.env.upgrade()
 
+    def test_upgrading_database_moves_attachment_to_correct_product(self):
+        ticket = self.insert_ticket('ticket')
+        wiki = self.insert_wiki('MyWiki')
+        attachment = self._create_file_with_content('Hello World!')
+        self.add_attachment(ticket.resource, attachment)
+        self.add_attachment(wiki.resource, attachment)
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+        with self.product('@'):
+            attachments = list(
+                Attachment.select(self.env, 'ticket', ticket.id))
+            attachments.extend(
+                Attachment.select(self.env, 'wiki', wiki.name))
+        self.assertEqual(len(attachments), 2)
+        for attachment in attachments:
+            self.assertEqual(attachment.open().read(), 'Hello World!')
+
+    def test_upgrading_database_copies_attachments_for_system_wikis(self):
+        wiki = self.insert_wiki('WikiStart', 'content')
+        self.add_attachment(wiki.resource,
+                            self._create_file_with_content('Hello World!'))
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+        with self.product('@'):
+            attachments = list(
+                Attachment.select(self.env, 'wiki', 'WikiStart'))
+        attachments.extend(Attachment.select(self.env, 'wiki', 'WikiStart'))
+        self.assertEqual(len(attachments), 2)
+        for attachment in attachments:
+            self.assertEqual(attachment.open().read(), 'Hello World!')
+
     def _enable_multiproduct(self):
         self.env.config.set('components', 'multiproduct.*', 'enabled')
         self.env.config.save()
@@ -238,6 +278,13 @@ class EnvironmentUpgradeTestCase(unittes
         for cls in self.enabled_components:
             self.env.compmgr.enabled[cls] = True
 
+    def _create_file_with_content(self, content):
+        filename = str(uuid.uuid4())[:6]
+        path = os.path.join(self.env_path, filename)
+        with open(path, 'wb') as f:
+            f.write(content)
+        return path
+
     @contextmanager
     def assertFailsWithMissingTable(self):
         with self.assertRaises(OperationalError) as cm:
@@ -250,6 +297,43 @@ class EnvironmentUpgradeTestCase(unittes
             yield
         self.assertIn('no such column', str(cm.exception))
 
+    def create_ticket(self, summary, **kw):
+        ticket = Ticket(self.env)
+        ticket["summary"] = summary
+        for k, v in kw.items():
+            ticket[k] = v
+        return ticket
+
+    def insert_ticket(self, summary, **kw):
+        """Helper for inserting a ticket into the database"""
+        ticket = self.create_ticket(summary, **kw)
+        ticket.insert()
+        return ticket
+
+    def create_wiki(self, name, text,  **kw):
+        page = WikiPage(self.env, name)
+        page.text = text
+        for k, v in kw.items():
+            page[k] = v
+        return page
+
+    def insert_wiki(self, name, text = None, **kw):
+        text = text or "Dummy text"
+        page = self.create_wiki(name, text, **kw)
+        page.save("dummy author", "dummy comment", "::1")
+        return page
+
+    def add_attachment(self, resource, path):
+        resource = '%s:%s' % (resource.realm, resource.id)
+        AttachmentAdmin(self.env)._do_add(resource, path)
+
+    @contextmanager
+    def product(self, prefix):
+        old_env = self.env
+        self.env = ProductEnvironment(self.env, prefix)
+        yield
+        self.env = old_env
+
 
 class DummyPlugin(Component):
     implements(IEnvironmentSetupParticipant)


Reply via email to