Added tooltips to cache explorer.
[zeroinstall.git] / zeroinstall / 0launch-gui / cache.py
blob04aca3cf2e518f1f67fb0ee8113417d6369775aa
1 import os
2 import gtk
4 import gui
5 import help_box
6 from dialog import Dialog
7 from zeroinstall.injector.iface_cache import iface_cache
8 from zeroinstall.injector import basedir, namespaces, model
9 from treetips import TreeTips
11 ITEM = 0
12 SIZE = 1
13 TOOLTIP = 2
15 def pretty_size(size):
16 if size == 0: return ''
17 return gui.pretty_size(size)
19 def size_if_exists(path):
20 "Get the size for a file, or 0 if it doesn't exist."
21 if path and os.path.isfile(path):
22 return os.path.getsize(path)
23 return 0
25 def get_size(path):
26 "Get the size for a directory tree. Get the size from the .manifest if possible."
27 man = os.path.join(path, '.manifest')
28 if os.path.exists(man):
29 size = os.path.getsize(man)
30 for line in file(man):
31 if line[:1] in "XF":
32 size += long(line.split(' ', 4)[3])
33 else:
34 size = 0
35 for root, dirs, files in os.walk(path):
36 for name in files:
37 size += getsize(join(root, name))
38 return size
40 def summary(iface):
41 if iface.summary:
42 return iface.get_name() + ' - ' + iface.summary
43 return iface.get_name()
45 tips = TreeTips()
47 class CacheExplorer(Dialog):
48 def __init__(self):
49 Dialog.__init__(self)
50 self.set_title('Zero Install Cache')
51 self.set_default_size(gtk.gdk.screen_width() / 2, gtk.gdk.screen_height() / 2)
53 # Model
54 self.model = gtk.TreeStore(str, str, str)
55 self.tree_view = gtk.TreeView(self.model)
57 # Find cached implementations
59 unowned = {} # Impl ID -> Store
60 duplicates = []
62 for s in iface_cache.stores.stores:
63 for id in os.listdir(s.dir):
64 if id in unowned:
65 duplicates.append(id)
66 unowned[id] = s
68 ok_interfaces = []
69 error_interfaces = []
70 unused_interfaces = []
72 # Look through cached interfaces for implementation owners
73 all = iface_cache.list_all_interfaces()
74 all.sort()
75 for uri in all:
76 iface_size = 0
77 try:
78 if uri.startswith('/'):
79 cached_iface = uri
80 else:
81 cached_iface = basedir.load_first_cache(namespaces.config_site,
82 'interfaces', model.escape(uri))
83 user_overrides = basedir.load_first_config(namespaces.config_site,
84 namespaces.config_prog,
85 'user_overrides', model.escape(uri))
87 iface_size = size_if_exists(cached_iface) + size_if_exists(user_overrides)
88 iface = iface_cache.get_interface(uri)
89 except Exception, ex:
90 error_interfaces.append((uri, str(ex), iface_size))
91 else:
92 in_cache = []
93 for impl in iface.implementations.values():
94 if impl.id in unowned:
95 impl_path = os.path.join(unowned[impl.id].dir, impl.id)
96 impl_size = get_size(impl_path)
97 in_cache.append((impl, impl_size))
98 del unowned[impl.id]
99 iface_size += impl_size
100 if in_cache:
101 in_cache.sort()
102 ok_interfaces.append((iface, in_cache, iface_size))
103 else:
104 unused_interfaces.append((iface, iface_size))
106 if error_interfaces:
107 total_size = sum([size for uri, ex, size in error_interfaces])
108 iter = self.model.append(None, ["Invalid interfaces (unreadable)",
109 pretty_size(total_size),
110 _("These interfaces exist in the cache but cannot be "
111 "read. You should probably delete them.")])
112 for uri, ex, size in error_interfaces:
113 self.model.append(iter, [uri, pretty_size(size), ex])
115 if unowned:
116 unowned_sizes = []
117 for id in unowned:
118 impl_path = os.path.join(unowned[id].dir, id)
119 unowned_sizes.append((get_size(impl_path), id, impl_path))
120 total_size = sum([size for size, id, path in unowned_sizes])
121 iter = self.model.append(None, ["Unowned implementations and temporary files",
122 pretty_size(total_size),
123 _("These probably aren't needed any longer. You can "
124 "delete them.")])
125 unowned_sizes.sort()
126 for size, id, impl_path in unowned_sizes:
127 self.model.append(iter, [id, pretty_size(size), impl_path])
129 if unused_interfaces:
130 total_size = sum([size for iface, size in unused_interfaces])
131 iter = self.model.append(None, ["Unused interfaces (no versions cached)",
132 pretty_size(total_size),
133 _("These interfaces are cached, but no actual versions "
134 "are present. They might be useful, and they don't "
135 "take up much space.")])
136 unused_interfaces.sort()
137 for iface, size in unused_interfaces:
138 self.model.append(iter, [iface.uri, pretty_size(size), summary(iface)])
140 if ok_interfaces:
141 total_size = sum([size for iface, in_cache, size in ok_interfaces])
142 iter = self.model.append(None, ["Used interfaces", pretty_size(total_size), None])
143 for iface, in_cache, iface_size in ok_interfaces:
144 iter2 = self.model.append(iter,
145 [iface.uri, pretty_size(iface_size), summary(iface)])
146 for impl, size in in_cache:
147 self.model.append(iter2,
148 ['Version %s : %s' % (impl.get_version(), impl.id),
149 pretty_size(size),
150 None])
152 # Tree view
153 swin = gtk.ScrolledWindow()
154 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
155 swin.set_shadow_type(gtk.SHADOW_IN)
156 swin.add(self.tree_view)
157 self.vbox.pack_start(swin, True, True, 0)
158 self.tree_view.set_rules_hint(True)
159 swin.show_all()
161 column = gtk.TreeViewColumn('Item', gtk.CellRendererText(), text = ITEM)
162 column.set_resizable(True)
163 self.tree_view.append_column(column)
165 cell = gtk.CellRendererText()
166 cell.set_property('xalign', 1.0)
167 column = gtk.TreeViewColumn('Size', cell, text = SIZE)
168 self.tree_view.append_column(column)
170 # Tree tooltips
171 def motion(tree_view, ev):
172 if ev.window is not tree_view.get_bin_window():
173 return False
174 pos = tree_view.get_path_at_pos(int(ev.x), int(ev.y))
175 if pos:
176 path = pos[0]
177 row = self.model[path]
178 tip = row[TOOLTIP]
179 if tip:
180 if tip != tips.item:
181 tips.prime(tree_view, tip)
182 else:
183 tips.hide()
184 else:
185 tips.hide()
187 self.tree_view.connect('motion-notify-event', motion)
188 self.tree_view.connect('leave-notify-event', lambda tv, ev: tips.hide())
190 # Responses
192 self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
193 self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
194 self.set_default_response(gtk.RESPONSE_OK)
196 def response(dialog, resp):
197 if resp == gtk.RESPONSE_OK:
198 self.destroy()
199 elif resp == gtk.RESPONSE_HELP:
200 cache_help.display()
201 self.connect('response', response)
203 cache_help = help_box.HelpBox("Cache Explorer Help",
204 ('Overview', """
205 When you run a program using Zero Install, it downloads the program's 'interface' file, \
206 which gives information about which versions of the program are available. This interface \
207 file is stored in the cache to save downloading it next time you run the program.
209 When you have chosen which version (implementation) of the program you want to \
210 run, Zero Install downloads that version and stores it in the cache too. Zero Install lets \
211 you have many different versions of each program on your computer at once. This is useful, \
212 since it lets you use an old version if needed, and different programs may need to use \
213 different versions of libraries in some cases.
215 The cache viewer shows you all the interfaces and implementations in your cache. \
216 This is useful to find versions you don't need anymore, so that you can delete them and \
217 free up some disk space.
219 Note: the cache viewer isn't finished; it doesn't currently let you delete things!"""),
221 ('Invalid interfaces', """
222 The cache viewer gets a list of all interfaces in your cache. However, some may not \
223 be valid; they are shown in the 'Invalid interfaces' section. It should be fine to \
224 delete these. An invalid interface may be caused by a local interface that no longer \
225 exists, by a failed attempt to download an interface (the name ends in '.new'), or \
226 by the interface file format changing since the interface was downloaded."""),
228 ('Unowned implementations and temporary files', """
229 The cache viewer searches through all the interfaces to find out which implementations \
230 they use. If no interface uses an implementation, it is shown in the 'Unowned implementations' \
231 section.
233 Unowned implementations can result from old versions of a program no longer being listed \
234 in the interface file. Temporary files are created when unpacking an implementation after \
235 downloading it. If the archive is corrupted, the unpacked files may be left there. Unless \
236 you are currently unpacking new programs, it should be fine to delete everything in this \
237 section."""),
239 ('Unused interfaces', """
240 An unused interface is one which was downloaded, but you don't have any implementations in \
241 the cache. Since interface files are small, there is little point in deleting them. They may \
242 even be useful in some cases (for example, the injector sometimes checks multiple interfaces \
243 to find a usable version; if you delete one of them then it will have to fetch it again, because \
244 it will forget that it doesn't contain anything useful)."""),
246 ('Used interfaces', """
247 All remaining interfaces are listed in this section. You may wish to delete old versions of \
248 certain programs. Deleting a program which you may later want to run will require it to be downloaded \
249 again. Deleting a version of a program which is currently running may cause it to crash, so be careful!
250 """))