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)