From 2d48fed58947874f583ec83db1e383e8c4fc5199 Mon Sep 17 00:00:00 2001 From: Tim Diels Date: Thu, 2 Jun 2011 14:21:22 +0200 Subject: [PATCH] Added ability to generate implementation ids on feed creation --- tests/testdownload.py | 22 +++++++++++++++++ zeroinstall/injector/model.py | 57 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/tests/testdownload.py b/tests/testdownload.py index eddca16..22b9bc8 100755 --- a/tests/testdownload.py +++ b/tests/testdownload.py @@ -269,6 +269,28 @@ class TestDownload(BaseTest): if "Downloaded archive has incorrect size" not in str(ex): raise ex + def testImplementationGenerateMissingId(self): + old_out = sys.stdout + try: + sys.stdout = StringIO() + self.child = server.handle_requests(('HelloWorld.tgz')) + + from zeroinstall.injector import qdom + root = qdom.parse(file(os.path.abspath('ImplementationNoId.xml'))) + + from zeroinstall.zerostore import manifest + alg = manifest.get_algorithm('sha1') + assert alg + + from zeroinstall.injector.model import ZeroInstallFeed + feed = ZeroInstallFeed(root, None, None, alg, self.config.fetcher, self.config.stores) + + expected_id = 'sha1=3ce644dc725f1d21cfcf02562c76f375944b266a' + assert feed.implementations[expected_id] + assert feed.implementations[expected_id].id == expected_id + finally: + sys.stdout = old_out + def testRecipe(self): old_out = sys.stdout try: diff --git a/zeroinstall/injector/model.py b/zeroinstall/injector/model.py index aecf19d..b5fb04b 100644 --- a/zeroinstall/injector/model.py +++ b/zeroinstall/injector/model.py @@ -654,7 +654,6 @@ class Implementation(object): 'id', 'feed', 'version', 'released', 'bindings', 'machine'] def __init__(self, feed, id): - assert id self.feed = feed self.id = id self.user_stability = None @@ -774,7 +773,8 @@ class ZeroInstallImplementation(Implementation): def __init__(self, feed, id, local_path): """id can be a local path (string starting with /) or a manifest hash (eg "sha1=XXX")""" - assert not id.startswith('package:'), id + if id: + assert not id.startswith('package:'), id Implementation.__init__(self, feed, id) self.size = None self.os = None @@ -782,8 +782,13 @@ class ZeroInstallImplementation(Implementation): self.local_path = local_path @staticmethod - def fromDOM(feed, item, item_attrs, local_dir, commands, bindings, depends): - """Make an implementation from a DOM implementation element.""" + def fromDOM(feed, item, item_attrs, local_dir, commands, bindings, depends, id_generation_alg=None, fetcher=None, stores=None): + """Make an implementation from a DOM implementation element. + @param id_generation_alg: if specified, id will be autogenerated, if id is None, with this alg + @type id_generation_alg: L{Algorithm} + @param fetcher: must be specified if id_generation_alg is specified + @param stores: must be specified if id_generation_alg is specified + """ id = item.getAttribute('id') local_path = item_attrs.get('local-path') if local_dir and local_path: @@ -795,7 +800,7 @@ class ZeroInstallImplementation(Implementation): impl = ZeroInstallImplementation(feed, id, id) else: impl = ZeroInstallImplementation(feed, id, None) - if '=' in id: + if id and '=' in id: # In older feeds, the ID was the (single) digest impl.digests.append(id) @@ -846,7 +851,11 @@ class ZeroInstallImplementation(Implementation): if recipe: impl.download_sources.append(recipe) - if id is None: + if id is None and id_generation_alg: + assert fetcher + assert stores + impl.id = impl._generate_digest(fetcher, stores, id_generation_alg) + if impl.id is None: raise InvalidInterface(_("Missing 'id' attribute on %s") % item) return impl @@ -858,6 +867,7 @@ class ZeroInstallImplementation(Implementation): def add_download_source(self, url, size, extract, start_offset = 0, type = None): """Add a download source.""" + # TODO should deprecate? self.download_sources.append(DownloadSource(self, url, size, extract, start_offset, type)) def set_arch(self, arch): @@ -889,6 +899,29 @@ class ZeroInstallImplementation(Implementation): else: return None + def _generate_digest(self, fetcher, stores, alg): + digest = None + + # Create an empty directory for the new implementation + store = stores.stores[0] + tmpdir = store.get_tmp_dir_for('missing') + + try: + blocker = self.best_download_source.retrieve(fetcher, tmpdir, force=False, impl_hint = self) + tasks.wait_for_blocker(blocker) + + from zeroinstall.zerostore import manifest + manifest.fixup_permissions(tmpdir) + digest = alg.getID(manifest.add_manifest_file(tmpdir, alg)) + finally: + # If unpacking fails, remove the temporary directory + if tmpdir is not None: + from zeroinstall import support + support.ro_rmtree(tmpdir) + + return digest + + def retrieve(self, fetcher, retrieval_method, stores, force = False): best = self.best_digest @@ -1045,11 +1078,16 @@ class ZeroInstallFeed(object): __slots__ = ['url', 'implementations', 'name', 'descriptions', 'first_description', 'summaries', 'first_summary', '_package_implementations', 'last_checked', 'last_modified', 'feeds', 'feed_for', 'metadata'] - def __init__(self, feed_element, local_path = None, distro = None): + def __init__(self, feed_element, local_path = None, distro = None, + implementation_id_alg=None, fetcher=None, stores=None): """Create a feed object from a DOM. @param feed_element: the root element of a feed file @type feed_element: L{qdom.Element} - @param local_path: the pathname of this local feed, or None for remote feeds""" + @param local_path: the pathname of this local feed, or None for remote feeds + @param implementation_id_alg: if specified, missing impl ids will be generated with this alg + @type implementation_id_alg: L{Algorithm} + @param fetcher: cannot be None if implementation_id_alg is specified + @param stores: cannot be None if implementation_id_alg is specified""" self.implementations = {} self.name = None self.summaries = {} # { lang: str } @@ -1180,7 +1218,8 @@ class ZeroInstallFeed(object): if item.name == 'group': process_group(item, item_attrs, depends, bindings, commands) elif item.name == 'implementation': - impl = ZeroInstallImplementation.fromDOM(self, item, item_attrs, local_dir, commands, bindings, depends) + impl = ZeroInstallImplementation.fromDOM(self, item, item_attrs, local_dir, commands, bindings, depends, + implementation_id_alg, fetcher, stores) if impl.id in self.implementations: warn(_("Duplicate ID '%(id)s' in feed '%(feed)s'"), {'id': id, 'feed': self}) self.implementations[impl.id] = impl -- 2.11.4.GIT