def __toggle_callback(self, cell, path): """Callback function to toggle option""" options.toggle(path) if online_update_notification_enabled: self.cb_beta.set_sensitive(options.get('check_online_updates')) if 'nt' == os.name: self.cb_winapp2.set_sensitive( options.get('check_online_updates')) if 'auto_hide' == path: self.cb_refresh_operations() if 'auto_start' == path: if 'nt' == os.name: swc = Windows.start_with_computer if 'posix' == os.name: swc = Unix.start_with_computer try: swc(options.get(path)) except: traceback.print_exc() dlg = gtk.MessageDialog(self.parent, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=str(sys.exc_info()[1])) dlg.run() dlg.destroy()
def __init__(self, *args, **kwargs): super(GUI, self).__init__(*args, **kwargs) from bleachbit import RecognizeCleanerML RecognizeCleanerML.RecognizeCleanerML() register_cleaners() self.populate_window() # 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 options.get("first_start") and 'posix' == os.name: pref = PreferencesDialog(self, 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." # http://bleachbit.sourceforge.net/forum/074-fails-errors try: import sqlite3 except ImportError as e: self.append_text( _("Error loading the SQLite module: the antivirus software may be blocking it."), 'error')
def __toggle_callback(self, cell, path): global win10_provider """Callback function to toggle option""" options.toggle(path) if online_update_notification_enabled: self.cb_beta.set_sensitive(options.get('check_online_updates')) if 'nt' == os.name: self.cb_winapp2.set_sensitive( options.get('check_online_updates')) if 'auto_hide' == path: self.refresh_operations = True if 'dark_mode' == path: if os.name != 'nt' or options.get("win10_mode") == False: Gtk.Settings.get_default().set_property( 'gtk-application-prefer-dark-theme', options.get('dark_mode')) if 'win10_mode' == path: if options.get("win10_mode"): screen = Gdk.Display.get_default_screen( Gdk.Display.get_default()) Gtk.StyleContext.add_provider_for_screen( screen, bleachbit.GUI.Bleachbit._style_provider, 600) else: screen = Gdk.Display.get_default_screen( Gdk.Display.get_default()) Gtk.StyleContext.remove_provider_for_screen( screen, bleachbit.GUI.Bleachbit._style_provider) if 'debug' == path: from bleachbit.Log import set_root_log_level set_root_log_level()
def set_windows10_theme(self): """Toggle the Windows 10 theme""" if not 'nt' == os.name: return if not self._style_provider_regular: self._style_provider_regular = Gtk.CssProvider() self._style_provider_regular.load_from_path( os.path.join(windows10_theme_path, 'gtk.css')) if not self._style_provider_dark: self._style_provider_dark = Gtk.CssProvider() self._style_provider_dark.load_from_path( os.path.join(windows10_theme_path, 'gtk-dark.css')) screen = Gdk.Display.get_default_screen(Gdk.Display.get_default()) if self._style_provider is not None: Gtk.StyleContext.remove_provider_for_screen( screen, self._style_provider) if options.get("win10_theme"): if options.get("dark_mode"): self._style_provider = self._style_provider_dark else: self._style_provider = self._style_provider_regular Gtk.StyleContext.add_provider_for_screen( screen, self._style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) else: self._style_provider = None
def is_running(self): """Return whether the program is currently running""" resp_cli="" logger = logging.getLogger(__name__) for running in self.running: test = running[0] pathname = running[1] if 'exe' == test and 'posix' == os.name: if Unix.is_running(pathname): #print "debug: process '%s' is running" % pathname logger.debug("Debug: process '%s' is running", pathname) if options.get("close_run"): if not subprocess.mswindows: #print "debug: Closing process '%s'" % pathname if "--preset" in sys.argv: resp_cli = raw_input("Do you Want BleachBit to Close " + pathname + " y/n : ") else: resp = GuiBasic.message_dialog(None,"Do you Want BleachBit to Close " + pathname,gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO) if gtk.RESPONSE_YES == resp or resp_cli.lower() == "y": # user cancelled, so don't toggle option logger.debug("Debug: Closing process '%s'",pathname) subprocess.check_output(["killall", "-9", pathname]) if not Unix.is_running(pathname): logger.debug("Debug: Closing process '%s' successful",pathname) return False return True elif 'exe' == test and 'nt' == os.name: if Windows.is_process_running(pathname): #print "debug: process '%s' is running" % pathname logger.debug("Debug: process '%s' is running", pathname) if options.get("close_run"): if subprocess.mswindows: #print "debug: Closing process '%s'" % pathname if "--preset" in sys.argv: resp_cli = raw_input("Do you Want BleachBit to Close " + pathname + " y/n : ") else: resp = GuiBasic.message_dialog(None,"Do you Want BleachBit to Close " + pathname,gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO) if gtk.RESPONSE_YES == resp or resp_cli.lower() == "y": logger.debug("debug: Closing process '%s'",pathname) subprocess.check_output(["taskkill", "/IM", pathname]) if not Windows.is_process_running(pathname): logger.debug("debug: Closing process '%s' successful",pathname) return False logger.debug("process '%s' is running", pathname) return True elif 'exe' == test and 'nt' == os.name: if Windows.is_process_running(pathname): logger.debug("process '%s' is running", pathname) return True elif 'pathname' == test: expanded = expanduser(expandvars(pathname)) for globbed in glob.iglob(expanded): if os.path.exists(globbed): logger.debug("file '%s' exists indicating '%s' is running", self.name) return True else: raise RuntimeError( "Unknown running-detection test '%s'" % test) return False
def __toggle_callback(self, cell, path): """Callback function to toggle option""" options.toggle(path) if online_update_notification_enabled: self.cb_beta.set_sensitive(options.get('check_online_updates')) if 'nt' == os.name: self.cb_winapp2.set_sensitive(options.get('check_online_updates')) if 'auto_hide' == path: self.refresh_operations = True
def cb_register_cleaners_done(self): """Called from register_cleaners()""" self.progressbar.hide() # update tree view self.tree_store.refresh_rows() # expand tree view self.view.expand_all() # Check for online updates. if not self._auto_exit and \ bleachbit.online_update_notification_enabled and \ options.get("check_online_updates") and \ not hasattr(self, 'checked_for_updates'): self.checked_for_updates = True self.check_online_updates() # Show information for first start. # (The first start flag is set also for each new version.) if options.get("first_start") and not self._auto_exit: if os.name == 'posix': self.append_text( _('Access the application menu by clicking the hamburger icon on the title bar.' )) pref = self.get_preferences_dialog() pref.run() if os.name == 'nt': self.append_text( _('Access the application menu by clicking the logo on the title bar.' )) options.set('first_start', False) if os.name == 'nt': # 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 as e: self.append_text( _("Error loading the SQLite module: the antivirus software may be blocking it." ), 'error') # Show notice about admin privileges. if os.name == 'posix' and os.path.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.' ) + '\n') if os.name == 'nt' 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') # remove from idle loop (see GObject.idle_add) 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) 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 __init__(self, auto_exit, *args, **kwargs): super(GUI, self).__init__(*args, **kwargs) self.auto_exit = auto_exit self.set_wmclass(APP_NAME, APP_NAME) self.populate_window() # Redirect logging to the GUI. bb_logger = logging.getLogger('bleachbit') gtklog = GtkLoggerHandler(self.append_text) bb_logger.addHandler(gtklog) if os.name == 'nt' and getattr(sys, 'frozen', None) == 'windows_exe': # 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) Gtk.Settings.get_default().set_property( 'gtk-application-prefer-dark-theme', options.get('dark_mode')) if options.is_corrupt(): logger.error( _('Resetting the configuration file because it is corrupt: %s') % bleachbit.options_file) bleachbit.Options.init_configuration() if options.get("first_start") and os.name == 'posix' and not auto_exit: pref = PreferencesDialog(self, self.cb_refresh_operations) pref.run() options.set('first_start', False) if os.name == 'nt': # 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 as e: self.append_text( _("Error loading the SQLite module: the antivirus software may be blocking it." ), 'error') if os.name == 'posix' 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 os.name == 'nt' 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') GLib.idle_add(self.cb_refresh_operations)
def sqlite_clean_helper(self, sql, fn, clean_func, check_func=None, setup_func=None): """Helper for cleaning special SQLite cleaning""" self.assertFalse( sql and fn, "sql and fn are mutually exclusive ways to create the data") if fn: filename = os.path.join(self.dir_base, fn) if not os.path.exists(filename): import pdb pdb.set_trace() self.assert_(os.path.exists(filename)) # create sqlite file if sql: # create test file tmpdir = tempfile.mkdtemp('bleachbit-sqlite-test') (fd, filename) = tempfile.mkstemp(dir=tmpdir) os.close(fd) # additional setup if setup_func: setup_func(filename) # before SQL creation executed, cleaning should fail self.assertRaises(sqlite3.DatabaseError, clean_func, filename) # create bleachbit.FileUtilities.execute_sqlite3(filename, sql) self.assert_(os.path.exists(filename)) # clean the file old_shred = options.get('shred') options.set('shred', False, commit=False) self.assertFalse(options.get('shred')) clean_func(filename) options.set('shred', True, commit=False) self.assertTrue(options.get('shred')) options.set('shred', old_shred, commit=False) clean_func(filename) self.assert_(os.path.exists(filename)) # check if check_func: check_func(self, filename) # tear down bleachbit.FileUtilities.delete(filename) self.assert_(not os.path.exists(filename))
def check_online_updates(self): """Check for software updates in background""" from bleachbit import Update try: updates = Update.check_updates( options.get('check_beta'), options.get('update_winapp2'), self.append_text, lambda: GLib.idle_add(self.cb_refresh_operations)) if updates: GLib.idle_add(lambda: Update.update_dialog(self, updates)) except Exception: logger.exception(_("Error when checking for updates: "))
def __toggle_callback(self, cell, path): """Callback function to toggle option""" options.toggle(path) if online_update_notification_enabled: self.cb_beta.set_sensitive(options.get('check_online_updates')) if 'nt' == os.name: self.cb_winapp2.set_sensitive( options.get('check_online_updates')) if 'auto_hide' == path: self.refresh_operations = True if 'dark_mode' == path: Gtk.Settings.get_default().set_property( 'gtk-application-prefer-dark-theme', options.get('dark_mode'))
def check_online_updates(self): """Check for software updates in background""" from bleachbit 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 Exception: logger.exception(_("Error when checking for updates: "))
def sqlite_clean_helper(self, sql, fn, clean_func, check_func=None, setup_func=None): """Helper for cleaning special SQLite cleaning""" self.assertFalse( sql and fn, "sql and fn are mutually exclusive ways to create the data") if fn: filename = os.path.normpath(os.path.join(self.dir_base, fn)) self.assertExists(filename) # create sqlite file elif sql: # create test file filename = self.mkstemp(prefix='bleachbit-test-sqlite') # additional setup if setup_func: setup_func(filename) # before SQL creation executed, cleaning should fail self.assertRaises(sqlite3.DatabaseError, clean_func, filename) # create FileUtilities.execute_sqlite3(filename, sql) self.assertExists(filename) else: raise RuntimeError('neither fn nor sql supplied') # clean the file old_shred = options.get('shred') options.set('shred', False, commit=False) self.assertFalse(options.get('shred')) clean_func(filename) options.set('shred', True, commit=False) self.assertTrue(options.get('shred')) options.set('shred', old_shred, commit=False) clean_func(filename) self.assertExists(filename) # check if check_func: check_func(self, filename) # tear down FileUtilities.delete(filename) self.assertNotExists(filename)
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 sqlite_clean_helper( self, sql, fn, clean_func, check_func=None, setup_func=None): """Helper for cleaning special SQLite cleaning""" self.assertFalse( sql and fn, "sql and fn are mutually exclusive ways to create the data") if fn: filename = os.path.join(self.dir_base, fn) if not os.path.exists(filename): import pdb pdb.set_trace() self.assert_(os.path.exists(filename)) # create sqlite file if sql: # create test file tmpdir = tempfile.mkdtemp('bleachbit-sqlite-test') (fd, filename) = tempfile.mkstemp(dir=tmpdir) os.close(fd) # additional setup if setup_func: setup_func(filename) # before SQL creation executed, cleaning should fail self.assertRaises(sqlite3.DatabaseError, clean_func, filename) # create bleachbit.FileUtilities.execute_sqlite3(filename, sql) self.assert_(os.path.exists(filename)) # clean the file old_shred = options.get('shred') options.set('shred', False, commit=False) self.assertFalse(options.get('shred')) clean_func(filename) options.set('shred', True, commit=False) self.assertTrue(options.get('shred')) options.set('shred', old_shred, commit=False) clean_func(filename) self.assert_(os.path.exists(filename)) # check if check_func: check_func(self, filename) # tear down bleachbit.FileUtilities.delete(filename) self.assert_(not os.path.exists(filename))
def bytes_to_human(bytes_i): # type: (int) -> str """Display a file size in human terms (megabytes, etc.) using preferred standard (SI or IEC)""" if bytes_i < 0: return '-' + bytes_to_human(-bytes_i) from bleachbit.Options import options if options.get('units_iec'): prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi'] base = 1024.0 else: prefixes = ['', 'k', 'M', 'G', 'T', 'P'] base = 1000.0 assert (isinstance(bytes_i, int)) if 0 == bytes_i: return '0B' if bytes_i >= base**3: decimals = 2 elif bytes_i >= base: decimals = 1 else: decimals = 0 for exponent in range(0, len(prefixes)): if bytes_i < base: abbrev = round(bytes_i, decimals) suf = prefixes[exponent] return locale.str(abbrev) + suf + 'B' else: bytes_i /= base return 'A lot.'
def clean_json(path, target): """Delete key in the JSON file""" import json changed = False targets = target.split('/') # read file to parser js = json.load(open(path, 'r')) # change file pos = js while True: new_target = targets.pop(0) if not isinstance(pos, dict): break if new_target in pos and len(targets) > 0: # descend pos = pos[new_target] elif new_target in pos: # delete terminal target changed = True del (pos[new_target]) else: # target not found break if 0 == len(targets): # target not found break if changed: from bleachbit.Options import options if options.get('shred'): delete(path, True) # write file json.dump(js, open(path, 'w'))
def clean_ini(path, section, parameter): """Delete sections and parameters (aka option) in the file""" # read file to parser config = bleachbit.RawConfigParser() fp = codecs.open(path, 'r', encoding='utf_8_sig') config.readfp(fp) # change file changed = False if config.has_section(section): if parameter is None: changed = True config.remove_section(section) elif config.has_option(section, parameter): changed = True config.remove_option(section, parameter) # write file if changed: from bleachbit.Options import options fp.close() if options.get('shred'): delete(path, True) fp = codecs.open(path, 'wb', encoding='utf_8') config.write(fp)
def execute_sqlite3(path, cmds): """Execute 'cmds' on SQLite database 'path'""" import sqlite3 conn = sqlite3.connect(path) cursor = conn.cursor() # overwrites deleted content with zeros # https://www.sqlite.org/pragma.html#pragma_secure_delete from bleachbit.Options import options if options.get('shred'): cursor.execute('PRAGMA secure_delete=ON') for cmd in cmds.split(';'): try: cursor.execute(cmd) except sqlite3.DatabaseError as exc: raise sqlite3.DatabaseError( '%s: %s' % (bleachbit.decode_str(exc), path)) except sqlite3.OperationalError as exc: if exc.message.find('no such function: ') >= 0: # fixme: determine why randomblob and zeroblob are not # available logger.exception(exc.message) else: raise sqlite3.OperationalError( '%s: %s' % (bleachbit.decode_str(exc), path)) cursor.close() conn.commit() conn.close()
def bytes_to_human(bytes_i): # type: (int) -> str """Display a file size in human terms (megabytes, etc.) using preferred standard (SI or IEC)""" if bytes_i < 0: return '-' + bytes_to_human(-bytes_i) from bleachbit.Options import options if options.get('units_iec'): prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi'] base = 1024.0 else: prefixes = ['', 'k', 'M', 'G', 'T', 'P'] base = 1000.0 assert(isinstance(bytes_i, (int, long))) if 0 == bytes_i: return "0" if bytes_i >= base ** 3: decimals = 2 elif bytes_i >= base: decimals = 1 else: decimals = 0 for exponent in range(0, len(prefixes)): if bytes_i < base: abbrev = round(bytes_i, decimals) suf = prefixes[exponent] return locale.str(abbrev) + suf + 'B' else: bytes_i /= base return 'A lot.'
def set_windows10_theme(self): """Toggle the Windows 10 theme""" if not 'nt' == os.name: return exec_path = os.path.dirname(sys.executable) windows_10_theme_exe_path = os.path.normpath( os.path.join(exec_path, 'themes/windows10/gtk.css')) windows_10_theme_source_path = "themes/windows10/gtk.css" load_path = None if os.path.exists(windows_10_theme_exe_path): load_path = windows_10_theme_exe_path elif os.path.exists(windows_10_theme_source_path): load_path = windows_10_theme_source_path if not self._style_provider: self._style_provider = Gtk.CssProvider() if not load_path: logger.warning('cannot find windows10/gtk.css') return self._style_provider.load_from_path(load_path) screen = Gdk.Display.get_default_screen(Gdk.Display.get_default()) if options.get("win10_theme"): Gtk.StyleContext.add_provider_for_screen(screen, self._style_provider, 600) else: Gtk.StyleContext.remove_provider_for_screen( screen, self._style_provider)
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.is_active(): return try: gi.require_version('Notify', '0.7') from gi.repository import Notify except: logger.debug('Notify not available') else: if Notify.init(APP_NAME): notify = Notify.Notification.new('BleachBit', _("Done."), 'bleachbit') if os.name == 'posix' 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 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') notify.show() notify.set_timeout(10000)
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 __init__(self, auto_exit, *args, **kwargs): super(GUI, self).__init__(*args, **kwargs) self.auto_exit = auto_exit self.set_wmclass(APP_NAME, APP_NAME) self.populate_window() # Redirect logging to the GUI. bb_logger = logging.getLogger('bleachbit') from bleachbit.Log import GtkLoggerHandler self.gtklog = GtkLoggerHandler(self.append_text) bb_logger.addHandler(self.gtklog) # process any delayed logs from bleachbit.Log import DelayLog if isinstance(sys.stderr, DelayLog): for msg in sys.stderr.read(): self.append_text(msg) # if stderr was redirected - keep redirecting it sys.stderr = self.gtklog self.set_windows10_theme() Gtk.Settings.get_default().set_property( 'gtk-application-prefer-dark-theme', options.get('dark_mode')) if options.is_corrupt(): logger.error( _('Resetting the configuration file because it is corrupt: %s') % bleachbit.options_file) bleachbit.Options.init_configuration() GLib.idle_add(self.cb_refresh_operations)
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 clean_json(path, target): """Delete key in the JSON file""" import json changed = False targets = target.split('/') # read file to parser js = json.load(open(path, 'r')) # change file pos = js while True: new_target = targets.pop(0) if not isinstance(pos, dict): break if new_target in pos and len(targets) > 0: # descend pos = pos[new_target] elif new_target in pos: # delete terminal target changed = True del(pos[new_target]) else: # target not found break if 0 == len(targets): # target not found break if changed: from bleachbit.Options import options if options.get('shred'): delete(path, True) # write file json.dump(js, open(path, 'w'))
def update_log_level(self): """Set the log level""" from bleachbit.Options import options if options.get('debug'): self.min_level = logging.DEBUG else: self.min_level = logging.WARNING
def execute_sqlite3(path, cmds): """Execute 'cmds' on SQLite database 'path'""" import sqlite3 import contextlib with contextlib.closing(sqlite3.connect(path)) as conn: cursor = conn.cursor() # overwrites deleted content with zeros # https://www.sqlite.org/pragma.html#pragma_secure_delete from bleachbit.Options import options if options.get('shred'): cursor.execute('PRAGMA secure_delete=ON') for cmd in cmds.split(';'): try: cursor.execute(cmd) except sqlite3.OperationalError as exc: if str(exc).find('no such function: ') >= 0: # fixme: determine why randomblob and zeroblob are not # available logger.exception(exc.message) else: raise sqlite3.OperationalError('%s: %s' % (exc, path)) except sqlite3.DatabaseError as exc: raise sqlite3.DatabaseError('%s: %s' % (exc, path)) cursor.close() conn.commit()
def __init__(self, parent_window=None): self.parent_window = parent_window try: self.salt = options.get('hashsalt') except bleachbit.NoOptionError: self.salt = hashdigest(str(random.random())) options.set('hashsalt', self.salt) self.__scan()
def __init__(self, parent_window=None): self.parent_window = parent_window try: self.salt = options.get('hashsalt') except bleachbit.NoOptionError: self.salt = hashdigest(os.urandom(512)) options.set('hashsalt', self.salt) self.__scan()
def on_show(self, widget): # restore window position, size and state if not options.get('remember_geometry'): return if options.has_option("window_x") and options.has_option("window_y") and \ options.has_option("window_width") and options.has_option("window_height"): r = Gdk.Rectangle() (r.x, r.y) = (options.get("window_x"), options.get("window_y")) (r.width, r.height) = (options.get("window_width"), options.get("window_height")) screen = self.get_screen() monitor_num = screen.get_monitor_at_point(r.x, r.y) g = screen.get_monitor_geometry(monitor_num) # only restore position and size if window left corner # is within the closest monitor if r.x >= g.x and r.x < g.x + g.width and \ r.y >= g.y and r.y < g.y + g.height: logger.debug( "closest monitor ({}) geometry = {}+{}, window geometry = {}+{}" .format(monitor_num, (g.x, g.y), (g.width, g.height), (r.x, r.y), (r.width, r.height))) self.move(r.x, r.y) self.resize(r.width, r.height) if options.get("window_fullscreen"): self.fullscreen() elif options.get("window_maximized"): self.maximize()
def sqlite_clean_helper(self, sql, fn, clean_func, check_func=None, setup_func=None): """Helper for cleaning special SQLite cleaning""" self.assertFalse(sql and fn, "sql and fn are mutually exclusive ways to create the data") if fn: filename = os.path.normpath(os.path.join(self.dir_base, fn)) self.assertExists(filename) # create sqlite file elif sql: # create test file filename = self.mkstemp(prefix='bleachbit-test-sqlite') # additional setup if setup_func: setup_func(filename) # before SQL creation executed, cleaning should fail self.assertRaises(sqlite3.DatabaseError, clean_func, filename) # create FileUtilities.execute_sqlite3(filename, sql) self.assertExists(filename) else: raise RuntimeError('neither fn nor sql supplied') # clean the file old_shred = options.get('shred') options.set('shred', False, commit=False) self.assertFalse(options.get('shred')) clean_func(filename) options.set('shred', True, commit=False) self.assertTrue(options.get('shred')) options.set('shred', old_shred, commit=False) clean_func(filename) self.assertExists(filename) # check if check_func: check_func(self, filename) # tear down FileUtilities.delete(filename) self.assertNotExists(filename)
def clean_ini(path, section, parameter): """Delete sections and parameters (aka option) in the file""" def write(parser, ini_file): """ Reimplementation of the original RowConfigParser write function. This function is 99% same as its origin. The only change is removing a cast to str. This is needed to handle unicode chars. """ if parser._defaults: ini_file.write("[%s]\n" % "DEFAULT") for (key, value) in parser._defaults.items(): ini_file.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t'))) ini_file.write("\n") for section in parser._sections: ini_file.write("[%s]\n" % section) for (key, value) in parser._sections[section].items(): if key == "__name__": continue if (value is not None) or (parser._optcre == parser.OPTCRE): # The line bellow is the only changed line of the original function. # This is the orignal line for reference: # key = " = ".join((key, str(value).replace('\n', '\n\t'))) key = " = ".join((key, value.replace('\n', '\n\t'))) ini_file.write("%s\n" % (key)) ini_file.write("\n") encoding = detect_encoding(path) or 'utf_8_sig' # read file to parser config = bleachbit.RawConfigParser() config.optionxform = lambda option: option config.write = write with open(path, 'r', encoding=encoding) as fp: config.read_file(fp) # change file changed = False if config.has_section(section): if parameter is None: changed = True config.remove_section(section) elif config.has_option(section, parameter): changed = True config.remove_option(section, parameter) # write file if changed: from bleachbit.Options import options fp.close() if options.get('shred'): delete(path, True) with open(path, 'w', encoding=encoding, newline='') as fp: config.write(config, fp)
def build_app_menu(self): if os.name == 'nt': """ Load windows 10 theme """ exec_path = os.path.dirname(sys.executable) windows_10_theme_exe_path = os.path.normpath( os.path.join(exec_path, 'themes/windows10/gtk.css')) windows_10_theme_source_path = "themes/windows10/gtk.css" Bleachbit._style_provider = Gtk.CssProvider() if os.path.exists(windows_10_theme_exe_path): Bleachbit._style_provider.load_from_path( windows_10_theme_exe_path) elif os.path.exists(windows_10_theme_source_path): Bleachbit._style_provider.load_from_path( windows_10_theme_source_path) if options.get("win10_mode"): screen = Gdk.Display.get_default_screen( Gdk.Display.get_default()) Gtk.StyleContext.add_provider_for_screen( screen, Bleachbit._style_provider, 600) """Build the application menu On Linux with GTK 3.24, this code is necessary but not sufficient for the menu to work. The headerbar code is also needed. On Windows with GTK 3.18, this cde is sufficient for the menu to work. """ builder = Gtk.Builder() builder.add_from_file(bleachbit.app_menu_filename) menu = builder.get_object('app-menu') self.set_app_menu(menu) # set up mappings between <attribute name="action"> in app-menu.ui and methods in this class actions = { 'shredFiles': self.cb_shred_file, 'shredFolders': self.cb_shred_folder, 'shredClipboard': self.cb_shred_clipboard, 'wipeFreeSpace': self.cb_wipe_free_space, 'makeChaff': self.cb_make_chaff, 'shredQuit': self.cb_shred_quit, 'preferences': self.cb_preferences_dialog, 'diagnostics': self.diagnostic_dialog, 'help': self.cb_help, 'about': self.about } for action_name, callback in actions.items(): action = Gio.SimpleAction.new(action_name, None) action.connect('activate', callback) self.add_action(action)
def set_root_log_level(): """Adjust the root log level This runs later in the application's startup process when the configuration is loaded or after a change via the GUI. """ from bleachbit.Options import options is_debug = options.get('debug') root_logger = logging.getLogger('bleachbit') root_logger.setLevel(logging.DEBUG if is_debug else logging.INFO)
def run_operations(self, __widget): """Event when the 'delete' toolbar button is clicked.""" # fixme: should present this dialog after finding operations # Disable delete confirmation message. # if the option is selected under preference. if options.get("delete_confirmation"): if not GuiBasic.delete_confirmation_dialog(self.window, True): return self.preview_or_run_operations(True)
def run_operations(self, __widget): """Event when the 'delete' toolbar button is clicked.""" # fixme: should present this dialog after finding operations # Disable delete confirmation message. # if the option is selected under preference. if options.get("delete_confirmation"): if not GuiBasic.delete_confirmation_dialog(self, True): return self.preview_or_run_operations(True)
def __toggle_callback(self, cell, path): """Callback function to toggle option""" options.toggle(path) if online_update_notification_enabled: self.cb_beta.set_sensitive(options.get('check_online_updates')) if 'nt' == os.name: self.cb_winapp2.set_sensitive( options.get('check_online_updates')) if 'auto_hide' == path: self.refresh_operations = True if 'dark_mode' == path: if 'nt' == os.name and options.get('win10_theme'): self.cb_set_windows10_theme() Gtk.Settings.get_default().set_property( 'gtk-application-prefer-dark-theme', options.get('dark_mode')) if 'win10_theme' == path: self.cb_set_windows10_theme() if 'debug' == path: from bleachbit.Log import set_root_log_level set_root_log_level()
def __shred_sqlite_char_columns(table, cols=None, where=""): """Create an SQL command to shred character columns""" cmd = "" if cols and options.get('shred'): cmd += "update or ignore %s set %s %s;" % \ (table, ",".join(["%s = randomblob(length(%s))" % (col, col) for col in cols]), where) cmd += "update or ignore %s set %s %s;" % \ (table, ",".join(["%s = zeroblob(length(%s))" % (col, col) for col in cols]), where) cmd += "delete from %s %s;" % (table, where) return cmd
def refresh_rows(self): """Clear rows (cleaners) and add them fresh""" if None != self.row_changed_handler_id: self.tree_store.disconnect(self.row_changed_handler_id) self.tree_store.clear() for key in sorted(backends): if not any(backends[key].get_options()): # localizations has no options, so it should be hidden # https://github.com/az0/bleachbit/issues/110 continue c_name = backends[key].get_name() c_id = backends[key].get_id() c_value = options.get_tree(c_id, None) if not c_value and options.get('auto_hide') and backends[key].auto_hide(): logger.debug("automatically hiding cleaner '%s'", c_id) continue parent = self.tree_store.append(None, (c_name, c_value, c_id, "")) for (o_id, o_name) in backends[key].get_options(): o_value = options.get_tree(c_id, o_id) self.tree_store.append(parent, (o_name, o_value, o_id, "")) self.row_changed_handler_id = self.tree_store.connect("row-changed", self.on_row_changed)
def __general_page(self): """Return a widget containing the general page""" if 'nt' == os.name: swcc = Windows.start_with_computer_check if 'posix' == os.name: swcc = Unix.start_with_computer_check options.set('auto_start', swcc()) vbox = gtk.VBox() if online_update_notification_enabled: cb_updates = gtk.CheckButton( _("Check periodically for software updates via the Internet")) cb_updates.set_active(options.get('check_online_updates')) cb_updates.connect( 'toggled', self.__toggle_callback, 'check_online_updates') cb_updates.set_tooltip_text( _("If an update is found, you will be given the option to view information about it. Then, you may manually download and install the update.")) vbox.pack_start(cb_updates, False) updates_box = gtk.VBox() updates_box.set_border_width(10) self.cb_beta = gtk.CheckButton(_("Check for new beta releases")) self.cb_beta.set_active(options.get('check_beta')) self.cb_beta.set_sensitive(options.get('check_online_updates')) self.cb_beta.connect( 'toggled', self.__toggle_callback, 'check_beta') updates_box.pack_start(self.cb_beta, False) if 'nt' == os.name: self.cb_winapp2 = gtk.CheckButton( _("Download and update cleaners from community (winapp2.ini)")) self.cb_winapp2.set_active(options.get('update_winapp2')) self.cb_winapp2.set_sensitive( options.get('check_online_updates')) self.cb_winapp2.connect( 'toggled', self.__toggle_callback, 'update_winapp2') updates_box.pack_start(self.cb_winapp2, False) vbox.pack_start(updates_box, False) # TRANSLATORS: This means to hide cleaners which would do # nothing. For example, if Firefox were never used on # this system, this option would hide Firefox to simplify # the list of cleaners. cb_auto_hide = gtk.CheckButton(_("Hide irrelevant cleaners")) cb_auto_hide.set_active(options.get('auto_hide')) cb_auto_hide.connect('toggled', self.__toggle_callback, 'auto_hide') vbox.pack_start(cb_auto_hide, False) # TRANSLATORS: Overwriting is the same as shredding. It is a way # to prevent recovery of the data. You could also translate # 'Shred files to prevent recovery.' cb_shred = gtk.CheckButton(_("Overwrite contents of files to prevent recovery")) cb_shred.set_active(options.get('shred')) cb_shred.connect('toggled', self.__toggle_callback, 'shred') cb_shred.set_tooltip_text( _("Overwriting is ineffective on some file systems and with certain BleachBit operations. Overwriting is significantly slower.")) vbox.pack_start(cb_shred, False) cb_start = gtk.CheckButton(_("Start BleachBit with computer")) cb_start.set_active(options.get('auto_start')) cb_start.connect('toggled', self.__toggle_callback, 'auto_start') vbox.pack_start(cb_start, False) # Close the application after cleaning is complete. cb_exit = gtk.CheckButton(_("Exit after cleaning")) cb_exit.set_active(options.get('exit_done')) cb_exit.connect('toggled', self.__toggle_callback, 'exit_done') vbox.pack_start(cb_exit, False) # Disable delete confirmation message. cb_popup = gtk.CheckButton(_("Confirm before delete")) cb_popup.set_active(options.get('delete_confirmation')) cb_popup.connect( 'toggled', self.__toggle_callback, 'delete_confirmation') vbox.pack_start(cb_popup, False) # Use base 1000 over 1024? cb_units_iec = gtk.CheckButton( _("Use IEC sizes (1 KiB = 1024 bytes) instead of SI (1 kB = 1000 bytes)")) cb_units_iec.set_active(options.get("units_iec")) cb_units_iec.connect('toggled', self.__toggle_callback, 'units_iec') vbox.pack_start(cb_units_iec, False) return vbox
def delete(path, shred=False, ignore_missing=False, allow_shred=True): """Delete path that is either file, directory, link or FIFO. If shred is enabled as a function parameter or the BleachBit global parameter, the path will be shredded unless allow_shred = False. """ from bleachbit.Options import options is_special = False path = extended_path(path) if not os.path.lexists(path): if ignore_missing: return raise OSError(2, 'No such file or directory', path) if 'posix' == os.name: # With certain (relatively rare) files on Windows os.lstat() # may return Access Denied mode = os.lstat(path)[stat.ST_MODE] is_special = stat.S_ISFIFO(mode) or stat.S_ISLNK(mode) if is_special: os.remove(path) elif os.path.isdir(path): delpath = path if allow_shred and (shred or options.get('shred')): delpath = wipe_name(path) try: os.rmdir(delpath) except OSError as e: # [Errno 39] Directory not empty # https://bugs.launchpad.net/bleachbit/+bug/1012930 if errno.ENOTEMPTY == e.errno: logger.info("directory is not empty: %s", path) else: raise except WindowsError as e: # WindowsError: [Error 145] The directory is not empty: # 'C:\\Documents and Settings\\username\\Local Settings\\Temp\\NAILogs' # Error 145 may happen if the files are scheduled for deletion # during reboot. if 145 == e.winerror: logger.info("directory is not empty: %s", path) else: raise elif os.path.isfile(path): # wipe contents if allow_shred and (shred or options.get('shred')): try: wipe_contents(path) except pywinerror as e: # 2 = The system cannot find the file specified. # This can happen with a broken symlink # https://github.com/bleachbit/bleachbit/issues/195 if 2 != e.winerror: raise # If a broken symlink, try os.remove() below. except IOError as e: # permission denied (13) happens shredding MSIE 8 on Windows 7 logger.debug("IOError #%s shredding '%s'", e.errno, path, exc_info=True) # wipe name os.remove(wipe_name(path)) else: # unlink os.remove(path) else: logger.info("special file type cannot be deleted: %s", path)