def update_dialog(parent, updates): """Updates contains the version numbers and URLs""" import gtk from bleachbit.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 __init__(self, uac=True, shred_paths=None, exit=False): if uac and 'nt' == os.name and Windows.elevate_privileges(): # privileges escalated in other process sys.exit(0) Gtk.Application.__init__(self, application_id='org.gnome.Bleachbit', flags=Gio.ApplicationFlags.FLAGS_NONE) if not exit: from bleachbit import RecognizeCleanerML RecognizeCleanerML.RecognizeCleanerML() register_cleaners() GObject.threads_init() if shred_paths: self.shred_paths(shred_paths) return 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." # https://www.bleachbit.org/forum/074-fails-errors try: import sqlite3 except ImportError: logger.exception(_("Error loading the SQLite module: the antivirus software may be blocking it.")) if 'posix' == os.name and bleachbit.expanduser('~') == '/root': self.append_text( _('You are running BleachBit with administrative privileges for cleaning shared parts of the system, and references to the user profile folder will clean only the root account.')) if 'nt' == os.name and options.get('shred'): from win32com.shell.shell import IsUserAnAdmin if not IsUserAnAdmin(): self.append_text( _('Run BleachBit with administrator privileges to improve the accuracy of overwriting the contents of files.')) self.append_text('\n') if exit: # This is used for automated testing of whether the GUI can start. print('Success') GObject.idle_add(lambda: self.quit(), priority=GObject.PRIORITY_LOW)
def create_headerbar(self): """Create the headerbar""" hb = Gtk.HeaderBar() hb.props.show_close_button = True hb.props.title = APP_NAME box = Gtk.Box() Gtk.StyleContext.add_class(box.get_style_context(), "linked") # create the preview button preview_icon = Gio.ThemedIcon(name='edit-find') # TRANSLATORS: This is the preview button on the main window. It # previews changes. preview_button = Gtk.Button() preview_button.add(Gtk.Image.new_from_gicon(preview_icon, Gtk.IconSize.BUTTON)) preview_button.connect('clicked', lambda *dummy: self.preview_or_run_operations(False)) preview_button.set_tooltip_text( _("Preview files in the selected operations (without deleting any files)")) box.add(preview_button) # create the delete button run_button = Gtk.Button() icon = Gio.ThemedIcon(name="edit-clear-all-symbolic") # TRANSLATORS: This is the clean button on the main window. # It makes permanent changes: usually deleting files, sometimes # altering them. run_button.add(Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)) run_button.set_tooltip_text(_("Clean files in the selected operations")) run_button.connect("clicked", self.run_operations) box.add(run_button) hb.pack_start(box) return hb
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 about(self, action, param): """Create and show the about dialog""" dialog = Gtk.AboutDialog(comments='Program to clean unnecessary files', copyright='Copyright (C) 2008-2018 Andrew Ziem', name=APP_NAME, version=bleachbit.APP_VERSION, website=bleachbit.APP_URL, transient_for=self._window) try: with open(bleachbit.license_filename) as f: dialog.set_license(f.read()) except IOError: dialog.set_license( _("GNU General Public License version 3 or later.\nSee https://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")) if appicon_path and os.path.exists(appicon_path): icon = Gtk.Image.new_from_file(appicon_path) dialog.set_logo(icon.get_pixbuf()) dialog.run() dialog.hide()
def worker_done(self, worker, really_delete): """Callback for when Worker is done""" self.progressbar.set_text("") self.progressbar.set_fraction(1) self.progressbar.set_text(_("Done.")) 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) logger.debug('elapsed time: %d seconds', elapsed) if elapsed < 10 or self.window.is_active(): return try: import pynotify except ImportError: logger.debug('pynotify not available') else: if pynotify.init(APP_NAME): notify = pynotify.Notification('BleachBit', _("Done."), icon='bleachbit') if 'posix' == os.name and bleachbit.expanduser('~') == '/root': notify.set_hint("desktop-entry", "bleachbit-root") else: notify.set_hint("desktop-entry", "bleachbit") notify.show() notify.set_timeout(10000)
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] # 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 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) 2008-2018 Andrew Ziem") try: with open(bleachbit.license_filename) as f: dialog.set_license(f.read()) except (IOError, TypeError): # In case the license file cannot be read, there will be an # IOError. In case the license file does not exist, the filename # will be none, causing a TypeError. 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(bleachbit.APP_VERSION) dialog.set_website(bleachbit.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 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 update_dialog(parent, updates): """Updates contains the version numbers and URLs""" from gi.repository import Gtk from bleachbit.GuiBasic import open_url dlg = Gtk.Dialog(title=_("Update BleachBit"), transient_for=parent, modal=True, destroy_with_parent=True) dlg.set_default_size(250, 125) label = Gtk.Label(label=_("A new version is available.")) dlg.vbox.pack_start(label, True, True, 0) for update in updates: ver = update[0] url = update[1] box_update = Gtk.Box() # 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, True, 10) dlg.vbox.pack_start(box_update, False, True, 0) dlg.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) dlg.show_all() dlg.run() dlg.destroy() return False
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 as 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: bleachbit.Windows.delete_locked_file(self.path) except: raise else: if self.shred: import warnings warnings.warn( _('At least one file was locked by another process, so its contents could not be overwritten. It will be marked for deletion upon system reboot.')) # TRANSLATORS: The file will be deleted when the # system reboots ret['label'] = _('Mark for deletion') yield ret
def delete_confirmation_dialog(parent, mention_preview): """Return boolean whether OK to delete files.""" dialog = Gtk.Dialog(title=_("Delete confirmation"), transient_for=parent, modal=True, destroy_with_parent=True) dialog.set_default_size(300, -1) hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10) 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(label=question_text) question.set_line_wrap(True) hbox.pack_start(question, False, True, 0) dialog.get_content_area().pack_start(hbox, False, True, 0) dialog.get_content_area().set_spacing(10) dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL) dialog.add_button(_('_Delete'), Gtk.ResponseType.ACCEPT) dialog.set_default_response(Gtk.ResponseType.CANCEL) dialog.show_all() ret = dialog.run() dialog.destroy() return ret == Gtk.ResponseType.ACCEPT
def open_url(self, action, 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.MessageType.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 www.bleachbit.org or similar msg = _("Open web browser to %s?") % host resp = message_dialog(parent_window, msg, Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL) if Gtk.ResponseType.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, Gdk.CURRENT_TIME)
def cb_shred_folder(self, action, param): """Callback for shredding a folder""" paths = GuiBasic.browse_folder(self._window, _("Choose folder to shred"), multiple=True, stock_button=_('_Delete')) if not paths: return GUI.shred_paths(self._window, paths)
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]: logger.warning("'%s' already exists in whitelist", pathname) return liststore.append([_('File'), pathname]) pathnames.append(['file', pathname]) options.set_custom_paths(pathnames)
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]: logger.warning("'%s' already exists in whitelist", pathname) return liststore.append([_('Folder'), pathname]) pathnames.append(['folder', pathname]) options.set_custom_paths(pathnames)
def __init__(self, uac=True, shred_paths=None, exit=False): if uac and 'nt' == os.name and Windows.elevate_privileges(): # privileges escalated in other process sys.exit(0) if not exit: from bleachbit import RecognizeCleanerML RecognizeCleanerML.RecognizeCleanerML() register_cleaners() self.create_window() gobject.threads_init() # Redirect logging to the GUI. bb_logger = logging.getLogger('bleachbit') gtklog = GtkLoggerHandler(self.append_text) bb_logger.addHandler(gtklog) if 'nt' == os.name and 'windows_exe' == getattr(sys, 'frozen', None): # On Microsoft Windows this avoids py2exe redirecting stderr to # bleachbit.exe.log. # sys.frozen = console_exe means the console is shown from bleachbit import logger_sh bb_logger.removeHandler(logger_sh) 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 bleachbit.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." # https://www.bleachbit.org/forum/074-fails-errors try: import sqlite3 except ImportError: logger.exception(_("Error loading the SQLite module: the antivirus software may be blocking it.")) if 'posix' == os.name and bleachbit.expanduser('~') == '/root': self.append_text( _('You are running BleachBit with administrative privileges for cleaning shared parts of the system, and references to the user profile folder will clean only the root account.')) if 'nt' == os.name and options.get('shred'): from win32com.shell.shell import IsUserAnAdmin if not IsUserAnAdmin(): self.append_text( _('Run BleachBit with administrator privileges to improve the accuracy of overwriting the contents of files.')) self.append_text('\n') if exit: # This is used for automated testing of whether the GUI can start. gobject.idle_add( lambda: gtk.main_quit(), priority=gobject.PRIORITY_LOW)
def load_cleaners(): """Scan for CleanerML and load them""" for pathname in list_cleanerml_files(): try: xmlcleaner = CleanerML(pathname) except: logger.exception(_("Error reading cleaner: %s"), pathname) continue cleaner = xmlcleaner.get_cleaner() if cleaner.is_usable(): Cleaner.backends[cleaner.id] = cleaner else: logger.debug( _("Cleaner is not usable on this OS because it has no actions: %s"), pathname)
def cb_wipe_free_space(self, action, param): """callback to wipe free space in arbitrary folder""" path = GuiBasic.browse_folder(self._window, _("Choose a folder"), multiple=False, stock_button=_('_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 __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, False) # populate data liststore = gtk.ListStore('gboolean', str, str) for lang, native in sorted(Unix.Locales.native_locale_names.items()): liststore.append([(options.get_language(lang)), 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) return vbox
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 bleachbit 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 = build_opener() opener.addheaders = [('User-Agent', user_agent())] doc = opener.open(fullurl=url, timeout=20).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 bleachbit.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 browse_folder(parent, title, multiple, stock_button): """Ask the user to select a folder. Return the full path or None.""" if 'nt' == os.name and None == os.getenv('BB_NATIVE'): ret = Windows.browse_folder( parent.window.handle if parent else None, title) return [ret] if multiple and not ret is None else ret # fall back to GTK+ chooser = Gtk.FileChooserDialog(transient_for=parent, title=title, action = Gtk.FileChooserAction.SELECT_FOLDER) chooser.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL, stock_button, Gtk.ResponseType.OK) chooser.set_default_response(Gtk.ResponseType.OK) chooser.set_select_multiple(multiple) chooser.set_current_folder(expanduser('~')) resp = chooser.run() if multiple: ret = chooser.get_filenames() else: ret = chooser.get_filename() chooser.hide() chooser.destroy() if Gtk.ResponseType.OK != resp: # user cancelled return None return ret
def diagnostic_dialog(self, parent): """Show diagnostic information""" dialog = gtk.Dialog(_("System information"), parent) dialog.resize(600, 400) txtbuffer = gtk.TextBuffer() from bleachbit 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 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 = bleachbit.Windows.delete_registry_value(self.keyname, self.valuename, really_delete) else: ret = bleachbit.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 preview_or_run_operations(self, really_delete, operations=None): """Preview operations or run operations (delete files)""" assert(isinstance(really_delete, bool)) from bleachbit 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(False) self.textbuffer.set_text("") self.progressbar.show() self.worker = Worker.Worker(self, really_delete, operations) except Exception: logger.exception('Error in Worker()') 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 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 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 from bleachbit 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 not os.path.isabs(path): path = os.path.abspath(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 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 from bleachbit 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 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('.')): logger.warning(_("not a valid cleaner: %s"), arg) continue (cleaner_id, option_id) = arg.split('.') # enable all options (for example, firefox.*) if '*' == option_id: if cleaner_id in operations: 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 cleaner_id not in operations: operations[cleaner_id] = [] if option_id not in operations[cleaner_id]: operations[cleaner_id].append(option_id) for (k, v) in operations.items(): operations[k] = sorted(v) return operations
def execute(self, cmd, operation_option): """Execute or preview the command""" ret = None try: for ret in cmd.execute(self.really_delete): if True == ret or isinstance(ret, tuple): # Temporarily pass control to the GTK idle loop, # allow user to abort, and # display progress (if applicable). yield ret if self.is_aborted: return except SystemExit: pass except Exception as e: # 2 = does not exist # 13 = permission denied from errno import ENOENT, EACCES if isinstance(e, OSError) and e.errno in (ENOENT, EACCES): # For access denied, do not show traceback exc_message = str(e).decode(FSE) logger.error('%s: %s', exc_message, cmd) else: # For other errors, show the traceback. msg = _('Error: {operation_option}: {command}') data = {'command': cmd, 'operation_option': operation_option} logger.error(msg.format(**data), exc_info=True) self.total_errors += 1 else: if ret is None: return if isinstance(ret['size'], (int, long)): size = FileUtilities.bytes_to_human(ret['size']) self.size += ret['size'] self.total_bytes += ret['size'] else: size = "?B" if ret['path']: path = ret['path'] else: path = '' path = path.decode('utf8', 'replace') # for invalid encoding line = u"%s %s %s\n" % (ret['label'], size, path) self.total_deleted += ret['n_deleted'] self.total_special += ret['n_special'] if ret['label']: # the label may be a hidden operation # (e.g., win.shell.change.notify) self.ui.append_text(line)
def get_swap_uuid(device): """Find the UUID for the swap device""" uuid = None args = ['blkid', device, '-s', 'UUID'] (_rc, stdout, _stderr) = General.run_external(args) for line in stdout.split('\n'): # example: /dev/sda5: UUID="ee0e85f6-6e5c-42b9-902f-776531938bbf" ret = re.search("^%s: UUID=\"([a-z0-9-]+)\"" % device, line) if ret is not None: uuid = ret.group(1) logger.debug( _("Found UUID for swap file {device} is {uuid}.").format(device=device, uuid=uuid)) return uuid
def browse_files(parent, title): """Prompt user to select multiple files to delete""" if os.name == 'nt' and not os.getenv('BB_NATIVE'): return Windows.browse_files(parent, title) chooser = Gtk.FileChooserDialog(title=title, transient_for=parent, action=Gtk.FileChooserAction.OPEN) chooser.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL, _("_Delete"), Gtk.ResponseType.OK) chooser.set_default_response(Gtk.ResponseType.OK) chooser.set_select_multiple(True) chooser.set_current_folder(os.path.expanduser('~')) resp = chooser.run() paths = chooser.get_filenames() chooser.destroy() if Gtk.ResponseType.OK != resp: # user cancelled return None return paths
def dnf_clean(): """Run 'dnf clean all' and return size in bytes recovered""" if os.path.exists('/var/run/dnf.pid'): msg = _( "%s cannot be cleaned because it is currently running. Close it, and try again.") % "Dnf" raise RuntimeError(msg) old_size = FileUtilities.getsizedir('/var/cache/dnf') args = ['--enablerepo=*', 'clean', 'all'] invalid = ['You need to be root', 'Cannot remove rpmdb file'] run_cleaner_cmd('dnf', args, '^unused regex$', invalid) new_size = FileUtilities.getsizedir('/var/cache/dnf') return old_size - new_size
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 as 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: bleachbit.Windows.delete_locked_file(self.path) except: raise else: if self.shred: import warnings warnings.warn( _('At least one file was locked by another process, so its contents could not be overwritten. It will be marked for deletion upon system reboot.' )) # TRANSLATORS: The file will be deleted when the # system reboots ret['label'] = _('Mark for deletion') yield ret
def update_dialog(parent, updates): """Updates contains the version numbers and URLs""" #모듈 import gtk from bleachbit.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에 "A new version is available."을 띄운다. label = gtk.Label(_("A new version is available.")) dlg.vbox.pack_start(label) #버전은 update[0], url은 update[1]에 초기화한다. 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 wipe_memory(): """Wipe unallocated memory""" # cache the file because 'swapoff' changes it proc_swaps = get_proc_swaps() devices = disable_swap_linux() yield True # process GTK+ idle loop logger.debug(_("Detected these swap devices: %s"), str(devices)) wipe_swap_linux(devices, proc_swaps) yield True child_pid = os.fork() if 0 == child_pid: make_self_oom_target_linux() fill_memory_linux() sys.exit(0) else: logger.debug( _("The function wipe_memory() with pid %d is waiting for child pid %d." ), os.getpid(), child_pid) rc = os.waitpid(child_pid, 0)[1] if 0 != rc: logger.warning( _("The child memory-wiping process returned code %d."), rc) enable_swap_linux() yield 0 # how much disk space was recovered
def make_view(self, model, parent, context_menu_event): """Create and return a TreeView object""" self.view = gtk.TreeView(model) # listen for right click (context menu) self.view.connect("button_press_event", context_menu_event) # first column self.renderer0 = gtk.CellRendererText() self.column0 = gtk.TreeViewColumn(_("Name"), self.renderer0, text=0) self.view.append_column(self.column0) self.view.set_search_column(0) # second column self.renderer1 = gtk.CellRendererToggle() self.renderer1.set_property('activatable', True) self.renderer1.connect('toggled', self.col1_toggled_cb, model, parent) self.column1 = gtk.TreeViewColumn(_("Active"), self.renderer1) self.column1.add_attribute(self.renderer1, "active", 1) self.view.append_column(self.column1) # third column self.renderer2 = gtk.CellRendererText() if hasattr(self.renderer2, 'set_alignment'): # requires PyGTK 2.22 # http://www.pygtk.org/pygtk2reference/class-gtkcellrenderer.html#method-gtkcellrenderer--set-alignment self.renderer2.set_alignment(1.0, 0.0) # TRANSLATORS: Size is the label for the column that shows how # much space an option would clean or did clean self.column2 = gtk.TreeViewColumn(_("Size"), self.renderer2, text=3) self.column2.set_alignment(1.0) self.view.append_column(self.column2) # finish self.view.expand_all() return self.view
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 not 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 len(path) != 2: return False # find the selected option model = treeview.get_model() option_id = model[path][2] cleaner_id = model[path[0]][2] # make a menu menu = Gtk.Menu() menu.connect('hide', lambda widget: widget.detach()) # TRANSLATORS: this is the context menu preview_item = Gtk.MenuItem(label=_("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(label=_("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.show_all() menu.popup(None, None, None, None, event.button, event.time) return True
def get_about_dialog(self): dialog = Gtk.AboutDialog(comments='Program to clean unnecessary files', copyright='Copyright (C) 2008-2020 Andrew Ziem', program_name=APP_NAME, version=bleachbit.APP_VERSION, website=bleachbit.APP_URL, transient_for=self._window) try: with open(bleachbit.license_filename) as f_license: dialog.set_license(f_license.read()) except (IOError, TypeError): dialog.set_license( _("GNU General Public License version 3 or later.\nSee https://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")) if appicon_path and os.path.exists(appicon_path): icon = Gtk.Image.new_from_file(appicon_path) dialog.set_logo(icon.get_pixbuf()) return dialog
def handle_cleaner_label(self, label): """<label> element under <cleaner> cleaner태그 아래에 label태그 요소""" self.cleaner.name = _(getText(label.childNodes)) # label객체의 자식노드들을 text로 추출해서 self.cleaner.name에 저장 translate = label.getAttribute('translate') #label에서 translate속성 추출 if translate and boolstr_to_bool(translate): self.xlate_cb(self.cleaner.name) # translate 와 translate를 bool값으로 한것이 True면 # xlate_cb함수에 self.cleaner.name값을 넣어서 사용 """ xlate_cb함수가 번역과 관련된 함수인데 위에서 xlate_cb의 값이 None이면 xlate_mode는 False이고이 함수는 None값을 돌려주는 함수가됨 """ """ xlate_mode의 값이 None이 아니면 xlate_mode가 True가 되는것으로 보아
def make_self_oom_target_linux(): """Make the current process the primary target for Linux out-of-memory killer""" # In Linux 2.6.36 the system changed from oom_adj to oom_score_adj path = '/proc/%d/oom_score_adj' % os.getpid() if os.path.exists(path): with open(path, 'w') as f: f.write('1000') else: path = '/proc/%d/oomadj' % os.getpid() if os.path.exists(path): with open(path, 'w') as f: f.write('15') # OOM likes nice processes logger.debug(_("Setting nice value %d for this process."), os.nice(19)) # OOM prefers non-privileged processes try: uid = General.getrealuid() if uid > 0: logger.debug( _("Dropping privileges of process ID {pid} to user ID {uid}."). format(pid=os.getpid(), uid=uid)) os.seteuid(uid) except: logger.exception('Error when dropping privileges')
def make_view(self, model, parent, context_menu_event): """Create and return a TreeView object""" self.view = Gtk.TreeView.new_with_model(model) # hide headers self.view.set_headers_visible(False) # listen for right click (context menu) self.view.connect("button_press_event", context_menu_event) # first column self.renderer0 = Gtk.CellRendererText() self.column0 = Gtk.TreeViewColumn(_("Name"), self.renderer0, text=0) self.view.append_column(self.column0) self.view.set_search_column(0) # second column self.renderer1 = Gtk.CellRendererToggle() self.renderer1.set_property('activatable', True) self.renderer1.connect('toggled', self.col1_toggled_cb, model, parent) self.column1 = Gtk.TreeViewColumn(_("Active"), self.renderer1) self.column1.add_attribute(self.renderer1, "active", 1) self.view.append_column(self.column1) # third column self.renderer2 = Gtk.CellRendererText() self.renderer2.set_alignment(1.0, 0.0) # TRANSLATORS: Size is the label for the column that shows how # much space an option would clean or did clean self.column2 = Gtk.TreeViewColumn(_("Size"), self.renderer2, text=3) self.column2.set_alignment(1.0) self.view.append_column(self.column2) # finish self.view.expand_all() return self.view
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 cb_clipboard_uri_received(self, clipboard, targets, data): """Callback for when URIs are received from clipboard""" shred_paths = None if Gdk.atom_intern_static_string('text/uri-list') in targets: # Linux shred_uris = clipboard.wait_for_contents( Gdk.atom_intern_static_string('text/uri-list')).get_uris() shred_paths = FileUtilities.uris_to_paths(shred_uris) elif Gdk.atom_intern_static_string('FileNameW') in targets: # Windows # Use non-GTK+ functions because because GTK+ 2 does not work. shred_paths = Windows.get_clipboard_paths() if shred_paths: GUI.shred_paths(self._window, shred_paths) else: logger.warning(_('No paths found in clipboard.'))
def make_files_thread(file_count, inspiration, output_folder, delete_when_finished, on_progress): if inspiration == 0: generated_file_names = generate_2600(file_count, output_folder, on_progress=on_progress) elif inspiration == 1: generated_file_names = generate_emails(file_count, output_folder, on_progress=on_progress) if delete_when_finished: on_progress(0, msg=_('Deleting files')) for i in range(0, file_count): os.unlink(generated_file_names[i]) on_progress(1.0 * (i + 1) / file_count) on_progress(1.0, is_done=True)
def _make_dialog(self, parent): """Make the main dialog""" Gtk.Dialog.__init__(self, _("Make chaff"), parent) self.set_border_width(5) box = self.get_content_area() label = Gtk.Label( _("Make randomly-generated messages derived from Hillary Clinton's emails.")) box.add(label) spin_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) spin_box.add(Gtk.Label(_("Number of files"))) adjustment = Gtk.Adjustment(100, 1, 99999, 1, 1000, 0) self.file_count = Gtk.SpinButton(adjustment=adjustment) spin_box.add(self.file_count) box.add(spin_box) folder_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) folder_box.add(Gtk.Label(_("Select destination folder"))) self.choose_folder_button = Gtk.FileChooserButton() self.choose_folder_button.set_action( Gtk.FileChooserAction.SELECT_FOLDER) import tempfile self.choose_folder_button.set_filename(tempfile.gettempdir()) folder_box.add(self.choose_folder_button) box.add(folder_box) delete_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) delete_box.add(Gtk.Label(_("When finished"))) self.when_finished_combo = Gtk.ComboBoxText() self.combo_options = ( _('Delete without shredding'), _('Do not delete')) for combo_option in self.combo_options: self.when_finished_combo.append_text(combo_option) self.when_finished_combo.set_active(0) # Set default delete_box.add(self.when_finished_combo) box.add(delete_box) self.progressbar = Gtk.ProgressBar() box.add(self.progressbar) self.progressbar.hide() self.make_button = Gtk.Button(_("Make files")) self.make_button.connect('clicked', self.on_make_files) box.add(self.make_button)
def run_deep_scan(self): """Run deep scans""" logger.debug(' deepscans=%s' % 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(self.deepscans) for cmd in ds.scan(): if True == cmd: yield True continue for ret in self.execute(cmd, 'deepscan'): yield True
def get_diagnostics_dialog(self): """Show diagnostic information""" dialog = Gtk.Dialog(_("System information"), self._window) dialog.set_default_size(600, 400) txtbuffer = Gtk.TextBuffer() from bleachbit import Diagnostic txt = Diagnostic.diagnostic_info() txtbuffer.set_text(txt) textview = Gtk.TextView.new_with_buffer(txtbuffer) textview.set_editable(False) swindow = Gtk.ScrolledWindow() swindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) swindow.add(textview) dialog.vbox.pack_start(swindow, True, True, 0) dialog.add_buttons(Gtk.STOCK_COPY, 100, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) return (dialog, txt)
def check_updates(check_beta, check_winapp2, append_text, cb_success): """Check for updates via the Internet""" opener = build_opener() socket.setdefaulttimeout(bleachbit.socket_timeout) opener.addheaders = [('User-Agent', user_agent())] import encodings.idna # https://github.com/bleachbit/bleachbit/issues/760 try: handle = opener.open(bleachbit.update_check_url) except URLError as e: logger.error( _('Error when opening a network connection to check for updates. Please verify the network is working and that a firewall is not blocking this application. Error message: {}' ).format(e)) return () doc = handle.read() try: dom = xml.dom.minidom.parseString(doc) except: logger.exception('The update information does not parse: %s', doc) return () def parse_updates(element): if element: ver = element[0].getAttribute('ver') url = element[0].firstChild.data return ver, url return () stable = parse_updates(dom.getElementsByTagName("stable")) beta = parse_updates(dom.getElementsByTagName("beta")) wa_element = dom.getElementsByTagName('winapp2') if check_winapp2 and wa_element: wa_sha512 = wa_element[0].getAttribute('sha512') wa_url = wa_element[0].getAttribute('url') update_winapp2(wa_url, wa_sha512, append_text, cb_success) dom.unlink() if stable and beta and check_beta: return stable, beta if stable: return stable, if beta and check_beta: return beta, return ()
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 #먼저 업데이트가 필요한지 여부를 확인합니다. #bleachbit모듈에서 personal_cleaners_dir을 가져온다. from bleachbit import personal_cleaners_dir #fn은 personal_cleaners_dir의 'winapp2.ini'이다. 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한다. return #파일을 닫는다. f.close() delete_current = True # download update # 업데이트를 다운로드한다. opener = build_opener() opener.addheaders = [('User-Agent', user_agent())] doc = opener.open(fullurl=url, timeout=20).read() # verify hash #해시를 확인한다. hash_actual = hashlib.sha512(doc).hexdigest() #hash_expected이고 hash_actual이 아니면 hash_expected이다. 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 # current를 삭제한다. if delete_current: from bleachbit.FileUtilities import delete delete(fn, True) # write file # 파일을 쓰기모드로 열고 'New winapp2.ini was downloaded.'에 추가한다. 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): """Make changes and return results""" if FileUtilities.whitelisted(self.path): yield whitelist(self.path) return ret = { # TRANSLATORS: The file will be truncated to 0 bytes in length 'label': _('Truncate'), 'n_deleted': 1, 'n_special': 0, 'path': self.path, 'size': FileUtilities.getsize(self.path)} if really_delete: f = open(self.path, 'wb') f.truncate(0) yield ret
def get_walk_all(top): """Delete files and directories inside a directory but not the top directory""" for expanded in glob.iglob(top): path = None # sentinel value for path in FileUtilities.children_in_directory( expanded, True): yield path # This condition executes when there are zero iterations # in the loop above. if path is None: # This is a lint checker because this scenario may # indicate the cleaner developer made a mistake. if os.path.isfile(expanded): logger.debug( _('search="walk.all" used with regular file path="%s"' ), expanded, )
def execute(self, really_delete): """Make changes and return results""" if FileUtilities.whitelisted(self.path): yield whitelist(self.path) return ret = { 'label': _('Clean file'), 'n_deleted': 0, 'n_special': 1, 'path': self.path, 'size': None} if really_delete: oldsize = FileUtilities.getsize(self.path) FileUtilities.clean_json(self.path, self.address) newsize = FileUtilities.getsize(self.path) ret['size'] = oldsize - newsize yield ret
def __flush(self): """Write information to disk""" if not self.purged: self.__purge() if not os.path.exists(bleachbit.options_dir): General.makedirs(bleachbit.options_dir) mkfile = not os.path.exists(bleachbit.options_file) with open(bleachbit.options_file, 'w', encoding='utf-8-sig') as _file: try: self.config.write(_file) except IOError as e: from errno import ENOSPC if e.errno == ENOSPC: logger.error( _("Disk was full when writing configuration to file %s"), bleachbit.options_file) else: raise if mkfile and General.sudo_mode(): General.chownself(bleachbit.options_file)
def get_walk_all(top): """Delete files and directories inside a directory but not the top directory""" for expanded in glob.iglob(top): path = None # sentinel value yield from FileUtilities.children_in_directory(expanded, True) # This condition executes when there are zero iterations # in the loop above. if path is None: # This is a lint checker because this scenario may # indicate the cleaner developer made a mistake. if os.path.isfile(expanded): logger.debug( # TRANSLATORS: This is a lint-style warning that there seems to be a # mild mistake in the CleanerML file because walk.all is expected to # be used with directories instead of with files. _('search="walk.all" used with regular file path="%s"' ), expanded, )
def list_cleanerml_files(local_only=False): """List CleanerML files""" cleanerdirs = (bleachbit.personal_cleaners_dir, ) if bleachbit.local_cleaners_dir: # If the application is installed, locale_cleaners_dir is None cleanerdirs = (bleachbit.local_cleaners_dir, ) if not local_only and bleachbit.system_cleaners_dir: cleanerdirs += (bleachbit.system_cleaners_dir, ) for pathname in listdir(cleanerdirs): if not pathname.lower().endswith('.xml'): continue import stat st = os.stat(pathname) if sys.platform != 'win32' and stat.S_IMODE(st[stat.ST_MODE]) & 2: logger.warning( _("Ignoring cleaner because it is world writable: %s"), pathname) continue yield pathname
def create_pot(): """Create a .pot for translation using gettext""" f = open('../po/cleanerml.pot', 'w') for pathname in listdir('../cleaners'): if not pathname.lower().endswith(".xml"): continue strings = [] try: CleanerML(pathname, lambda newstr, translators=None: strings.append( [newstr, translators])) except: logger.exception(_("Error reading cleaner: %s"), pathname) continue for (string, translators) in strings: f.write(pot_fragment(string, pathname, translators)) f.close()
def get_commands(self): def run_process(): try: if self.wait: args = self.cmd.split(' ') (rc, stdout, stderr) = General.run_external(args) else: rc = 0 # unknown because we don't wait from subprocess import Popen Popen(self.cmd) except Exception as e: raise RuntimeError( 'Exception in external command\nCommand: %s\nError: %s' % (self.cmd, str(e))) else: if not 0 == rc: logger.warning('Command: %s\nReturn code: %d\nStdout: %s\nStderr: %s\n', self.cmd, rc, stdout, stderr) return 0 yield Command.Function(path=None, func=run_process, label=_("Run external command: %s") % self.cmd)
def disable_swap_linux(): """Disable Linux swap and return list of devices""" if 0 == count_swap_linux(): return logger.debug(_("Disabling swap.")) args = ["swapoff", "-a", "-v"] (rc, stdout, stderr) = General.run_external(args) if 0 != rc: raise RuntimeError(stderr.replace("\n", "")) devices = [] for line in stdout.split('\n'): line = line.replace('\n', '') if '' == line: continue ret = parse_swapoff(line) if ret is None: raise RuntimeError("Unexpected output:\nargs='%(args)s'\nstdout='%(stdout)s'\nstderr='%(stderr)s'" % {'args': str(args), 'stdout': stdout, 'stderr': stderr}) devices.append(ret) return devices