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