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