class JanitorPage(Gtk.VBox, GuiBuilder): (JANITOR_CHECK, JANITOR_ICON, JANITOR_NAME, JANITOR_DISPLAY, JANITOR_PLUGIN, JANITOR_SPINNER_ACTIVE, JANITOR_SPINNER_PULSE) = range(7) (RESULT_CHECK, RESULT_ICON, RESULT_NAME, RESULT_DISPLAY, RESULT_DESC, RESULT_PLUGIN, RESULT_CRUFT) = range(7) max_janitor_view_width = 0 def __init__(self): GObject.GObject.__init__(self) self.scan_tasks = [] self.clean_tasks = [] self._total_count = 0 self.set_border_width(6) GuiBuilder.__init__(self, 'janitorpage.ui') self.autoscan_setting = GSetting('com.ubuntu-tweak.janitor.auto-scan') self.autoscan_setting.connect_notify(self.on_autoscan_button_toggled) self.plugins_setting = GSetting('com.ubuntu-tweak.janitor.plugins') self.view_width_setting = GSetting('com.ubuntu-tweak.janitor.janitor-view-width') self.pack_start(self.vbox1, True, True, 0) self.connect('realize', self.setup_ui_tasks) self.janitor_view.get_selection().connect('changed', self.on_janitor_selection_changed) self.plugins_setting.connect_notify(self.update_model, True) self.show() def on_move_handle(self, widget, gproperty): log.debug("on_move_handle: %d", widget.get_property('position')) self.view_width_setting.set_value(widget.get_property('position')) # cancel the size request, or it will fail to resize # TODO why the first scan will make it fail? self.janitor_view.set_size_request(self.max_janitor_view_width, -1) def is_auto_scan(self): return self.autoscan_setting.get_value() @log_func(log) def on_result_view_row_activated(self, treeview, path, column): iter = self.result_model.get_iter(path) cruft = self.result_model[iter][self.RESULT_CRUFT] display = self.result_model[iter][self.RESULT_DISPLAY] if 'red' in display: plugin = self.result_model[iter][self.RESULT_PLUGIN] error = plugin.get_property('error') self.result_model[iter][self.RESULT_DISPLAY] = '<span color="red"><b>%s</b></span>' % error elif hasattr(cruft, 'get_path'): path = cruft.get_path() if not os.path.isdir(path): path = os.path.dirname(path) os.system("xdg-open '%s' &" % path) def setup_ui_tasks(self, widget): self.janitor_model.set_sort_column_id(self.JANITOR_NAME, Gtk.SortType.ASCENDING) #add janitor columns janitor_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.on_janitor_check_button_toggled) janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'active', self.JANITOR_CHECK) self.janitor_view.append_column(janitor_column) janitor_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererPixbuf() janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'pixbuf', self.JANITOR_ICON) janitor_column.set_cell_data_func(renderer, self.icon_column_view_func, self.JANITOR_ICON) renderer = Gtk.CellRendererText() renderer.set_property('ellipsize', Pango.EllipsizeMode.END) janitor_column.pack_start(renderer, True) janitor_column.add_attribute(renderer, 'markup', self.JANITOR_DISPLAY) renderer = Gtk.CellRendererSpinner() janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'active', self.JANITOR_SPINNER_ACTIVE) janitor_column.add_attribute(renderer, 'pulse', self.JANITOR_SPINNER_PULSE) self.janitor_view.append_column(janitor_column) #end janitor columns #new result columns result_display_renderer = self.builder.get_object('result_display_renderer') result_display_renderer.set_property('ellipsize', Pango.EllipsizeMode.END) result_icon_renderer= self.builder.get_object('result_icon_renderer') self.result_column.set_cell_data_func(result_icon_renderer, self.icon_column_view_func, self.RESULT_ICON) #end new result columns auto_scan = self.autoscan_setting.get_value() log.info("Auto scan status: %s", auto_scan) self.scan_button.set_visible(not auto_scan) self.update_model() self._expand_janitor_view() self.hpaned1.connect('notify::position', self.on_move_handle) def _expand_janitor_view(self): self.janitor_view.expand_all() left_view_width = self.view_width_setting.get_value() log.debug("left_view_width is: %d, max_janitor_view_width is: %d" % (left_view_width, self.max_janitor_view_width)) if left_view_width: self.janitor_view.set_size_request(left_view_width, -1) elif self.max_janitor_view_width: self.janitor_view.set_size_request(self.max_janitor_view_width, -1) def set_busy(self): self.get_parent_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def unset_busy(self): self.get_parent_window().set_cursor(None) def on_janitor_selection_changed(self, selection): model, iter = selection.get_selected() if iter: if self.janitor_model.iter_has_child(iter): iter = self.janitor_model.iter_children(iter) plugin = model[iter][self.JANITOR_PLUGIN] for row in self.result_model: if row[self.RESULT_PLUGIN] == plugin: self.result_view.get_selection().select_path(row.path) log.debug("scroll_to_cell: %s" % row.path) self.result_view.scroll_to_cell(row.path) def _is_scanning_or_cleaning(self): for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_SPINNER_ACTIVE]: return True else: return False def on_janitor_check_button_toggled(self, cell, path): self.result_view.show() self.happy_box.hide() iter = self.janitor_model.get_iter(path) if self._is_scanning_or_cleaning(): return checked = not self.janitor_model[iter][self.JANITOR_CHECK] if self.janitor_model.iter_has_child(iter): child_iter = self.janitor_model.iter_children(iter) while child_iter: self.janitor_model[child_iter][self.JANITOR_CHECK] = checked child_iter = self.janitor_model.iter_next(child_iter) self.janitor_model[iter][self.JANITOR_CHECK] = checked self._check_child_is_all_the_same(self.janitor_model, iter, self.JANITOR_CHECK, checked) if self.is_auto_scan(): self._auto_scan_cruft(iter, checked) def _update_clean_button_sensitive(self): self.clean_button.set_sensitive(False) for row in self.result_model: for child_row in row.iterchildren(): if child_row[self.RESULT_CHECK]: self.clean_button.set_sensitive(True) break def on_result_check_renderer_toggled(self, cell, path): iter = self.result_model.get_iter(path) checked = self.result_model[iter][self.RESULT_CHECK] if self._is_scanning_or_cleaning(): return if self.result_model.iter_has_child(iter): child_iter = self.result_model.iter_children(iter) while child_iter: self.result_model[child_iter][self.RESULT_CHECK] = not checked child_iter = self.result_model.iter_next(child_iter) self.result_model[iter][self.RESULT_CHECK] = not checked self._check_child_is_all_the_same(self.result_model, iter, self.RESULT_CHECK, not checked) self._update_clean_button_sensitive() def _check_child_is_all_the_same(self, model, iter, column_id, status): iter = model.iter_parent(iter) if iter: child_iter = model.iter_children(iter) while child_iter: if status != model[child_iter][column_id]: model[iter][column_id] = False break child_iter = model.iter_next(child_iter) else: model[iter][column_id] = status def on_scan_button_clicked(self, widget=None): self.result_model.clear() self.clean_button.set_sensitive(False) scan_dict = OrderedDict() for row in self.janitor_model: for child_row in row.iterchildren(): checked = child_row[self.JANITOR_CHECK] scan_dict[child_row.iter] = checked self.scan_tasks = list(scan_dict.items()) self._total_count = 0 self.result_view.show() self.happy_box.hide() self.set_busy() self.do_scan_task() def _auto_scan_cruft(self, iter, checked): self.set_busy() scan_dict = OrderedDict() if self.janitor_model.iter_has_child(iter): log.info('Scan cruft for all plugins') #Scan cruft for children child_iter = self.janitor_model.iter_children(iter) while child_iter: scan_dict[child_iter] = checked child_iter = self.janitor_model.iter_next(child_iter) else: scan_dict[iter] = checked self.scan_tasks = list(scan_dict.items()) for plugin_iter, checked in self.scan_tasks: plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] for row in self.result_model: if row[self.RESULT_PLUGIN] == plugin: self.result_model.remove(row.iter) self.do_scan_task() def do_scan_task(self): plugin_iter, checked = self.scan_tasks.pop(0) plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] plugin.set_property('scan_finished', False) log.debug("do_scan_task for %s for status: %s" % (plugin, checked)) if checked: log.info('Scan cruft for plugin: %s' % plugin.get_name()) iter = self.result_model.append(None, (None, None, plugin.get_title(), '<b>%s</b>' % _('Scanning cruft for "%s"...') % plugin.get_title(), None, plugin, None)) self.janitor_model[plugin_iter][self.JANITOR_SPINNER_ACTIVE] = True self.janitor_model[plugin_iter][self.JANITOR_SPINNER_PULSE] = 0 self.janitor_view.scroll_to_cell(self.janitor_model.get_path(plugin_iter)) self._find_handler = plugin.connect('find_object', self.on_find_object, (plugin_iter, iter)) self._scan_handler = plugin.connect('scan_finished', self.on_scan_finished, (plugin_iter, iter)) self._error_handler = plugin.connect('scan_error', self.on_scan_error, (plugin_iter, iter)) t = threading.Thread(target=plugin.get_cruft) GObject.timeout_add(50, self._on_spinner_timeout, plugin_iter, t) t.start() else: # Update the janitor title for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_PLUGIN] == plugin: child_row[self.JANITOR_DISPLAY] = plugin.get_title() if self.scan_tasks: self.do_scan_task() else: if self._total_count == 0: self.result_view.hide() self.happy_box.show() else: self.result_view.show() self.happy_box.hide() self.unset_busy() def _on_spinner_timeout(self, plugin_iter, thread): plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] finished = plugin.get_property('scan_finished') self.janitor_model[plugin_iter][self.JANITOR_SPINNER_PULSE] += 1 if finished: for handler in (self._find_handler, self._scan_handler, self._error_handler): if plugin.handler_is_connected(handler): log.debug("Disconnect the cleaned signal, or it will clean many times: %s" % plugin) plugin.disconnect(handler) self.janitor_model[plugin_iter][self.JANITOR_SPINNER_ACTIVE] = False thread.join() if len(self.scan_tasks) != 0: log.debug("Pending scan tasks: %d" % len(self.scan_tasks)) self.do_scan_task() else: log.debug("total_count is: %d" % self._total_count) if self._total_count == 0: self.result_view.hide() self.happy_box.show() else: self.result_view.show() self.happy_box.hide() self.unset_busy() return not finished @post_ui def on_find_object(self, plugin, cruft, count, iters): while Gtk.events_pending(): Gtk.main_iteration() plugin_iter, result_iter = iters self.result_model.append(result_iter, (False, cruft.get_icon(), cruft.get_name(), cruft.get_name(), cruft.get_size_display(), plugin, cruft)) self.result_view.expand_row(self.result_model.get_path(result_iter), True) # Update the janitor title if count: self.janitor_model[plugin_iter][self.JANITOR_DISPLAY] = "<b>[%d] %s</b>" % (count, plugin.get_title()) else: self.janitor_model[plugin_iter][self.JANITOR_DISPLAY] = "[0] %s" % plugin.get_title() @post_ui def on_scan_finished(self, plugin, result, count, size, iters): plugin.disconnect(self._find_handler) plugin.disconnect(self._scan_handler) plugin.set_property('scan_finished', True) plugin_iter, result_iter = iters if count == 0: self.result_model.remove(result_iter) else: self.result_model[result_iter][self.RESULT_DISPLAY] = "<b>%s</b>" % plugin.get_summary(count) if size != 0: self.result_model[result_iter][self.RESULT_DESC] = "<b>%s</b>" % filesizeformat(size) # Update the janitor title self._total_count += count if count: self.janitor_model[plugin_iter][self.JANITOR_DISPLAY] = "<b>[%d] %s</b>" % (count, plugin.get_title()) self.result_view.collapse_row(self.result_model.get_path(result_iter)) else: self.janitor_model[plugin_iter][self.JANITOR_DISPLAY] = "[0] %s" % plugin.get_title() @post_ui def on_scan_error(self, plugin, error, iters): plugin_iter, result_iter = iters self.janitor_model[plugin_iter][self.JANITOR_ICON] = icon.get_from_name('error', size=16) self.result_model[result_iter][self.RESULT_DISPLAY] = '<span color="red"><b>%s</b></span>' % _('Scan error for "%s", double-click to see details') % plugin.get_title() plugin.set_property('scan_finished', True) plugin.set_property('error', error) @inline_callbacks def on_clean_button_clicked(self, widget): '''plugin_dict: {plugin: {cruft: iter}}''' try: yield PolkitAction(PK_ACTION_CLEAN).do_authenticate() except Exception, e: log.debug(e) return self.plugin_to_run = 0 self.set_busy() self.clean_button.set_sensitive(False) plugin_dict = OrderedDict() for row in self.result_model: plugin = row[self.RESULT_PLUGIN] cruft_dict = OrderedDict() for child_row in row.iterchildren(): checked = child_row[self.RESULT_CHECK] if checked: cruft_dict[child_row[self.RESULT_CRUFT]] = child_row.iter if cruft_dict: plugin_dict[plugin] = cruft_dict self.clean_tasks = list(plugin_dict.items()) self.do_real_clean_task() log.debug("All finished!")
class JanitorPage(Gtk.VBox, GuiBuilder): (JANITOR_CHECK, JANITOR_ICON, JANITOR_NAME, JANITOR_DISPLAY, JANITOR_PLUGIN, JANITOR_SPINNER_ACTIVE, JANITOR_SPINNER_PULSE) = range(7) (RESULT_CHECK, RESULT_ICON, RESULT_NAME, RESULT_DISPLAY, RESULT_DESC, RESULT_PLUGIN, RESULT_CRUFT) = range(7) max_janitor_view_width = 0 def __init__(self): GObject.GObject.__init__(self) self.scan_tasks = [] self.clean_tasks = [] self.set_border_width(6) GuiBuilder.__init__(self, 'janitorpage.ui') self.autoscan_setting = GSetting('com.ubuntu-tweak.tweak.auto-scan') self.janitor_setting = GSetting('com.ubuntu-tweak.tweak.janitor') self.pack_start(self.vbox1, True, True, 0) self.connect('realize', self.setup_ui_tasks) self.janitor_view.get_selection().connect('changed', self.on_janitor_selection_changed) self.janitor_setting.connect_notify(self.update_model, True) self.show() def is_auto_scan(self): return self.autoscan_button.get_active() def setup_ui_tasks(self, widget): self.janitor_model.set_sort_column_id(self.JANITOR_NAME, Gtk.SortType.ASCENDING) #add janitor columns janitor_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.on_janitor_check_button_toggled) janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'active', self.JANITOR_CHECK) self.janitor_view.append_column(janitor_column) janitor_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererPixbuf() janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'pixbuf', self.JANITOR_ICON) janitor_column.set_cell_data_func(renderer, self.icon_column_view_func, self.JANITOR_ICON) renderer = Gtk.CellRendererText() renderer.set_property('ellipsize', Pango.EllipsizeMode.MIDDLE) janitor_column.pack_start(renderer, True) janitor_column.add_attribute(renderer, 'markup', self.JANITOR_DISPLAY) renderer = Gtk.CellRendererSpinner() janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'active', self.JANITOR_SPINNER_ACTIVE) janitor_column.add_attribute(renderer, 'pulse', self.JANITOR_SPINNER_PULSE) self.janitor_view.append_column(janitor_column) #end janitor columns #add result columns result_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.on_result_check_renderer_toggled) result_column.pack_start(renderer, False) result_column.add_attribute(renderer, 'active', self.RESULT_CHECK) renderer = Gtk.CellRendererPixbuf() result_column.pack_start(renderer, False) result_column.add_attribute(renderer, 'pixbuf', self.RESULT_ICON) result_column.set_cell_data_func(renderer, self.icon_column_view_func, self.RESULT_ICON) renderer = Gtk.CellRendererText() renderer.set_property('ellipsize', Pango.EllipsizeMode.END) result_column.pack_start(renderer, True) result_column.add_attribute(renderer, 'markup', self.RESULT_DISPLAY) renderer = Gtk.CellRendererText() result_column.pack_start(renderer, False) result_column.add_attribute(renderer, 'text', self.RESULT_DESC) self.result_view.append_column(result_column) #end result columns auto_scan = self.autoscan_setting.get_value() log.info("Auto scan status: %s", auto_scan) self.scan_button.set_visible(not auto_scan) self.autoscan_button.set_active(auto_scan) self.update_model() self._expand_janitor_view() def _expand_janitor_view(self): self.janitor_view.expand_all() if self.max_janitor_view_width: self.janitor_view.set_size_request(self.max_janitor_view_width, -1) def set_busy(self): self.get_parent_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def unset_busy(self): self.get_parent_window().set_cursor(None) def on_janitor_selection_changed(self, selection): model, iter = selection.get_selected() if iter: if self.janitor_model.iter_has_child(iter): iter = self.janitor_model.iter_children(iter) plugin = model[iter][self.JANITOR_PLUGIN] for row in self.result_model: if row[self.RESULT_PLUGIN] == plugin: self.result_view.get_selection().select_path(row.path) self.result_view.scroll_to_cell(row.path) def on_janitor_check_button_toggled(self, cell, path): iter = self.janitor_model.get_iter(path) for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_SPINNER_ACTIVE]: return checked = not self.janitor_model[iter][self.JANITOR_CHECK] if self.janitor_model.iter_has_child(iter): child_iter = self.janitor_model.iter_children(iter) while child_iter: self.janitor_model[child_iter][self.JANITOR_CHECK] = checked child_iter = self.janitor_model.iter_next(child_iter) self.janitor_model[iter][self.JANITOR_CHECK] = checked self._check_child_is_all_the_same(self.janitor_model, iter, self.JANITOR_CHECK, checked) if self.is_auto_scan(): self._auto_scan_cruft(iter, checked) def _update_clean_button_sensitive(self): self.clean_button.set_sensitive(False) for row in self.result_model: for child_row in row.iterchildren(): if child_row[self.RESULT_CHECK]: self.clean_button.set_sensitive(True) break def on_result_check_renderer_toggled(self, cell, path): iter = self.result_model.get_iter(path) checked = self.result_model[iter][self.RESULT_CHECK] if self.result_model.iter_has_child(iter): child_iter = self.result_model.iter_children(iter) while child_iter: self.result_model[child_iter][self.RESULT_CHECK] = not checked child_iter = self.result_model.iter_next(child_iter) self.result_model[iter][self.RESULT_CHECK] = not checked self._check_child_is_all_the_same(self.result_model, iter, self.RESULT_CHECK, not checked) self._update_clean_button_sensitive() def _check_child_is_all_the_same(self, model, iter, column_id, status): iter = model.iter_parent(iter) if iter: child_iter = model.iter_children(iter) while child_iter: if status != model[child_iter][column_id]: model[iter][column_id] = False break child_iter = model.iter_next(child_iter) else: model[iter][column_id] = status def on_scan_button_clicked(self, widget=None): self.result_model.clear() self.clean_button.set_sensitive(False) scan_dict = OrderedDict() for row in self.janitor_model: for child_row in row.iterchildren(): checked = child_row[self.JANITOR_CHECK] scan_dict[child_row.iter] = checked self.set_busy() self.scan_tasks = list(scan_dict.items()) self.do_scan_task() def _auto_scan_cruft(self, iter, checked): self.set_busy() scan_dict = OrderedDict() if self.janitor_model.iter_has_child(iter): log.info('Scan cruft for all plugins') #Scan cruft for children child_iter = self.janitor_model.iter_children(iter) while child_iter: scan_dict[child_iter] = checked child_iter = self.janitor_model.iter_next(child_iter) else: scan_dict[iter] = checked self.scan_tasks = list(scan_dict.items()) for plugin_iter, checked in self.scan_tasks: plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] for row in self.result_model: if row[self.RESULT_PLUGIN] == plugin: self.result_model.remove(row.iter) self.do_scan_task() def do_scan_task(self): plugin_iter, checked = self.scan_tasks.pop(0) plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] plugin.set_data('scan_finished', False) log.debug("do_scan_task for %s for status: %s" % (plugin, checked)) if checked: log.info('Scan cruft for plugin: %s' % plugin.get_name()) iter = self.result_model.append(None, (None, None, plugin.get_title(), "<b>%s</b>" % plugin.get_title(), None, plugin, None)) self.janitor_model[plugin_iter][self.JANITOR_SPINNER_ACTIVE] = True self.janitor_model[plugin_iter][self.JANITOR_SPINNER_PULSE] = 0 self._find_handler = plugin.connect('find_object', self.on_find_object, iter) self._scan_handler = plugin.connect('scan_finished', self.on_scan_finished, iter) self._error_handler = plugin.connect('error', self.on_scan_error, plugin_iter) t = threading.Thread(target=plugin.get_cruft) GObject.timeout_add(50, self._on_spinner_timeout, plugin_iter, t) t.start() else: # Update the janitor title for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_PLUGIN] == plugin: child_row[self.JANITOR_DISPLAY] = plugin.get_title() if self.scan_tasks: self.do_scan_task() else: self.unset_busy() def _on_spinner_timeout(self, plugin_iter, thread): plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] finished = plugin.get_data('scan_finished') self.janitor_model[plugin_iter][self.JANITOR_SPINNER_PULSE] += 1 if finished: for handler in (self._find_handler, self._scan_handler): if plugin.handler_is_connected(handler): log.debug("Disconnect the cleaned signal, or it will clean many times") plugin.disconnect(handler) self.janitor_model[plugin_iter][self.JANITOR_SPINNER_ACTIVE] = False for view in (self.janitor_view, self.result_view): view.hide() view.show() thread.join() if len(self.scan_tasks) != 0: self.do_scan_task() else: self.unset_busy() return not finished def on_find_object(self, plugin, cruft, result_iter): Gdk.threads_enter() while Gtk.events_pending(): Gtk.main_iteration() self.result_model.append(result_iter, (False, cruft.get_icon(), cruft.get_name(), cruft.get_name(), cruft.get_size_display(), plugin, cruft)) self.result_view.expand_row(self.result_model.get_path(result_iter), True) Gdk.threads_leave() def on_scan_finished(self, plugin, result, count, size, result_iter): Gdk.threads_enter() plugin.disconnect(self._find_handler) plugin.disconnect(self._scan_handler) plugin.set_data('scan_finished', True) if count == 0: self.result_model.remove(result_iter) else: self.result_model[result_iter][self.RESULT_DISPLAY] = "<b>%s</b>" % plugin.get_summary(count, size) # Update the janitor title for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_PLUGIN] == plugin: if count: child_row[self.JANITOR_DISPLAY] = "<b>%s (%d) </b>" % (plugin.get_title(), count) else: child_row[self.JANITOR_DISPLAY] = "%s (%d)" % (plugin.get_title(), count) Gdk.threads_leave() def on_scan_error(self, plugin, error, plugin_iter): Gdk.threads_enter() #TODO deal with the error self.janitor_model[plugin_iter][self.JANITOR_ICON] = icon.get_from_name('error', size=16) plugin.set_data('scan_finished', True) self.scan_tasks = [] Gdk.threads_leave() def on_clean_button_clicked(self, widget): self.plugin_to_run = 0 plugin_dict = OrderedDict() for row in self.result_model: plugin = row[self.RESULT_PLUGIN] cruft_list = [] for child_row in row.iterchildren(): checked = child_row[self.RESULT_CHECK] if checked: cruft = child_row[self.RESULT_CRUFT] cruft_list.append(cruft) if cruft_list: plugin_dict[plugin] = cruft_list self.clean_tasks = list(plugin_dict.items()) self.do_real_clean_task() log.debug("All finished!") def do_real_clean_task(self): plugin, cruft_list = self.clean_tasks.pop(0) log.debug("Call %s to clean cruft" % plugin) self._plugin_handler = plugin.connect('cleaned', self.on_plugin_cleaned) self._error_handler = plugin.connect('error', self.on_clean_error) plugin.clean_cruft(self.get_toplevel(), cruft_list) def on_plugin_cleaned(self, plugin, cleaned): for handler in (self._plugin_handler, self._error_handler): if plugin.handler_is_connected(handler): log.debug("Disconnect the cleaned signal, or it will clean many times") plugin.disconnect(handler) if len(self.clean_tasks) == 0: self.on_scan_button_clicked() else: GObject.timeout_add(300, self.do_real_clean_task) def on_clean_error(self, plugin, error): self.clean_tasks = [] def on_autoscan_button_toggled(self, widget): if widget.get_active(): self.autoscan_setting.set_value(True) self.scan_button.hide() else: self.autoscan_setting.set_value(False) self.scan_button.show() def icon_column_view_func(self, cell_layout, renderer, model, iter, id): if model[iter][id] == None: renderer.set_property("visible", False) else: renderer.set_property("visible", True) def update_model(self, a=None, b=None, expand=False): self.janitor_model.clear() self.result_model.clear() size_list = [] loader = ModuleLoader('janitor') plugin_to_load = self.janitor_setting.get_value() system_text = _('System') iter = self.janitor_model.append(None, (None, icon.get_from_name('ubuntu-logo'), system_text, "<b><big>%s</big></b>" % system_text, None, None, None)) for plugin in loader.get_modules_by_category('system'): if plugin.is_user_extension() and plugin.get_name() not in plugin_to_load: log.debug("User extension: %s not in setting to load" % plugin.get_name()) continue size_list.append(Gtk.Label(label=plugin.get_title()).get_layout().get_pixel_size()[0]) self.janitor_model.append(iter, (False, None, plugin.get_title(), plugin.get_title(), plugin(), None, None)) personal_text = _('Personal') iter = self.janitor_model.append(None, (None, icon.get_from_name('system-users'), personal_text, "<b><big>%s</big></b>" % personal_text, None, None, None)) for plugin in loader.get_modules_by_category('personal'): if plugin.is_user_extension() and plugin.get_name() not in plugin_to_load: log.debug("User extension: %s not in setting to load" % plugin.get_name()) continue size_list.append(Gtk.Label(label=plugin.get_title()).get_layout().get_pixel_size()[0]) self.janitor_model.append(iter, (False, None, plugin.get_title(), plugin.get_title(), plugin(), None, None)) if size_list: self.max_janitor_view_width = max(size_list) + 80 if expand: self._expand_janitor_view()
class FeaturePage(Gtk.ScrolledWindow): __gsignals__ = { 'module_selected': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)) } _categories = None _boxes = [] def __str__(self): return '<FeaturePage: %s>' % self._feature def __init__(self, feature_name): GObject.GObject.__init__(self, hscrollbar_policy=Gtk.PolicyType.NEVER, vscrollbar_policy=Gtk.PolicyType.AUTOMATIC) self.set_property('shadow-type', Gtk.ShadowType.NONE) self.set_border_width(12) self._feature = feature_name self._setting = GSetting('com.ubuntu-tweak.tweak.%s' % feature_name) self._categories = {} self._boxes = [] self._box = Gtk.VBox(spacing=6) viewport = Gtk.Viewport() viewport.set_property('shadow-type', Gtk.ShadowType.NONE) viewport.add(self._box) self.add(viewport) self.load_modules() self.connect('size-allocate', self.rebuild_boxes) self._setting.connect_notify(self.load_modules) self.show_all() def load_modules(self, *args, **kwargs): log.debug("Loading modules...") loader = ModuleLoader(self._feature) self._boxes = [] for child in self._box.get_children(): self._box.remove(child) for category, category_name in loader.get_categories(): modules = loader.get_modules_by_category(category) if modules: module_to_loads = self._setting.get_value() for module in modules: if module.is_user_extension() and module.get_name() not in module_to_loads: modules.remove(module) category_box = CategoryBox(modules=modules, category_name=category_name) self._connect_signals(category_box) self._boxes.append(category_box) self._box.pack_start(category_box, False, False, 0) self.rebuild_boxes() def _connect_signals(self, category_box): for button in category_box.get_buttons(): button.connect('clicked', self.on_button_clicked) def on_button_clicked(self, widget): log.info('Button clicked') module = widget.get_module() self.emit('module_selected', module.get_name()) @log_func(log) def rebuild_boxes(self, widget=None, event=None): request = self.get_allocation() ncols = request.width / 164 # 32 + 120 + 6 + 4 width = ncols * (164 + 2 * 4) + 40 if width > request.width: ncols -= 1 pos = 0 children = self._box.get_children() for box in self._boxes: modules = box.get_modules() if len (modules) == 0: if box in children: self._box.remove(box) else: if box not in children: self._box.pack_start(box, False, False, 0) self._box.reorder_child(box, pos) box.rebuild_table(ncols) pos += 1
class JanitorPage(Gtk.VBox, GuiBuilder): (JANITOR_CHECK, JANITOR_ICON, JANITOR_NAME, JANITOR_DISPLAY, JANITOR_PLUGIN, JANITOR_SPINNER_ACTIVE, JANITOR_SPINNER_PULSE) = range(7) (RESULT_CHECK, RESULT_ICON, RESULT_NAME, RESULT_DISPLAY, RESULT_DESC, RESULT_PLUGIN, RESULT_CRUFT) = range(7) max_janitor_view_width = 0 def __init__(self): GObject.GObject.__init__(self) self.scan_tasks = [] self.clean_tasks = [] self._total_count = 0 self.set_border_width(6) GuiBuilder.__init__(self, 'janitorpage.ui') self.autoscan_setting = GSetting('com.ubuntu-tweak.janitor.auto-scan') self.autoscan_setting.connect_notify(self.on_autoscan_button_toggled) self.plugins_setting = GSetting('com.ubuntu-tweak.janitor.plugins') self.view_width_setting = GSetting( 'com.ubuntu-tweak.janitor.janitor-view-width') self.pack_start(self.vbox1, True, True, 0) self.connect('realize', self.setup_ui_tasks) self.janitor_view.get_selection().connect( 'changed', self.on_janitor_selection_changed) self.plugins_setting.connect_notify(self.update_model, True) self.show() def on_move_handle(self, widget, gproperty): log.debug("on_move_handle: %d", widget.get_property('position')) self.view_width_setting.set_value(widget.get_property('position')) # cancel the size request, or it will fail to resize # TODO why the first scan will make it fail? self.janitor_view.set_size_request(self.max_janitor_view_width, -1) def is_auto_scan(self): return self.autoscan_setting.get_value() @log_func(log) def on_result_view_row_activated(self, treeview, path, column): iter = self.result_model.get_iter(path) cruft = self.result_model[iter][self.RESULT_CRUFT] display = self.result_model[iter][self.RESULT_DISPLAY] if 'red' in display: plugin = self.result_model[iter][self.RESULT_PLUGIN] error = plugin.get_property('error') self.result_model[iter][ self. RESULT_DISPLAY] = '<span color="red"><b>%s</b></span>' % error elif hasattr(cruft, 'get_path'): path = cruft.get_path() if not os.path.isdir(path): path = os.path.dirname(path) os.system("xdg-open '%s' &" % path) def setup_ui_tasks(self, widget): self.janitor_model.set_sort_column_id(self.JANITOR_NAME, Gtk.SortType.ASCENDING) #add janitor columns janitor_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.on_janitor_check_button_toggled) janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'active', self.JANITOR_CHECK) self.janitor_view.append_column(janitor_column) janitor_column = Gtk.TreeViewColumn() renderer = Gtk.CellRendererPixbuf() janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'pixbuf', self.JANITOR_ICON) janitor_column.set_cell_data_func(renderer, self.icon_column_view_func, self.JANITOR_ICON) renderer = Gtk.CellRendererText() renderer.set_property('ellipsize', Pango.EllipsizeMode.END) janitor_column.pack_start(renderer, True) janitor_column.add_attribute(renderer, 'markup', self.JANITOR_DISPLAY) renderer = Gtk.CellRendererSpinner() janitor_column.pack_start(renderer, False) janitor_column.add_attribute(renderer, 'active', self.JANITOR_SPINNER_ACTIVE) janitor_column.add_attribute(renderer, 'pulse', self.JANITOR_SPINNER_PULSE) self.janitor_view.append_column(janitor_column) #end janitor columns #new result columns result_display_renderer = self.builder.get_object( 'result_display_renderer') result_display_renderer.set_property('ellipsize', Pango.EllipsizeMode.END) result_icon_renderer = self.builder.get_object('result_icon_renderer') self.result_column.set_cell_data_func(result_icon_renderer, self.icon_column_view_func, self.RESULT_ICON) #end new result columns auto_scan = self.autoscan_setting.get_value() log.info("Auto scan status: %s", auto_scan) self.scan_button.set_visible(not auto_scan) self.update_model() self._expand_janitor_view() self.hpaned1.connect('notify::position', self.on_move_handle) def _expand_janitor_view(self): self.janitor_view.expand_all() left_view_width = self.view_width_setting.get_value() log.debug("left_view_width is: %d, max_janitor_view_width is: %d" % (left_view_width, self.max_janitor_view_width)) if left_view_width: self.janitor_view.set_size_request(left_view_width, -1) elif self.max_janitor_view_width: self.janitor_view.set_size_request(self.max_janitor_view_width, -1) def set_busy(self): self.get_parent_window().set_cursor( Gdk.Cursor.new(Gdk.CursorType.WATCH)) def unset_busy(self): self.get_parent_window().set_cursor(None) def on_janitor_selection_changed(self, selection): model, iter = selection.get_selected() if iter: if self.janitor_model.iter_has_child(iter): iter = self.janitor_model.iter_children(iter) plugin = model[iter][self.JANITOR_PLUGIN] for row in self.result_model: if row[self.RESULT_PLUGIN] == plugin: self.result_view.get_selection().select_path(row.path) log.debug("scroll_to_cell: %s" % row.path) self.result_view.scroll_to_cell(row.path) def _is_scanning_or_cleaning(self): for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_SPINNER_ACTIVE]: return True else: return False def on_janitor_check_button_toggled(self, cell, path): self.result_view.show() self.happy_box.hide() iter = self.janitor_model.get_iter(path) if self._is_scanning_or_cleaning(): return checked = not self.janitor_model[iter][self.JANITOR_CHECK] if self.janitor_model.iter_has_child(iter): child_iter = self.janitor_model.iter_children(iter) while child_iter: self.janitor_model[child_iter][self.JANITOR_CHECK] = checked child_iter = self.janitor_model.iter_next(child_iter) self.janitor_model[iter][self.JANITOR_CHECK] = checked self._check_child_is_all_the_same(self.janitor_model, iter, self.JANITOR_CHECK, checked) if self.is_auto_scan(): self._auto_scan_cruft(iter, checked) def _update_clean_button_sensitive(self): self.clean_button.set_sensitive(False) for row in self.result_model: for child_row in row.iterchildren(): if child_row[self.RESULT_CHECK]: self.clean_button.set_sensitive(True) break def on_result_check_renderer_toggled(self, cell, path): iter = self.result_model.get_iter(path) checked = self.result_model[iter][self.RESULT_CHECK] if self._is_scanning_or_cleaning(): return if self.result_model.iter_has_child(iter): child_iter = self.result_model.iter_children(iter) while child_iter: self.result_model[child_iter][self.RESULT_CHECK] = not checked child_iter = self.result_model.iter_next(child_iter) self.result_model[iter][self.RESULT_CHECK] = not checked self._check_child_is_all_the_same(self.result_model, iter, self.RESULT_CHECK, not checked) self._update_clean_button_sensitive() def _check_child_is_all_the_same(self, model, iter, column_id, status): iter = model.iter_parent(iter) if iter: child_iter = model.iter_children(iter) while child_iter: if status != model[child_iter][column_id]: model[iter][column_id] = False break child_iter = model.iter_next(child_iter) else: model[iter][column_id] = status def on_scan_button_clicked(self, widget=None): self.result_model.clear() self.clean_button.set_sensitive(False) scan_dict = OrderedDict() for row in self.janitor_model: for child_row in row.iterchildren(): checked = child_row[self.JANITOR_CHECK] scan_dict[child_row.iter] = checked self.scan_tasks = list(scan_dict.items()) self._total_count = 0 self.result_view.show() self.happy_box.hide() self.set_busy() self.do_scan_task() def _auto_scan_cruft(self, iter, checked): self.set_busy() scan_dict = OrderedDict() if self.janitor_model.iter_has_child(iter): log.info('Scan cruft for all plugins') #Scan cruft for children child_iter = self.janitor_model.iter_children(iter) while child_iter: scan_dict[child_iter] = checked child_iter = self.janitor_model.iter_next(child_iter) else: scan_dict[iter] = checked self.scan_tasks = list(scan_dict.items()) for plugin_iter, checked in self.scan_tasks: plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] for row in self.result_model: if row[self.RESULT_PLUGIN] == plugin: self.result_model.remove(row.iter) self.do_scan_task() def do_scan_task(self): plugin_iter, checked = self.scan_tasks.pop(0) plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] plugin.set_property('scan_finished', False) log.debug("do_scan_task for %s for status: %s" % (plugin, checked)) if checked: log.info('Scan cruft for plugin: %s' % plugin.get_name()) iter = self.result_model.append( None, (None, None, plugin.get_title(), '<b>%s</b>' % _('Scanning cruft for "%s"...') % plugin.get_title(), None, plugin, None)) self.janitor_model[plugin_iter][self.JANITOR_SPINNER_ACTIVE] = True self.janitor_model[plugin_iter][self.JANITOR_SPINNER_PULSE] = 0 self.janitor_view.scroll_to_cell( self.janitor_model.get_path(plugin_iter)) self._find_handler = plugin.connect('find_object', self.on_find_object, (plugin_iter, iter)) self._scan_handler = plugin.connect('scan_finished', self.on_scan_finished, (plugin_iter, iter)) self._error_handler = plugin.connect('scan_error', self.on_scan_error, (plugin_iter, iter)) t = threading.Thread(target=plugin.get_cruft) GObject.timeout_add(50, self._on_spinner_timeout, plugin_iter, t) t.start() else: # Update the janitor title for row in self.janitor_model: for child_row in row.iterchildren(): if child_row[self.JANITOR_PLUGIN] == plugin: child_row[self.JANITOR_DISPLAY] = plugin.get_title() if self.scan_tasks: self.do_scan_task() else: if self._total_count == 0: self.result_view.hide() self.happy_box.show() else: self.result_view.show() self.happy_box.hide() self.unset_busy() def _on_spinner_timeout(self, plugin_iter, thread): plugin = self.janitor_model[plugin_iter][self.JANITOR_PLUGIN] finished = plugin.get_property('scan_finished') self.janitor_model[plugin_iter][self.JANITOR_SPINNER_PULSE] += 1 if finished: for handler in (self._find_handler, self._scan_handler, self._error_handler): if plugin.handler_is_connected(handler): log.debug( "Disconnect the cleaned signal, or it will clean many times: %s" % plugin) plugin.disconnect(handler) self.janitor_model[plugin_iter][ self.JANITOR_SPINNER_ACTIVE] = False thread.join() if len(self.scan_tasks) != 0: log.debug("Pending scan tasks: %d" % len(self.scan_tasks)) self.do_scan_task() else: log.debug("total_count is: %d" % self._total_count) if self._total_count == 0: self.result_view.hide() self.happy_box.show() else: self.result_view.show() self.happy_box.hide() self.unset_busy() return not finished @post_ui def on_find_object(self, plugin, cruft, count, iters): while Gtk.events_pending(): Gtk.main_iteration() plugin_iter, result_iter = iters self.result_model.append( result_iter, (False, cruft.get_icon(), cruft.get_name(), cruft.get_name(), cruft.get_size_display(), plugin, cruft)) self.result_view.expand_row(self.result_model.get_path(result_iter), True) # Update the janitor title if count: self.janitor_model[plugin_iter][ self.JANITOR_DISPLAY] = "<b>[%d] %s</b>" % (count, plugin.get_title()) else: self.janitor_model[plugin_iter][ self.JANITOR_DISPLAY] = "[0] %s" % plugin.get_title() @post_ui def on_scan_finished(self, plugin, result, count, size, iters): plugin.disconnect(self._find_handler) plugin.disconnect(self._scan_handler) plugin.set_property('scan_finished', True) plugin_iter, result_iter = iters if count == 0: self.result_model.remove(result_iter) else: self.result_model[result_iter][ self.RESULT_DISPLAY] = "<b>%s</b>" % plugin.get_summary(count) if size != 0: self.result_model[result_iter][ self.RESULT_DESC] = "<b>%s</b>" % filesizeformat(size) # Update the janitor title self._total_count += count if count: self.janitor_model[plugin_iter][ self.JANITOR_DISPLAY] = "<b>[%d] %s</b>" % (count, plugin.get_title()) self.result_view.collapse_row( self.result_model.get_path(result_iter)) else: self.janitor_model[plugin_iter][ self.JANITOR_DISPLAY] = "[0] %s" % plugin.get_title() @post_ui def on_scan_error(self, plugin, error, iters): plugin_iter, result_iter = iters self.janitor_model[plugin_iter][ self.JANITOR_ICON] = icon.get_from_name('error', size=16) self.result_model[result_iter][ self.RESULT_DISPLAY] = '<span color="red"><b>%s</b></span>' % _( 'Scan error for "%s", double-click to see details' ) % plugin.get_title() plugin.set_property('scan_finished', True) plugin.set_property('error', error) @inline_callbacks def on_clean_button_clicked(self, widget): '''plugin_dict: {plugin: {cruft: iter}}''' try: yield PolkitAction(PK_ACTION_CLEAN).do_authenticate() except Exception, e: log.debug(e) return self.plugin_to_run = 0 self.set_busy() self.clean_button.set_sensitive(False) plugin_dict = OrderedDict() for row in self.result_model: plugin = row[self.RESULT_PLUGIN] cruft_dict = OrderedDict() for child_row in row.iterchildren(): checked = child_row[self.RESULT_CHECK] if checked: cruft_dict[child_row[self.RESULT_CRUFT]] = child_row.iter if cruft_dict: plugin_dict[plugin] = cruft_dict self.clean_tasks = list(plugin_dict.items()) self.do_real_clean_task() log.debug("All finished!")