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)