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