def update_dialog(parent, updates): """Updates contains the version numbers and URLs""" import gtk from GuiBasic import open_url dlg = gtk.Dialog(title=_("Update BleachBit"), parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) dlg.set_default_size(250, 125) label = gtk.Label(_("A new version is available.")) dlg.vbox.pack_start(label) for update in updates: ver = update[0] url = update[1] box_update = gtk.HBox() # TRANSLATORS: %s expands to version such as '0.8.4' or '0.8.5beta' or # similar button_stable = gtk.Button(_("Update to version %s") % ver) button_stable.connect( 'clicked', lambda dummy: open_url(url, parent, False)) button_stable.connect('clicked', lambda dummy: dlg.response(0)) box_update.pack_start(button_stable, False, padding=10) dlg.vbox.pack_start(box_update, False) dlg.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) dlg.show_all() dlg.run() dlg.destroy() return False
def open_url(url, parent_window=None, prompt=True): """Open an HTTP URL. Try to run as non-root.""" # drop privileges so the web browser is running as a normal process if 'posix' == os.name and 0 == os.getuid(): msg = _( "Because you are running as root, please manually open this link in a web browser:\n%s") % url message_dialog(None, msg, gtk.MESSAGE_INFO) return if prompt: # find hostname import re ret = re.search('^http(s)?://([a-z\.]+)', url) if None == ret: host = url else: host = ret.group(2) # TRANSLATORS: %s expands to bleachbit.sourceforge.net or similar msg = _("Open web browser to %s?") % host resp = message_dialog(parent_window, msg, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL) if gtk.RESPONSE_OK != resp: return # open web browser if 'nt' == os.name: # in gtk.show_uri() avoid 'glib.GError: No application is registered as # handling this file' import webbrowser webbrowser.open(url) else: gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME)
def execute(self, really_delete): """Make changes and return results""" if FileUtilities.whitelisted(self.path): yield whitelist(self.path) return ret = { # TRANSLATORS: This is the label in the log indicating will be # deleted (for previews) or was actually deleted 'label': _('Delete'), 'n_deleted': 1, 'n_special': 0, 'path': self.path, 'size': FileUtilities.getsize(self.path)} if really_delete: try: FileUtilities.delete(self.path, self.shred) except WindowsError, e: # WindowsError: [Error 32] The process cannot access the file because it is being # used by another process: u'C:\\Documents and # Settings\\username\\Cookies\\index.dat' if 32 != e.winerror and 5 != e.winerror: raise try: Windows.delete_locked_file(self.path) except: raise else: # TRANSLATORS: The file will be deleted when the # system reboots ret['label'] = _('Mark for deletion')
def context_menu_event(self, treeview, event): """When user right clicks on the tree view""" if event.button != 3: return False pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y)) if None == pathinfo: return False path, col, cellx, celly = pathinfo treeview.grab_focus() treeview.set_cursor(path, col, 0) # context menu applies only to children, not parents if 2 != len(path): return False # find the seleted option model = treeview.get_model() option_id = model[path][2] cleaner_id = model[path[0]][2] cleaner_name = model[path[0]][0] # make a menu menu = gtk.Menu() # TRANSLATORS: this is the context menu preview_item = gtk.MenuItem(_("Preview")) preview_item.connect("activate", self.cb_run_option, False, cleaner_id, option_id) menu.append(preview_item) # TRANSLATORS: this is the context menu clean_item = gtk.MenuItem(_("Clean")) clean_item.connect("activate", self.cb_run_option, True, cleaner_id, option_id) menu.append(clean_item) # show the context menu menu.attach_to_widget(treeview, menu.destroy) menu.show_all() menu.popup(None, None, None, event.button, event.time) return True
def delete_confirmation_dialog(parent, mention_preview): """Return boolean whether OK to delete files.""" dialog = gtk.Dialog(title=_("Delete confirmation"), parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) dialog.set_default_size(300, -1) hbox = gtk.HBox(homogeneous=False, spacing=10) icon = gtk.Image() icon.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG) hbox.pack_start(icon, False) if mention_preview: question_text = _( "Are you sure you want to permanently delete files according to the selected operations? The actual files that will be deleted may have changed since you ran the preview.") else: question_text = _( "Are you sure you want to permanently delete these files?") question = gtk.Label(question_text) question.set_line_wrap(True) hbox.pack_start(question, False) dialog.vbox.pack_start(hbox, False) dialog.vbox.set_spacing(10) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button(gtk.STOCK_DELETE, gtk.RESPONSE_ACCEPT) dialog.set_default_response(gtk.RESPONSE_CANCEL) dialog.show_all() ret = dialog.run() dialog.destroy() return ret == gtk.RESPONSE_ACCEPT
def yum_clean(): """Run 'yum clean all' and return size in bytes recovered""" if os.path.exists('/var/run/yum.pid'): msg = _( "%s cannot be cleaned because it is currently running. Close it, and try again.") % "Yum" raise RuntimeError(msg) if not FileUtilities.exe_exists('yum'): raise RuntimeError(_('Executable not found: %s') % 'yum') old_size = FileUtilities.getsizedir('/var/cache/yum') args = ['yum', "--enablerepo=*", 'clean', 'all'] p = subprocess.Popen(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) non_blank_line = "" while True: line = p.stdout.readline().replace("\n", "") if len(line) > 2: non_blank_line = line if -1 != line.find('You need to be root'): # Seen before Fedora 13 raise RuntimeError(line) if -1 != line.find('Cannot remove rpmdb file'): # Since first in Fedora 13 raise RuntimeError(line) if -1 != line.find('Another app is currently holding'): print "debug: yum: '%s'" % line old_size = FileUtilities.getsizedir('/var/cache/yum') if "" == line and p.poll() != None: break print 'debug: yum process return code = %d' % p.returncode if p.returncode > 0: raise RuntimeError(non_blank_line) new_size = FileUtilities.getsizedir('/var/cache/yum') return old_size - new_size
def create_toolbar(self): """Create the toolbar""" toolbar = gtk.Toolbar() # create the preview button preview_icon = gtk.Image() preview_icon.set_from_stock( gtk.STOCK_FIND, gtk.ICON_SIZE_LARGE_TOOLBAR) # TRANSLATORS: This is the preview button on the main window. It # previews changes. preview_button = gtk.ToolButton( icon_widget=preview_icon, label=_p('button', "Preview")) preview_button.connect( "clicked", lambda *dummy: self.preview_or_run_operations(False)) toolbar.insert(preview_button, -1) preview_button.set_tooltip_text( _("Preview files in the selected operations (without deleting any files)")) preview_button.set_is_important(True) # create the delete button icon = gtk.Image() icon.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_LARGE_TOOLBAR) # TRANSLATORS: This is the clean button on the main window. # It makes permanent changes: usually deleting files, sometimes # altering them. run_button = gtk.ToolButton( icon_widget=icon, label=_p("button", "Clean")) run_button.connect("clicked", self.run_operations) toolbar.insert(run_button, -1) run_button.set_tooltip_text( _("Clean files in the selected operations")) run_button.set_is_important(True) return toolbar
def worker_done(self, worker, really_delete): """Callback for when Worker is done""" self.progressbar.set_text("") self.progressbar.set_fraction(1) if not self.stop_now: self.progressbar.set_text(_("Done.")) else: self.progressbar.set_text(_("Stopped.")) self.textview.scroll_mark_onscreen(self.textbuffer.get_insert()) self.set_sensitive(True) # Close the program after cleaning is completed. # if the option is selected under preference. if really_delete: if options.get("exit_done"): sys.exit() # notification for long-running process elapsed = (time.time() - self.start_time) print 'debug: elapsed time: %d seconds' % elapsed if elapsed < 10 or self.window.is_active(): return try: import pynotify except: print "debug: pynotify not available" else: if pynotify.init(APP_NAME): notify = pynotify.Notification('BleachBit', _("Done."), icon='bleachbit') notify.show() notify.set_timeout(10000)
def about(self, __event): """Create and show the about dialog""" if 'nt' != os.name and (2, 16, 6) != gtk.gtk_version: # workaround for broken GTK+ # (https://bugs.launchpad.net/bleachbit/+bug/797012) gtk.about_dialog_set_url_hook(lambda dialog, link: GuiBasic.open_url(link, self.window, False)) dialog = gtk.AboutDialog() dialog.set_comments(_("Program to clean unnecessary files")) dialog.set_copyright("Copyright (C) 2014 Andrew Ziem") try: dialog.set_license(open(license_filename).read()) except: dialog.set_license( _("GNU General Public License version 3 or later.\nSee http://www.gnu.org/licenses/gpl-3.0.txt")) dialog.set_name(APP_NAME) # TRANSLATORS: Maintain the names of translators here. # Launchpad does this automatically for translations # typed in Launchpad. This is a special string shown # in the 'About' box. dialog.set_translator_credits(_("translator-credits")) dialog.set_version(APP_VERSION) dialog.set_website(APP_URL) dialog.set_transient_for(self.window) if appicon_path and os.path.exists(appicon_path): icon = gtk.gdk.pixbuf_new_from_file(appicon_path) dialog.set_logo(icon) dialog.run() dialog.hide()
def args_to_operations(args, preset): """Read arguments and return list of operations""" register_cleaners() operations = {} if preset: # restore presets from the GUI for key in sorted(backends): c_id = backends[key].get_id() for (o_id, o_name) in backends[key].get_options(): if Options.options.get_tree(c_id, o_id): args.append('.'.join([c_id, o_id])) for arg in args: if 2 != len(arg.split('.')): print _("not a valid cleaner: %s") % arg continue (cleaner_id, option_id) = arg.split('.') # enable all options (for example, firefox.*) if '*' == option_id: if operations.has_key(cleaner_id): del operations[cleaner_id] operations[cleaner_id] = [] for (option_id2, o_name) in backends[cleaner_id].get_options(): operations[cleaner_id].append(option_id2) continue # add the specified option if not operations.has_key(cleaner_id): operations[cleaner_id] = [] if not option_id in operations[cleaner_id]: operations[cleaner_id].append(option_id) for (k, v) in operations.iteritems(): operations[k] = sorted(v) return operations
def add_custom_file_cb(button): """Callback for adding a file""" title = _("Choose a file") pathname = GuiBasic.browse_file(self.parent, title) if pathname: for this_pathname in pathnames: if pathname == this_pathname[1]: print "warning: '%s' already exists in whitelist" % pathname return liststore.append([_('File'), pathname]) pathnames.append(['file', pathname]) options.set_custom_paths(pathnames)
def __init__(self): Cleaner.__init__(self) self.options = {} self.add_option('cache', _('Cache'), _('Delete the cache')) self.add_option('recent_documents', _('Most recently used'), _("Delete the list of recently used documents")) # reference: http://katana.oooninja.com/w/editions_of_openoffice.org if 'posix' == os.name: self.prefixes = [ "~/.ooo-2.0", "~/.openoffice.org2", "~/.openoffice.org2.0", "~/.openoffice.org/3" ] self.prefixes += [ "~/.ooo-dev3" ] if 'nt' == os.name: self.prefixes = [ "$APPDATA\\OpenOffice.org\\3", "$APPDATA\\OpenOffice.org2" ]
def add_custom_folder_cb(button): """Callback for adding a folder""" title = _("Choose a folder") pathname = GuiBasic.browse_folder(self.parent, title, multiple=False, stock_button=gtk.STOCK_ADD) if pathname: for this_pathname in pathnames: if pathname == this_pathname[1]: print "warning: '%s' already exists in whitelist" % pathname return liststore.append([_('Folder'), pathname]) pathnames.append(['folder', pathname]) options.set_custom_paths(pathnames)
def __languages_page(self): """Return widget containing the languages page""" def preserve_toggled_cb(cell, path, liststore): """Callback for toggling the 'preserve' column""" __iter = liststore.get_iter_from_string(path) value = not liststore.get_value(__iter, 0) liststore.set(__iter, 0, value) langid = liststore[path][1] options.set_language(langid, value) vbox = gtk.VBox() notice = gtk.Label( _("All languages will be deleted except those checked.")) vbox.pack_start(notice) # populate data liststore = gtk.ListStore('gboolean', str, str) for lang in Unix.locales.iterate_languages(): preserve = options.get_language(lang) native = Unix.locales.native_name(lang) liststore.append([preserve, lang, native]) # create treeview treeview = gtk.TreeView(liststore) # create column views self.renderer0 = gtk.CellRendererToggle() self.renderer0.set_property('activatable', True) self.renderer0.connect('toggled', preserve_toggled_cb, liststore) self.column0 = gtk.TreeViewColumn( _("Preserve"), self.renderer0, active=0) treeview.append_column(self.column0) self.renderer1 = gtk.CellRendererText() self.column1 = gtk.TreeViewColumn(_("Code"), self.renderer1, text=1) treeview.append_column(self.column1) self.renderer2 = gtk.CellRendererText() self.column2 = gtk.TreeViewColumn(_("Name"), self.renderer2, text=2) treeview.append_column(self.column2) treeview.set_search_column(2) # finish swindow = gtk.ScrolledWindow() swindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) swindow.set_size_request(300, 200) swindow.add(treeview) vbox.pack_start(swindow, False) return vbox
def __init__(self): Cleaner.__init__(self) self.options = {} self.add_option("cache", _("Cache"), _("Delete the cache")) self.add_option("recent_documents", _("Most recently used"), _("Delete the list of recently used documents")) self.id = "openofficeorg" self.name = "OpenOffice.org" self.description = _("Office suite") # reference: http://katana.oooninja.com/w/editions_of_openoffice.org if "posix" == os.name: self.prefixes = ["~/.ooo-2.0", "~/.openoffice.org2", "~/.openoffice.org2.0", "~/.openoffice.org/3"] self.prefixes += ["~/.ooo-dev3"] if "nt" == os.name: self.prefixes = ["$APPDATA\\OpenOffice.org\\3", "$APPDATA\\OpenOffice.org2"]
def add_whitelist_folder_cb(button): """Callback for adding a folder""" title = _("Choose a folder") pathname = GuiBasic.browse_folder(self.parent, title, multiple=False, stock_button=gtk.STOCK_ADD) if pathname: for this_pathname in pathnames: if pathname == this_pathname[1]: print "warning: '%s' already exists in whitelist" % pathname return liststore.append([_('Folder'), pathname]) pathnames.append(['folder', pathname]) options.set_whitelist_paths(pathnames)
def create_toolbar(self): """Create the toolbar""" toolbar = gtk.Toolbar() # create the preview button preview_icon = gtk.Image() preview_icon.set_from_stock( gtk.STOCK_FIND, gtk.ICON_SIZE_LARGE_TOOLBAR) # TRANSLATORS: This is the preview button on the main window. It # previews changes. preview_button = gtk.ToolButton( icon_widget=preview_icon, label=_p('button', "Preview")) preview_button.connect( "clicked", lambda *dummy: self.preview_or_run_operations(False)) toolbar.insert(preview_button, -1) preview_button.set_tooltip_text( _("Preview files in the selected operations (without deleting any files)")) preview_button.set_is_important(True) # create the delete button icon = gtk.Image() icon.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_LARGE_TOOLBAR) # TRANSLATORS: This is the clean button on the main window. # It makes permanent changes: usually deleting files, sometimes # altering them. run_button = gtk.ToolButton( icon_widget=icon, label=_p("button", "Clean")) run_button.connect("clicked", self.run_operations) toolbar.insert(run_button, -1) run_button.set_tooltip_text( _("Clean files in the selected operations")) run_button.set_is_important(True) # create the stop icon = gtk.Image() icon.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_LARGE_TOOLBAR) # TRANSLATORS: This is the clean button on the main window. # It makes permanent changes: usually deleting files, sometimes # altering them. run_button = gtk.ToolButton( icon_widget=icon, label=_p("button", "Stop!")) run_button.connect("clicked", self.run_operations_stop) toolbar.insert(run_button, -1) run_button.set_tooltip_text( _("STOP the running operations")) run_button.set_is_important(True) return toolbar
def apt_autoremove(): """Run 'apt-get autoremove' and return the size (un-rounded, in bytes) of freed space""" if not FileUtilities.exe_exists('apt-get'): raise RuntimeError(_('Executable not found: %s') % 'apt-get') args = ['apt-get', '--yes', 'autoremove'] process = subprocess.Popen(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) total_bytes = 0 while True: line = process.stdout.readline().replace("\n", "") if line.startswith('E: '): raise RuntimeError(line) # After this operation, 74.7MB disk space will be freed. match = re.search(r", ([0-9.]+[a-zA-Z]{2}) disk space will be freed", line) if match: pkg_bytes_str = match.groups(0)[0] pkg_bytes = FileUtilities.human_to_bytes(pkg_bytes_str.upper()) total_bytes += pkg_bytes if "" == line and process.poll() != None: break return total_bytes
def execute(self, really_delete): """Execute the Windows registry cleaner""" if 'nt' != os.name: raise StopIteration _str = None # string representation ret = None # return value meaning 'deleted' or 'delete-able' if self.valuename: _str = '%s<%s>' % (self.keyname, self.valuename) ret = Windows.delete_registry_value(self.keyname, self.valuename, really_delete) else: ret = Windows.delete_registry_key(self.keyname, really_delete) _str = self.keyname if not ret: # Nothing to delete or nothing was deleted. This return # makes the auto-hide feature work nicely. raise StopIteration ret = { 'label': _('Delete registry key'), 'n_deleted': 0, 'n_special': 1, 'path': _str, 'size': 0} yield ret
def run_deep_scan(self): """Run deep scans""" print "debug: deepscans=", self.deepscans # TRANSLATORS: The "deep scan" feature searches over broad # areas of the file system such as the user's whole home directory # or all the system executables. self.ui.update_progress_bar(_("Please wait. Running deep scan.")) yield True # allow GTK to update the screen ds = DeepScan.DeepScan() for (path, dsdict) in self.deepscans.iteritems(): print "debug: deepscan path=", path print "debug: deepscan dict=", dsdict for dsdict2 in dsdict: ds.add_search(path, dsdict2["regex"]) for path in ds.scan(): if True == path: yield True continue # fixme: support non-delete commands import Command cmd = Command.Delete(path) for ret in self.execute(cmd): yield True
def create_wipe_cleaner(path): """Wipe free disk space of arbitrary paths (used in GUI)""" cleaner = Cleaner() cleaner.add_option( option_id='free_disk_space', name='', description='') cleaner.name = '' # create a temporary cleaner object display = _("Overwrite free disk space %s") % path def wipe_path_func(): for ret in FileUtilities.wipe_path(path, idle=True): yield ret yield 0 import Action class CustomWipeAction(Action.ActionProvider): action_key = '__customwipeaction' def get_commands(self): yield Command.Function(None, wipe_path_func, display) provider = CustomWipeAction(None) cleaner.add_action('free_disk_space', provider) return cleaner
def create_simple_cleaner(paths): """Shred arbitrary files (used in CLI and GUI)""" cleaner = Cleaner() cleaner.add_option(option_id='files', name='', description='') cleaner.name = _("System") # shows up in progress bar import Action class CustomFileAction(Action.ActionProvider): action_key = '__customfileaction' def get_commands(self): for path in paths: if not isinstance(path, (str, unicode)): raise RuntimeError( 'expected path as string but got %s' % str(path)) if os.path.isdir(path): for child in children_in_directory(path, True): yield Command.Shred(child) yield Command.Shred(path) else: yield Command.Shred(path) provider = CustomFileAction(None) cleaner.add_action('files', provider) return cleaner
def add_section(self, cleaner_id, name): """Add a section (cleaners)""" self.cleaner_ids.append(cleaner_id) self.cleaners[cleaner_id] = Cleaner.Cleaner() self.cleaners[cleaner_id].id = cleaner_id self.cleaners[cleaner_id].name = name self.cleaners[cleaner_id].description = _('Imported from winapp2.ini')
def update_winapp2(url, hash_expected, append_text, cb_success): """Download latest winapp2.ini file. Hash is sha512 or None to disable checks""" # first, determine whether an update is necessary from Common import personal_cleaners_dir fn = os.path.join(personal_cleaners_dir, 'winapp2.ini') delete_current = False if os.path.exists(fn): f = open(fn, 'r') hash_current = hashlib.sha512(f.read()).hexdigest() if not hash_expected or hash_current == hash_expected: # update is same as current return f.close() delete_current = True # download update opener = urllib2.build_opener() opener.addheaders = [('User-Agent', user_agent())] doc = opener.open(url).read() # verify hash hash_actual = hashlib.sha512(doc).hexdigest() if hash_expected and not hash_actual == hash_expected: raise RuntimeError("hash for %s actually %s instead of %s" % (url, hash_actual, hash_expected)) # delete current if delete_current: from FileUtilities import delete delete(fn, True) # write file if not os.path.exists(personal_cleaners_dir): os.mkdir(personal_cleaners_dir) f = open(fn, 'w') f.write(doc) append_text(_('New winapp2.ini was downloaded.')) cb_success()
def execute(self, really_delete): """Execute the Windows registry cleaner""" if 'nt' != os.name: raise StopIteration _str = None # string representation ret = None # return value meaning 'deleted' or 'delete-able' if self.valuename: _str = '%s<%s>' % (self.keyname, self.valuename) ret = Windows.delete_registry_value(self.keyname, self.valuename, really_delete) else: ret = Windows.delete_registry_key(self.keyname, really_delete) _str = self.keyname if not ret: # Nothing to delete or nothing was deleted. This return # makes the auto-hide feature work nicely. raise StopIteration ret = { 'label': _('Delete registry key'), 'n_deleted': 0, 'n_special': 1, 'path': _str, 'size': 0 } yield ret
def handle_cleaner_option_label(self, label): """<label> element under <option>""" self.option_name = _(getText(label.childNodes)) translate = label.getAttribute('translate') translators = label.getAttribute('translators') if not translate or boolstr_to_bool(translate): self.xlate_cb(self.option_name, translators)
def run_cleaner_cmd(cmd, args, freed_space_regex=r'[\d.]+[kMGTE]?B?', error_line_regexes=None): """Runs a specified command and returns how much space was (reportedly) freed. The subprocess shouldn't need any user input and the user should have the necessary rights. freed_space_regex gets applied to every output line, if the re matches, add values captured by the single group in the regex""" if not FileUtilities.exe_exists(cmd): raise RuntimeError(_('Executable not found: %s') % cmd) freed_space_regex = re.compile(freed_space_regex) error_line_regexes = [ re.compile(regex) for regex in error_line_regexes or [] ] output = subprocess.check_output([cmd] + args, stderr=subprocess.STDOUT, universal_newlines=True, env={'LC_ALL': 'C'}) freed_space = 0 for line in output.split('\n'): m = freed_space_regex.match(line) if m is not None: freed_space += FileUtilities.human_to_bytes(m.group(1)) for error_re in error_line_regexes: if error_re.search(line): raise RuntimeError('Invalid output from %s: %s' % (cmd, line)) return freed_space
def preview_or_run_operations(self, really_delete, operations=None): """Preview operations or run operations (delete files)""" assert(isinstance(really_delete, bool)) import Worker self.start_time = None if None == operations: operations = {} for operation in self.get_selected_operations(): operations[operation] = self.get_operation_options(operation) assert(isinstance(operations, dict)) if 0 == len(operations): GuiBasic.message_dialog(self.window, _("You must select an operation"), gtk.MESSAGE_WARNING, gtk.BUTTONS_OK) return try: self.set_sensitive(True) self.textbuffer.set_text("") self.progressbar.show() print "STOP NOW" print self,stop_now self.worker = Worker.Worker(self, really_delete, operations, self.stop_now) except: traceback.print_exc() err = str(sys.exc_info()[1]) self.append_text(err + "\n", 'error') else: self.start_time = time.time() worker = self.worker.run() gobject.idle_add(worker.next)
def set_cleaner(self, path, model, parent_window, value=None): """Activate or deactive option of cleaner.""" if None == value: # if not value given, toggle current value value = not model[path][1] assert(type(value) is types.BooleanType) assert(type(model) is gtk.TreeStore) cleaner_id = None i = path if type(i) is str: # type is either str or gtk.TreeIter i = model.get_iter(path) parent = model.iter_parent(i) if None != parent: # this is an option (child), not a cleaner (parent) cleaner_id = model[parent][2] option_id = model[path][2] if cleaner_id and value: # when toggling an option, present any warnings warning = backends[cleaner_id].get_warning(option_id) # TRANSLATORS: %(cleaner) may be Firefox, System, etc. # %(option) may be cache, logs, cookies, etc. # %(warning) may be 'This option is really slow' msg = _("Warning regarding %(cleaner)s - %(option)s:\n\n%(warning)s") % \ {'cleaner': model[parent][0], 'option': model[path][0], 'warning': warning} if warning: resp = GuiBasic.message_dialog(parent_window, msg, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK_CANCEL) if gtk.RESPONSE_OK != resp: # user cancelled, so don't toggle option return model[path][1] = value
def __init__(self, uac=True, shred_paths=None): if uac and 'nt' == os.name and Windows.elevate_privileges(): # privileges escalated in other process sys.exit(0) import RecognizeCleanerML RecognizeCleanerML.RecognizeCleanerML() register_cleaners() self.create_window() gobject.threads_init() if shred_paths: self.shred_paths(shred_paths) return if options.get("first_start") and 'posix' == os.name: pref = PreferencesDialog(self.window, self.cb_refresh_operations) pref.run() options.set('first_start', False) if online_update_notification_enabled and options.get("check_online_updates"): self.check_online_updates() if 'nt' == os.name: # BitDefender false positive. BitDefender didn't mark BleachBit as infected or show # anything in its log, but sqlite would fail to import unless BitDefender was in "game mode." # http://bleachbit.sourceforge.net/forum/074-fails-errors try: import sqlite3 except ImportError, e: print e print dir(e) self.append_text( _("Error loading the SQLite module: the antivirus software may be blocking it."), 'error')
def diagnostic_dialog(self, parent): """Show diagnostic information""" dialog = gtk.Dialog(_("System information"), parent) dialog.resize(600, 400) txtbuffer = gtk.TextBuffer() import Diagnostic txt = Diagnostic.diagnostic_info() txtbuffer.set_text(txt) textview = gtk.TextView(txtbuffer) textview.set_editable(False) swindow = gtk.ScrolledWindow() swindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) swindow.add_with_viewport(textview) dialog.vbox.pack_start(swindow) dialog.add_buttons( gtk.STOCK_COPY, 100, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) dialog.show_all() while True: rc = dialog.run() if 100 == rc: clipboard = gtk.clipboard_get() clipboard.set_text(txt) else: break dialog.hide()
def apt_autoremove(): """Run 'apt-get autoremove' and return the size (un-rounded, in bytes) of freed space""" if not FileUtilities.exe_exists('apt-get'): raise RuntimeError(_('Executable not found: %s') % 'apt-get') args = ['apt-get', '--yes', 'autoremove'] process = subprocess.Popen(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) total_bytes = 0 while True: line = process.stdout.readline().replace("\n", "") if line.startswith('E: '): raise RuntimeError(line) # After this operation, 74.7MB disk space will be freed. match = re.search( r", ([0-9.]+[a-zA-Z]{2}) disk space will be freed", line) if match: pkg_bytes_str = match.groups(0)[0] pkg_bytes = FileUtilities.human_to_bytes(pkg_bytes_str.upper()) total_bytes += pkg_bytes if "" == line and process.poll() != None: break return total_bytes
def apt_autoclean(): """Run 'apt-get autoclean' and return the size (un-rounded, in bytes) of freed space""" if not FileUtilities.exe_exists('apt-get'): raise RuntimeError(_('Executable not found: %s') % 'apt-get') args = ['apt-get', 'autoclean'] process = subprocess.Popen(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) total_bytes = 0 while True: line = process.stdout.readline().replace("\n", "") if line.startswith('E: '): raise RuntimeError(line) # Del cups-common 1.3.9-17ubuntu3 [1165kB] match = re.search("^Del .*\[([0-9.]+[a-zA-Z]{2})\]", line) if match: pkg_bytes_str = match.groups(0)[0] pkg_bytes = FileUtilities.human_to_bytes(pkg_bytes_str.upper()) total_bytes += pkg_bytes if "" == line and process.poll() != None: break return total_bytes
def get_commands(self, option_id): # paths for which to run expand_glob_join egj = [] if 'recent_documents' == option_id: egj.append( "user/registry/data/org/openoffice/Office/Histories.xcu") egj.append( "user/registry/cache/org.openoffice.Office.Histories.dat") if 'recent_documents' == option_id and not 'cache' == option_id: egj.append("user/registry/cache/org.openoffice.Office.Common.dat") for egj_ in egj: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, egj_): if 'nt' == os.name: path = os.path.normpath(path) if os.path.lexists(path): yield Command.Delete(path) if 'cache' == option_id: dirs = [] for prefix in self.prefixes: dirs += FileUtilities.expand_glob_join( prefix, "user/registry/cache/") for dirname in dirs: if 'nt' == os.name: dirname = os.path.normpath(dirname) for filename in children_in_directory(dirname, False): yield Command.Delete(filename) if 'recent_documents' == option_id: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, "user/registry/data/org/openoffice/Office/Common.xcu"): if os.path.lexists(path): yield Command.Function(path, Special.delete_ooo_history, _('Delete the usage history')) # ~/.openoffice.org/3/user/registrymodifications.xcu # Apache OpenOffice.org 3.4.1 from openoffice.org on Ubuntu 13.04 # %AppData%\OpenOffice.org\3\user\registrymodifications.xcu # Apache OpenOffice.org 3.4.1 from openoffice.org on Windows XP for path in FileUtilities.expand_glob_join(prefix, "user/registrymodifications.xcu"): if os.path.lexists(path): yield Command.Function(path, Special.delete_office_registrymodifications, _('Delete the usage history'))
def cb_shred_file(self, action): """Callback for shredding a file or folder""" # get list of files if "ShredFiles" == action.get_name(): paths = GuiBasic.browse_files(self.window, _("Choose files to shred")) elif "ShredFolders" == action.get_name(): paths = GuiBasic.browse_folder( self.window, _("Choose folder to shred"), multiple=True, stock_button=gtk.STOCK_DELETE ) else: raise RuntimeError("Unexpected kind in cb_shred_file") if not paths: return self.shred_paths(paths)
def __init__(self): Cleaner.__init__(self) self.options = {} self.add_option('cache', _('Cache'), _('Delete the cache')) self.add_option('recent_documents', _('Most recently used'), _("Delete the list of recently used documents")) # reference: http://katana.oooninja.com/w/editions_of_openoffice.org if 'posix' == os.name: self.prefixes = [ "~/.ooo-2.0", "~/.openoffice.org2", "~/.openoffice.org2.0", "~/.openoffice.org/3" ] self.prefixes += ["~/.ooo-dev3"] if 'nt' == os.name: self.prefixes = [ "$APPDATA\\OpenOffice.org\\3", "$APPDATA\\OpenOffice.org2" ]
def add_drive_cb(button): """Callback for adding a drive""" title = _("Choose a folder") pathname = GuiBasic.browse_folder(self.parent, title, multiple=False, stock_button=gtk.STOCK_ADD) if pathname: liststore.append([pathname]) pathnames.append(pathname) options.set_list('shred_drives', pathnames)
def cb_shred_file(self, action): """Callback for shredding a file or folder""" # get list of files if 'ShredFiles' == action.get_name(): paths = GuiBasic.browse_files(self.window, _("Choose files to shred")) elif 'ShredFolders' == action.get_name(): paths = GuiBasic.browse_folder(self.window, _("Choose folder to shred"), multiple=True, stock_button=gtk.STOCK_DELETE) else: raise RuntimeError("Unexpected kind in cb_shred_file") if not paths: return self.shred_paths(paths)
def get_commands(self): for path in self.get_paths(): yield Command.Function( path, FileUtilities.vacuum_sqlite3, # TRANSLATORS: Vacuum is a verb. The term is jargon # from the SQLite database. Microsoft Access uses # the term 'Compact Database' (which you may translate # instead). Another synonym is 'defragment.' _('Vacuum'))
def print_exception(self, operation): """Display exception""" # TRANSLATORS: This indicates an error. The special keyword # %(operation)s will be replaced by 'firefox' or 'opera' or # some other cleaner ID. The special keyword %(msg)s will be # replaced by a message such as 'Permission denied.' err = _("Exception while running operation '%(operation)s': '%(msg)s'") \ % {'operation': operation, 'msg': str(sys.exc_info()[1])} logger = logging.getLogger(__name__) logger.error(err, exc_info=True) self.total_errors += 1
def whitelist(path): """Return information that this file was whitelisted""" ret = { # TRANSLATORS: This is the label in the log indicating was # skipped because it matches the whitelist 'label': _('Skip'), 'n_deleted': 0, 'n_special': 0, 'path': path, 'size': 0} return ret
def get_commands(self, option_id): if 'recent_documents' == option_id: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, "user/registry/data/org/openoffice/Office/Histories.xcu"): if os.path.lexists(path): yield Command.Delete(path) for path in FileUtilities.expand_glob_join(prefix, "user/registry/cache/org.openoffice.Office.Histories.dat"): if os.path.lexists(path): yield Command.Delete(path) if 'recent_documents' == option_id and not 'cache' == option_id: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, "user/registry/cache/org.openoffice.Office.Common.dat"): if os.path.lexists(path): yield Command.Delete(path) if 'cache' == option_id: dirs = [] for prefix in self.prefixes: dirs += FileUtilities.expand_glob_join( prefix, "user/registry/cache/") for dirname in dirs: for filename in children_in_directory(dirname, False): yield Command.Delete(filename) if 'recent_documents' == option_id: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, "user/registry/data/org/openoffice/Office/Common.xcu"): if os.path.lexists(path): yield Command.Function(path, Special.delete_ooo_history, _('Delete the usage history')) # ~/.openoffice.org/3/user/registrymodifications.xcu # Apache OpenOffice.org 3.4.1 from openoffice.org on Ubuntu 13.04 # %AppData%\OpenOffice.org\3\user\registrymodifications.xcu # Apache OpenOffice.org 3.4.1 from openoffice.org on Windows XP for path in FileUtilities.expand_glob_join(prefix, "user/registrymodifications.xcu"): if os.path.lexists(path): yield Command.Function(path, Special.delete_office_registrymodifications, _('Delete the usage history'))
def context_menu_event(self, treeview, event): """When user right clicks on the tree view""" if event.button != 3: return False pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y)) if None == pathinfo: return False path, col, cellx, celly = pathinfo treeview.grab_focus() treeview.set_cursor(path, col, 0) # context menu applies only to children, not parents if 2 != len(path): return False # find the seleted option model = treeview.get_model() option_id = model[path][2] cleaner_id = model[path[0]][2] cleaner_name = model[path[0]][0] # make a menu menu = gtk.Menu() # TRANSLATORS: this is the context menu preview_item = gtk.MenuItem(_("Preview")) preview_item.connect('activate', self.cb_run_option, False, cleaner_id, option_id) menu.append(preview_item) # TRANSLATORS: this is the context menu clean_item = gtk.MenuItem(_("Clean")) clean_item.connect('activate', self.cb_run_option, True, cleaner_id, option_id) menu.append(clean_item) # TRANSLATORS: this is the context menu stop_item = gtk.MenuItem(_("Stop")) stop_item.connect('activate', self.run_operations_stop) menu.append(stop_item) # show the context menu menu.attach_to_widget(treeview, menu.destroy) menu.show_all() menu.popup(None, None, None, event.button, event.time) return True
def clean_operation(self, operation): """Perform a single cleaning operation""" operation_options = self.operations[operation] assert (isinstance(operation_options, list)) logger = logging.getLogger(__name__) logger.debug("clean_operation('%s'), options = '%s'" % (operation, operation_options)) if not operation_options: raise StopIteration if self.really_delete and backends[operation].is_running(): # TRANSLATORS: %s expands to a name such as 'Firefox' or 'System'. err = _("%s cannot be cleaned because it is currently running. Close it, and try again.") \ % backends[operation].get_name() self.ui.append_text(err + "\n", 'error') self.total_errors += 1 return import time self.yield_time = time.time() total_size = 0 for option_id in operation_options: self.size = 0 assert (isinstance(option_id, (str, unicode))) # normal scan for cmd in backends[operation].get_commands(option_id): for ret in self.execute(cmd): if True == ret: # Return control to PyGTK idle loop to keep # it responding allow the user to abort self.yield_time = time.time() yield True if time.time() - self.yield_time > 0.25: if self.really_delete: self.ui.update_total_size(self.total_bytes) yield True self.yield_time = time.time() self.ui.update_item_size(operation, option_id, self.size) total_size += self.size # deep scan for ds in backends[operation].get_deep_scan(option_id): if '' == ds['path']: ds['path'] = os.path.expanduser('~') if 'delete' != ds['command']: raise NotImplementedError( 'Deep scan only supports deleting now') if not self.deepscans.has_key(ds['path']): self.deepscans[ds['path']] = [] self.deepscans[ds['path']].append(ds) self.ui.update_item_size(operation, -1, total_size)
def run_operations(self, my_operations): """Run a set of operations (general, memory, free disk space)""" count = 0 for operation in my_operations: self.ui.update_progress_bar(1.0 * count / len(my_operations)) name = backends[operation].get_name() if self.really_delete: # TRANSLATORS: %s is replaced with Firefox, System, etc. msg = _("Please wait. Cleaning %s.") % name else: # TRANSLATORS: %s is replaced with Firefox, System, etc. msg = _("Please wait. Previewing %s.") % name self.ui.update_progress_bar(msg) yield True # show the progress bar message now try: for dummy in self.clean_operation(operation): yield True except: self.print_exception(operation) count += 1
def print_exception(self, operation): """Display exception""" # TRANSLATORS: This indicates an error. The special keyword # %(operation)s will be replaced by 'firefox' or 'opera' or # some other cleaner ID. The special keyword %(msg)s will be # replaced by a message such as 'Permission denied.' err = _("Exception while running operation '%(operation)s': '%(msg)s'") \ % {'operation': operation, 'msg': str(sys.exc_info()[1])} print err traceback.print_exc() self.ui.append_text(err + "\n", 'error') self.total_errors += 1
def run_delayed_op(self, operation, option_id): """Run one delayed operation""" self.ui.update_progress_bar(0.0) if 'free_disk_space' == option_id: # TRANSLATORS: 'free' means 'unallocated' msg = _("Please wait. Wiping free disk space.") elif 'memory' == option_id: msg = _("Please wait. Cleaning %s.") % _("Memory") else: raise RuntimeError("Unexpected option_id in delayed ops") self.ui.update_progress_bar(msg) for cmd in backends[operation].get_commands(option_id): old_phase = None for ret in self.execute(cmd): if isinstance(ret, tuple): # Display progress (for free disk space) phase = ret[ 0] # 1=wipe free disk space, 2=wipe inodes, 3=clean up inodes files percent_done = ret[1] eta_seconds = ret[2] self.ui.update_progress_bar(percent_done) if phase == 2: msg = _('Please wait. Wiping file system metadata.') elif phase == 3: msg = _( 'Please wait. Cleaning up after wiping file system metadata.' ) if isinstance(eta_seconds, int): eta_mins = math.ceil(eta_seconds / 60) msg2 = ungettext("About %d minute remaining.", "About %d minutes remaining.", eta_mins) \ % eta_mins self.ui.update_progress_bar(msg + ' ' + msg2) else: self.ui.update_progress_bar(msg) if True == ret or isinstance(ret, tuple): # Return control to PyGTK idle loop to keep # it responding and allow the user to abort. yield True
def cb_wipe_free_space(self, action): """callback to wipe free space in arbitrary folder""" path = GuiBasic.browse_folder(self.window, _("Choose a folder"), multiple=False, stock_button=gtk.STOCK_OK) if not path: # user cancelled return backends['_gui'] = Cleaner.create_wipe_cleaner(path) # execute operations = {'_gui': ['free_disk_space']} self.preview_or_run_operations(True, operations)
def yum_clean(): """Run 'yum clean all' and return size in bytes recovered""" if os.path.exists('/var/run/yum.pid'): msg = _( "%s cannot be cleaned because it is currently running. Close it, and try again." ) % "Yum" raise RuntimeError(msg) old_size = FileUtilities.getsizedir('/var/cache/yum') args = ['--enablerepo=*', 'clean', 'all'] invalid = ['You need to be root', 'Cannot remove rpmdb file'] run_cleaner_cmd('yum', args, '^unused regex$', invalid) new_size = FileUtilities.getsizedir('/var/cache/yum') return old_size - new_size
def check_online_updates(self): """Check for software updates in background""" import Update try: updates = Update.check_updates(options.get('check_beta'), options.get('update_winapp2'), self.append_text, lambda: gobject.idle_add(self.cb_refresh_operations)) if updates: gobject.idle_add( lambda: Update.update_dialog(self.window, updates)) except: traceback.print_exc() self.append_text( _("Error when checking for updates: ") + str(sys.exc_info()[1]), 'error')