Updated to newer Python syntax where possible
[zeroinstall/zeroinstall-afb.git] / zeroinstall / zerostore / cli.py
blob5f9934944197b61fce3e7bf617f58bda110d58f7
1 """Code for the B{0store} command-line interface."""
3 # Copyright (C) 2009, Thomas Leonard
4 # See the README file for details, or visit http://0install.net.
6 from zeroinstall import _
7 import sys, os
8 from zeroinstall.zerostore.manifest import verify, get_algorithm, copy_tree_with_verify
9 from zeroinstall import zerostore, SafeException, support
11 stores = None
13 def init_stores():
14 global stores
15 assert stores is None
16 if stores is None:
17 stores = zerostore.Stores()
19 class UsageError(SafeException): pass
21 def do_manifest(args):
22 """manifest DIRECTORY [ALGORITHM]"""
23 if len(args) < 1 or len(args) > 2: raise UsageError(_("Wrong number of arguments"))
24 if len(args) == 2:
25 alg = get_algorithm(args[1])
26 else:
27 # If no algorithm was given, guess from the directory name
28 name = os.path.basename(args[0])
29 if '=' in name:
30 alg = get_algorithm(name.split('=', 1)[0])
31 else:
32 alg = get_algorithm('sha1new')
33 digest = alg.new_digest()
34 for line in alg.generate_manifest(args[0]):
35 print line
36 digest.update(line + '\n')
37 print alg.getID(digest)
38 sys.exit(0)
40 def do_find(args):
41 """find DIGEST"""
42 if len(args) != 1: raise UsageError(_("Wrong number of arguments"))
43 try:
44 print stores.lookup(args[0])
45 sys.exit(0)
46 except zerostore.BadDigest, ex:
47 print >>sys.stderr, ex
48 except zerostore.NotStored, ex:
49 print >>sys.stderr, ex
50 sys.exit(1)
52 def do_add(args):
53 """add DIGEST (DIRECTORY | (ARCHIVE [EXTRACT]))"""
54 from zeroinstall.zerostore import unpack
55 if len(args) < 2: raise UsageError(_("Missing arguments"))
56 digest = args[0]
57 if os.path.isdir(args[1]):
58 if len(args) > 2: raise UsageError(_("Too many arguments"))
59 stores.add_dir_to_cache(digest, args[1])
60 elif os.path.isfile(args[1]):
61 if len(args) > 3: raise UsageError(_("Too many arguments"))
62 if len(args) > 2:
63 extract = args[2]
64 else:
65 extract = None
67 type = unpack.type_from_url(args[1])
68 if not type:
69 raise SafeException(_("Unknown extension in '%s' - can't guess MIME type") % args[1])
70 unpack.check_type_ok(type)
72 stores.add_archive_to_cache(digest, file(args[1]), args[1], extract, type = type)
73 else:
74 try:
75 os.stat(args[1])
76 except OSError, ex:
77 if ex.errno != 2: # No such file or directory
78 raise UsageError(str(ex)) # E.g. permission denied
79 raise UsageError(_("No such file or directory '%s'") % args[1])
81 def do_optimise(args):
82 """optimise [ CACHE ]"""
83 if len(args) == 1:
84 cache_dir = args[0]
85 else:
86 cache_dir = stores.stores[0].dir
88 cache_dir = os.path.realpath(cache_dir)
90 import stat
91 info = os.stat(cache_dir)
92 if not stat.S_ISDIR(info.st_mode):
93 raise UsageError(_("Not a directory: '%s'") % cache_dir)
95 impl_name = os.path.basename(cache_dir)
96 if impl_name != 'implementations':
97 raise UsageError(_("Cache directory should be named 'implementations', not\n"
98 "'%(name)s' (in '%(cache_dir)s')") % {'name': impl_name, 'cache_dir': cache_dir})
100 print _("Optimising"), cache_dir
102 from . import optimise
103 uniq_size, dup_size, already_linked, man_size = optimise.optimise(cache_dir)
104 print _("Original size : %(size)s (excluding the %(manifest_size)s of manifests)") % {'size': support.pretty_size(uniq_size + dup_size), 'manifest_size': support.pretty_size(man_size)}
105 print _("Already saved : %s") % support.pretty_size(already_linked)
106 if dup_size == 0:
107 print _("No duplicates found; no changes made.")
108 else:
109 print _("Optimised size : %s") % support.pretty_size(uniq_size)
110 perc = (100 * float(dup_size)) / (uniq_size + dup_size)
111 print _("Space freed up : %(size)s (%(percentage).2f%%)") % {'size': support.pretty_size(dup_size), 'percentage': perc}
112 print _("Optimisation complete.")
114 def do_verify(args):
115 """verify (DIGEST | (DIRECTORY [DIGEST])"""
116 if len(args) == 2:
117 required_digest = args[1]
118 root = args[0]
119 elif len(args) == 1:
120 root = get_stored(args[0])
121 required_digest = None # Get from name
122 else:
123 raise UsageError(_("Missing DIGEST or DIRECTORY"))
125 print _("Verifying"), root
126 try:
127 verify(root, required_digest)
128 print _("OK")
129 except zerostore.BadDigest, ex:
130 print str(ex)
131 if ex.detail:
132 print
133 print ex.detail
134 sys.exit(1)
136 def do_audit(args):
137 """audit [DIRECTORY]"""
138 if len(args) == 0:
139 audit_stores = stores.stores
140 else:
141 audit_stores = [zerostore.Store(x) for x in args]
143 audit_ls = []
144 total = 0
145 for a in audit_stores:
146 if os.path.isdir(a.dir):
147 items = sorted(os.listdir(a.dir))
148 audit_ls.append((a.dir, items))
149 total += len(items)
150 elif len(args):
151 raise SafeException(_("No such directory '%s'") % a.dir)
153 verified = 0
154 failures = []
155 i = 0
156 for root, impls in audit_ls:
157 print _("Scanning %s") % root
158 for required_digest in impls:
159 i += 1
160 path = os.path.join(root, required_digest)
161 if '=' not in required_digest:
162 print _("Skipping non-implementation directory %s") % path
163 continue
164 try:
165 msg = _("[%(done)d / %(total)d] Verifying %(digest)s") % {'done': i, 'total': total, 'digest': required_digest}
166 print msg,
167 sys.stdout.flush()
168 verify(path, required_digest)
169 print "\r" + (" " * len(msg)) + "\r",
170 verified += 1
171 except zerostore.BadDigest, ex:
172 print
173 failures.append(path)
174 print str(ex)
175 if ex.detail:
176 print
177 print ex.detail
178 if failures:
179 print '\n' + _("List of corrupted or modified implementations:")
180 for x in failures:
181 print x
182 print
183 print _("Checked %d items") % i
184 print _("Successfully verified implementations: %d") % verified
185 print _("Corrupted or modified implementations: %d") % len(failures)
186 if failures:
187 sys.exit(1)
189 def do_list(args):
190 """list"""
191 if args: raise UsageError(_("List takes no arguments"))
192 print _("User store (writable) : %s") % stores.stores[0].dir
193 for s in stores.stores[1:]:
194 print _("System store : %s") % s.dir
195 if len(stores.stores) < 2:
196 print _("No system stores.")
198 def get_stored(dir_or_digest):
199 if os.path.isdir(dir_or_digest):
200 return dir_or_digest
201 else:
202 try:
203 return stores.lookup(dir_or_digest)
204 except zerostore.NotStored, ex:
205 print >>sys.stderr, ex
206 sys.exit(1)
208 def do_copy(args):
209 """copy SOURCE [ TARGET ]"""
210 if len(args) == 2:
211 source, target = args
212 elif len(args) == 1:
213 source = args[0]
214 target = stores.stores[0].dir
215 else:
216 raise UsageError(_("Wrong number of arguments."))
218 if not os.path.isdir(source):
219 raise UsageError(_("Source directory '%s' not found") % source)
220 if not os.path.isdir(target):
221 raise UsageError(_("Target directory '%s' not found") % target)
222 manifest_path = os.path.join(source, '.manifest')
223 if not os.path.isfile(manifest_path):
224 raise UsageError(_("Source manifest '%s' not found") % manifest_path)
225 required_digest = os.path.basename(source)
226 manifest_data = file(manifest_path, 'rb').read()
228 copy_tree_with_verify(source, target, manifest_data, required_digest)
230 def do_manage(args):
231 """manage"""
232 if args:
233 raise UsageError(_("manage command takes no arguments"))
235 import pygtk
236 pygtk.require('2.0')
237 import gtk
238 from zeroinstall.gtkui import cache
239 from zeroinstall.injector.iface_cache import iface_cache
240 cache_explorer = cache.CacheExplorer(iface_cache)
241 cache_explorer.window.connect('destroy', gtk.main_quit)
242 cache_explorer.show()
243 gtk.main()
245 commands = [do_add, do_audit, do_copy, do_find, do_list, do_manifest, do_optimise, do_verify, do_manage]