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 ROX_IFACE
= 'http://rox.sourceforge.net/2005/interfaces/ROX-Filer'
20 def popup_menu(bev
, obj
):
22 for i
in obj
.menu_items
:
24 item
= gtk
.SeparatorMenuItem()
27 item
= gtk
.MenuItem(name
)
28 item
.connect('activate', lambda item
, cb
=cb
: cb(obj
))
31 menu
.popup(None, None, None, bev
.button
, bev
.time
)
33 def pretty_size(size
):
34 if size
== 0: return ''
35 return gui
.pretty_size(size
)
37 def size_if_exists(path
):
38 "Get the size for a file, or 0 if it doesn't exist."
39 if path
and os
.path
.isfile(path
):
40 return os
.path
.getsize(path
)
44 "Get the size for a directory tree. Get the size from the .manifest if possible."
45 man
= os
.path
.join(path
, '.manifest')
46 if os
.path
.exists(man
):
47 size
= os
.path
.getsize(man
)
48 for line
in file(man
):
50 size
+= long(line
.split(' ', 4)[3])
53 for root
, dirs
, files
in os
.walk(path
):
55 size
+= getsize(join(root
, name
))
60 return iface
.get_name() + ' - ' + iface
.summary
61 return iface
.get_name()
63 def get_selected_paths(tree_view
):
64 "GTK 2.0 doesn't have this built-in"
65 selection
= tree_view
.get_selection()
67 def add(model
, path
, iter):
69 selection
.selected_foreach(add
)
77 class CachedInterface
:
78 def __init__(self
, uri
, size
):
83 if not self
.uri
.startswith('/'):
84 cached_iface
= basedir
.load_first_cache(namespaces
.config_site
,
85 'interfaces', model
.escape(self
.uri
))
87 #print "Delete", cached_iface
88 os
.unlink(cached_iface
)
89 user_overrides
= basedir
.load_first_config(namespaces
.config_site
,
90 namespaces
.config_prog
,
91 'user_overrides', model
.escape(self
.uri
))
93 #print "Delete", user_overrides
94 os
.unlink(user_overrides
)
96 def __cmp__(self
, other
):
97 return self
.uri
.__cmp
__(other
.uri
)
99 class ValidInterface(CachedInterface
):
100 def __init__(self
, iface
, size
, in_cache
):
101 CachedInterface
.__init
__(self
, iface
.uri
, size
)
103 self
.in_cache
= in_cache
105 def append_to(self
, model
, iter):
106 iter2
= model
.append(iter,
107 [self
.uri
, self
.size
, None, summary(self
.iface
), self
])
108 for cached_impl
in self
.in_cache
:
109 cached_impl
.append_to(model
, iter2
)
111 may_delete
= property(lambda self
: not self
.in_cache
)
113 class InvalidInterface(CachedInterface
):
116 def __init__(self
, uri
, ex
, size
):
117 CachedInterface
.__init
__(self
, uri
, size
)
120 def append_to(self
, model
, iter):
121 model
.append(iter, [self
.uri
, self
.size
, None, self
.ex
, self
])
123 class CachedImplementation
:
126 def __init__(self
, cache_dir
, name
):
127 self
.impl_path
= os
.path
.join(cache_dir
, name
)
128 self
.size
= get_size(self
.impl_path
)
132 #print "Delete", self.impl_path
133 shutil
.rmtree(self
.impl_path
)
136 os
.spawnlp(os
.P_WAIT
, '0launch', '0launch', ROX_IFACE
, '-d', self
.impl_path
)
138 menu_items
= [('Open in ROX-Filer', open_rox
)]
140 class UnusedImplementation(CachedImplementation
):
141 def append_to(self
, model
, iter):
142 model
.append(iter, [self
.name
, self
.size
, None, self
.impl_path
, self
])
144 class KnownImplementation(CachedImplementation
):
145 def __init__(self
, cache_dir
, impl
, impl_size
):
146 CachedImplementation
.__init
__(self
, cache_dir
, impl
.id)
148 self
.size
= impl_size
150 def append_to(self
, model
, iter):
152 ['Version %s : %s' % (self
.impl
.get_version(), self
.impl
.id),
157 def __cmp__(self
, other
):
158 if hasattr(other
, 'impl'):
159 return self
.impl
.__cmp
__(other
.impl
)
162 class CacheExplorer(Dialog
):
164 Dialog
.__init
__(self
)
165 self
.set_title('Zero Install Cache')
166 self
.set_default_size(gtk
.gdk
.screen_width() / 2, gtk
.gdk
.screen_height() / 2)
169 self
.model
= gtk
.TreeStore(str, int, str, str, object)
170 self
.tree_view
= gtk
.TreeView(self
.model
)
173 swin
= gtk
.ScrolledWindow()
174 swin
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_ALWAYS
)
175 swin
.set_shadow_type(gtk
.SHADOW_IN
)
176 swin
.add(self
.tree_view
)
177 self
.vbox
.pack_start(swin
, True, True, 0)
178 self
.tree_view
.set_rules_hint(True)
181 column
= gtk
.TreeViewColumn('Item', gtk
.CellRendererText(), text
= ITEM
)
182 column
.set_resizable(True)
183 self
.tree_view
.append_column(column
)
185 cell
= gtk
.CellRendererText()
186 cell
.set_property('xalign', 1.0)
187 column
= gtk
.TreeViewColumn('Size', cell
, text
= PRETTY_SIZE
)
188 self
.tree_view
.append_column(column
)
190 def button_press(tree_view
, bev
):
193 pos
= tree_view
.get_path_at_pos(int(bev
.x
), int(bev
.y
))
196 path
, col
, x
, y
= pos
197 obj
= self
.model
[path
][ITEM_OBJECT
]
198 if obj
and hasattr(obj
, 'menu_items'):
200 self
.tree_view
.connect('button-press-event', button_press
)
203 def motion(tree_view
, ev
):
204 if ev
.window
is not tree_view
.get_bin_window():
206 pos
= tree_view
.get_path_at_pos(int(ev
.x
), int(ev
.y
))
209 row
= self
.model
[path
]
213 tips
.prime(tree_view
, tip
)
219 self
.tree_view
.connect('motion-notify-event', motion
)
220 self
.tree_view
.connect('leave-notify-event', lambda tv
, ev
: tips
.hide())
224 self
.add_button(gtk
.STOCK_HELP
, gtk
.RESPONSE_HELP
)
225 self
.add_button(gtk
.STOCK_CLOSE
, gtk
.RESPONSE_OK
)
226 self
.add_button(gtk
.STOCK_DELETE
, DELETE
)
227 self
.set_default_response(gtk
.RESPONSE_OK
)
229 selection
= self
.tree_view
.get_selection()
230 def selection_changed(selection
):
232 for x
in get_selected_paths(self
.tree_view
):
233 obj
= self
.model
[x
][ITEM_OBJECT
]
234 if obj
is None or not obj
.may_delete
:
235 self
.set_response_sensitive(DELETE
, False)
238 self
.set_response_sensitive(DELETE
, any_selected
)
239 selection
.set_mode(gtk
.SELECTION_MULTIPLE
)
240 selection
.connect('changed', selection_changed
)
241 selection_changed(selection
)
243 def response(dialog
, resp
):
244 if resp
== gtk
.RESPONSE_OK
:
246 elif resp
== gtk
.RESPONSE_HELP
:
250 self
.connect('response', response
)
254 paths
= get_selected_paths(self
.tree_view
)
257 item
= model
[path
][ITEM_OBJECT
]
260 model
.remove(model
.get_iter(path
))
263 def populate_model(self
):
264 # Find cached implementations
266 unowned
= {} # Impl ID -> Store
269 for s
in iface_cache
.stores
.stores
:
270 for id in os
.listdir(s
.dir):
272 duplicates
.append(id)
276 error_interfaces
= []
277 unused_interfaces
= []
279 # Look through cached interfaces for implementation owners
280 all
= iface_cache
.list_all_interfaces()
285 if uri
.startswith('/'):
288 cached_iface
= basedir
.load_first_cache(namespaces
.config_site
,
289 'interfaces', model
.escape(uri
))
290 user_overrides
= basedir
.load_first_config(namespaces
.config_site
,
291 namespaces
.config_prog
,
292 'user_overrides', model
.escape(uri
))
294 iface_size
= size_if_exists(cached_iface
) + size_if_exists(user_overrides
)
295 iface
= iface_cache
.get_interface(uri
)
296 except Exception, ex
:
297 error_interfaces
.append((uri
, str(ex
), iface_size
))
300 for impl
in iface
.implementations
.values():
301 if impl
.id in unowned
:
302 cached_dir
= unowned
[impl
.id].dir
303 impl_path
= os
.path
.join(cached_dir
, impl
.id)
304 impl_size
= get_size(impl_path
)
305 in_cache
.append(KnownImplementation(cached_dir
, impl
, impl_size
))
308 item
= ValidInterface(iface
, iface_size
, in_cache
)
310 ok_interfaces
.append(item
)
312 unused_interfaces
.append(item
)
315 iter = self
.model
.append(None, [_("Invalid interfaces (unreadable)"),
317 _("These interfaces exist in the cache but cannot be "
318 "read. You should probably delete them."),
320 for uri
, ex
, size
in error_interfaces
:
321 item
= InvalidInterface(uri
, ex
, size
)
322 item
.append_to(self
.model
, iter)
325 local_dir
= os
.path
.join(basedir
.xdg_cache_home
, '0install.net', 'implementations')
327 if unowned
[id].dir == local_dir
:
328 impl
= UnusedImplementation(local_dir
, id)
329 unowned_sizes
.append((impl
.size
, impl
))
331 iter = self
.model
.append(None, [_("Unowned implementations and temporary files"),
333 _("These probably aren't needed any longer. You can "
334 "delete them."), None])
336 unowned_sizes
.reverse()
337 for size
, item
in unowned_sizes
:
338 item
.append_to(self
.model
, iter)
340 if unused_interfaces
:
341 iter = self
.model
.append(None, [_("Unused interfaces (no versions cached)"),
343 _("These interfaces are cached, but no actual versions "
344 "are present. They might be useful, and they don't "
345 "take up much space."),
347 unused_interfaces
.sort()
348 for item
in unused_interfaces
:
349 item
.append_to(self
.model
, iter)
352 iter = self
.model
.append(None,
353 [_("Used interfaces"),
355 _("At least one implementation of each of "
356 "these interfaces is in the cache."),
358 for item
in ok_interfaces
:
359 item
.append_to(self
.model
, iter)
362 def update_sizes(self
):
363 """Set PRETTY_SIZE to the total size, including all children."""
366 total
= m
[itr
][SELF_SIZE
]
367 child
= m
.iter_children(itr
)
369 total
+= update(child
)
370 child
= m
.iter_next(child
)
371 m
[itr
][PRETTY_SIZE
] = pretty_size(total
)
373 itr
= m
.get_iter_root()
376 itr
= m
.iter_next(itr
)
378 cache_help
= help_box
.HelpBox("Cache Explorer Help",
380 When you run a program using Zero Install, it downloads the program's 'interface' file, \
381 which gives information about which versions of the program are available. This interface \
382 file is stored in the cache to save downloading it next time you run the program.
384 When you have chosen which version (implementation) of the program you want to \
385 run, Zero Install downloads that version and stores it in the cache too. Zero Install lets \
386 you have many different versions of each program on your computer at once. This is useful, \
387 since it lets you use an old version if needed, and different programs may need to use \
388 different versions of libraries in some cases.
390 The cache viewer shows you all the interfaces and implementations in your cache. \
391 This is useful to find versions you don't need anymore, so that you can delete them and \
392 free up some disk space.
394 Note: the cache viewer isn't finished; it doesn't currently let you delete things!"""),
396 ('Invalid interfaces', """
397 The cache viewer gets a list of all interfaces in your cache. However, some may not \
398 be valid; they are shown in the 'Invalid interfaces' section. It should be fine to \
399 delete these. An invalid interface may be caused by a local interface that no longer \
400 exists, by a failed attempt to download an interface (the name ends in '.new'), or \
401 by the interface file format changing since the interface was downloaded."""),
403 ('Unowned implementations and temporary files', """
404 The cache viewer searches through all the interfaces to find out which implementations \
405 they use. If no interface uses an implementation, it is shown in the 'Unowned implementations' \
408 Unowned implementations can result from old versions of a program no longer being listed \
409 in the interface file. Temporary files are created when unpacking an implementation after \
410 downloading it. If the archive is corrupted, the unpacked files may be left there. Unless \
411 you are currently unpacking new programs, it should be fine to delete everything in this \
414 ('Unused interfaces', """
415 An unused interface is one which was downloaded, but you don't have any implementations in \
416 the cache. Since interface files are small, there is little point in deleting them. They may \
417 even be useful in some cases (for example, the injector sometimes checks multiple interfaces \
418 to find a usable version; if you delete one of them then it will have to fetch it again, because \
419 it will forget that it doesn't contain anything useful)."""),
421 ('Used interfaces', """
422 All remaining interfaces are listed in this section. You may wish to delete old versions of \
423 certain programs. Deleting a program which you may later want to run will require it to be downloaded \
424 again. Deleting a version of a program which is currently running may cause it to crash, so be careful!