Cope with variations in autopackage metadata format.
[0publish.git] / archive.py
blob9901c2eb3456c44e2f342a20a7032d0157cf3121
1 from xml.dom import minidom
2 from zeroinstall import SafeException
3 from zeroinstall.zerostore import Store, manifest
4 try:
5 from zeroinstall.zerostore import unpack
6 except ImportError:
7 # Older versions don't have it
8 import unpack
9 from zeroinstall.injector import namespaces
10 import os, time, re, shutil, tempfile
12 def manifest_for_dir(dir, alg):
13 if alg == 'sha1':
14 # (for older versions of the injector)
15 import sha
16 class SHA1:
17 def new_digest(self): return sha.new()
18 def generate_manifest(self, dir): return manifest.generate_manifest(dir)
19 def getID(self, digest): return 'sha1=' + digest.hexdigest()
20 algorithm = SHA1()
21 else:
22 algorithm = manifest.get_algorithm(alg)
24 digest = algorithm.new_digest()
25 for line in algorithm.generate_manifest(dir):
26 digest.update(line + '\n')
27 return algorithm.getID(digest)
29 def autopackage_get_start_offset(package):
30 for line in file(package):
31 if line.startswith('export dataSize=') or line.startswith('export data_size='):
32 return os.path.getsize(package) - int(line.split('"', 2)[1])
33 raise Exception("Can't find payload in autopackage (missing 'dataSize')")
35 def add_archive(data, url, local_file, extract, alg):
36 if local_file is None:
37 local_file = os.path.abspath(os.path.basename(url))
38 if not os.path.exists(local_file):
39 raise SafeException("Use --archive-file option to specify a local copy of the archive "
40 "(default file '%s' does not exist)" % local_file)
42 doc = minidom.parseString(data)
44 if alg is None:
45 if local_file.endswith('.deb') or local_file.endswith('.zip') or \
46 local_file.endswith('.package'):
47 # These require 0launch >= 0.20 anyway, so use the new hash to avoid
48 # problems with directory mtimes
49 alg = 'sha1new'
50 else:
51 alg = 'sha1'
53 if local_file.endswith('.package'):
54 start_offset = autopackage_get_start_offset(local_file)
55 type = 'application/x-bzip-compressed-tar'
56 else:
57 start_offset = 0
58 type = None
60 all_impls = doc.documentElement.getElementsByTagNameNS(namespaces.XMLNS_IFACE, 'implementation')
61 tmpdir = tempfile.mkdtemp('-0publish')
62 try:
63 if start_offset or type:
64 unpack.unpack_archive(url, file(local_file), tmpdir, extract, start_offset = start_offset, type = type)
65 else:
66 unpack.unpack_archive(url, file(local_file), tmpdir, extract)
67 if extract:
68 extracted = os.path.join(tmpdir, extract)
69 else:
70 extracted = tmpdir
72 archive_id = manifest_for_dir(extracted, alg)
73 finally:
74 shutil.rmtree(tmpdir)
76 local_ifaces = []
77 for impl in all_impls:
78 this_id = impl.getAttribute('id')
79 if this_id == archive_id:
80 break
81 if this_id.startswith('/') or this_id.startswith('.'):
82 local_ifaces.append(impl)
83 else:
84 if len(local_ifaces) == 0:
85 raise Exception('Nothing with id "%s", and no local implementations' % archive_id)
86 if len(local_ifaces) > 1:
87 raise Exception('Nothing with id "%s", and multiple local implementations!' % archive_id)
88 impl = local_ifaces[0]
89 impl.setAttribute('id', archive_id)
91 assert impl.getAttribute('id') == archive_id
93 nl = doc.createTextNode('\n ')
94 impl.appendChild(nl)
96 archive = doc.createElementNS(namespaces.XMLNS_IFACE, 'archive')
97 impl.appendChild(archive)
98 archive.setAttribute('href', url)
99 archive.setAttribute('size', str(os.stat(local_file).st_size - start_offset))
100 if extract is not None:
101 archive.setAttribute('extract', extract)
102 if start_offset:
103 archive.setAttribute('start-offset', str(start_offset))
104 if type:
105 archive.setAttribute('type', type)
107 nl = doc.createTextNode('\n ')
108 impl.appendChild(nl)
110 return doc.toxml()