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_chrome_history(path): """Clean history from History and Favicon files without affecting bookmarks""" cols = ('url', 'title') where = "" ids_int = get_chrome_bookmark_ids(path) if ids_int: ids_str = ",".join([str(id0) for id0 in ids_int]) where = "where id not in (%s) " % ids_str cmds = __shred_sqlite_char_columns('urls', cols, where) cmds += __shred_sqlite_char_columns('visits') cols = ('lower_term', 'term') cmds += __shred_sqlite_char_columns('keyword_search_terms', cols) ver = __get_chrome_history(path) if ver >= 20: # downloads, segments, segment_usage first seen in Chrome 14, # Google Chrome 15 (database version = 20). # Google Chrome 30 (database version 28) doesn't have full_path, but it # does have current_path and target_path if ver >= 28: cmds += __shred_sqlite_char_columns( 'downloads', ('current_path', 'target_path')) cmds += __shred_sqlite_char_columns( 'downloads_url_chains', ('url', )) else: cmds += __shred_sqlite_char_columns( 'downloads', ('full_path', 'url')) cmds += __shred_sqlite_char_columns('segments', ('name',)) cmds += __shred_sqlite_char_columns('segment_usage') FileUtilities.execute_sqlite3(path, cmds)
def wipe_swap_linux(devices, proc_swaps): """Shred the Linux swap file and then reinitilize it""" if devices is None: return if 0 < count_swap_linux(): raise RuntimeError('Cannot wipe swap while it is in use') for device in devices: logger.info("wiping swap device '%s'", device) safety_limit_bytes = 16 * 1024 ** 3 # 16 gibibytes actual_size_bytes = get_swap_size_linux(device, proc_swaps) if actual_size_bytes > safety_limit_bytes: raise RuntimeError( 'swap device %s is larger (%d) than expected (%d)' % (device, actual_size_bytes, safety_limit_bytes)) uuid = get_swap_uuid(device) # wipe FileUtilities.wipe_contents(device, truncate=False) # reinitialize logger.debug('reinitializing swap device %s', device) args = ['mkswap', device] if uuid: args.append("-U") args.append(uuid) (rc, _, stderr) = General.run_external(args) if 0 != rc: raise RuntimeError(stderr.replace("\n", ""))
def __is_broken_xdg_desktop_application(config, desktop_pathname): """Returns boolean whether application deskop entry file is broken""" if not config.has_option('Desktop Entry', 'Exec'): logger.info("is_broken_xdg_menu: missing required option 'Exec': '%s'", desktop_pathname) return True exe = config.get('Desktop Entry', 'Exec').split(" ")[0] if not FileUtilities.exe_exists(exe): logger.info("is_broken_xdg_menu: executable '%s' does not exist '%s'", exe, desktop_pathname) return True if 'env' == exe: # Wine v1.0 creates .desktop files like this # Exec=env WINEPREFIX="/home/z/.wine" wine "C:\\Program # Files\\foo\\foo.exe" execs = shlex.split(config.get('Desktop Entry', 'Exec')) wineprefix = None del execs[0] while True: if 0 <= execs[0].find("="): (name, value) = execs[0].split("=") if 'WINEPREFIX' == name: wineprefix = value del execs[0] else: break if not FileUtilities.exe_exists(execs[0]): logger.info("is_broken_xdg_menu: executable '%s' does not exist '%s'", execs[0], desktop_pathname) return True # check the Windows executable exists if wineprefix: windows_exe = wine_to_linux_path(wineprefix, execs[1]) if not os.path.exists(windows_exe): logger.info("is_broken_xdg_menu: Windows executable '%s' does not exist '%s'", windows_exe, desktop_pathname) return True return False
def test_get_sqlite_int(self): """Unit test for get_sqlite_int()""" sql = """CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,value LONGVARCHAR); INSERT INTO "meta" VALUES('version','20');""" # create test file filename = self.mkstemp(prefix='bleachbit-test-sqlite') FileUtilities.execute_sqlite3(filename, sql) self.assertExists(filename) # run the test ver = Special.get_sqlite_int(filename, 'select value from meta where key="version"') self.assertEqual(ver, [20])
def delete_chrome_favicons(path): """Delete Google Chrome and Chromium favicons not use in in history for bookmarks""" path_history = os.path.join(os.path.dirname(path), 'History') ver = __get_chrome_history(path) cmds = "" if ver >= 4: # Version 4 includes Chromium 12 # Version 20 includes Chromium 14, Google Chrome 15, Google Chrome 19 # Version 22 includes Google Chrome 20 # Version 25 is Google Chrome 26 # Version 26 is Google Chrome 29 # Version 28 is Google Chrome 30 # Version 29 is Google Chrome 37 # Version 32 is Google Chrome 51 # Version 36 is Google Chrome 60 # Version 38 is Google Chrome 64 # icon_mapping cols = ('page_url',) where = None if os.path.exists(path_history): cmds += "attach database \"%s\" as History;" % path_history where = "where page_url not in (select distinct url from History.urls)" cmds += __shred_sqlite_char_columns('icon_mapping', cols, where) # favicon images cols = ('image_data', ) where = "where icon_id not in (select distinct icon_id from icon_mapping)" cmds += __shred_sqlite_char_columns('favicon_bitmaps', cols, where) # favicons # Google Chrome 30 (database version 28): image_data moved to table # favicon_bitmaps if ver < 28: cols = ('url', 'image_data') else: cols = ('url', ) where = "where id not in (select distinct icon_id from icon_mapping)" cmds += __shred_sqlite_char_columns('favicons', cols, where) elif 3 == ver: # Version 3 includes Google Chrome 11 cols = ('url', 'image_data') where = None if os.path.exists(path_history): cmds += "attach database \"%s\" as History;" % path_history where = "where id not in(select distinct favicon_id from History.urls)" cmds += __shred_sqlite_char_columns('favicons', cols, where) else: raise RuntimeError('%s is version %d' % (path, ver)) FileUtilities.execute_sqlite3(path, cmds)
def execute(self, really_delete): if self.path is not None and FileUtilities.whitelisted(self.path): yield whitelist(self.path) return ret = { 'label': self.label, 'n_deleted': 0, 'n_special': 1, 'path': self.path, 'size': None} if really_delete: if self.path is None: # Function takes no path. It returns the size. func_ret = self.func() if isinstance(func_ret, types.GeneratorType): # function returned generator for func_ret in self.func(): if True == func_ret or isinstance(func_ret, tuple): # Return control to GTK idle loop. # If tuple, then display progress. yield func_ret # either way, func_ret should be an integer assert isinstance(func_ret, (int, long)) ret['size'] = func_ret else: if os.path.isdir(self.path): raise RuntimeError('Attempting to run file function %s on directory %s' % (self.func.func_name, self.path)) # Function takes a path. We check the size. oldsize = FileUtilities.getsize(self.path) try: self.func(self.path) except DatabaseError as e: if -1 == e.message.find('file is encrypted or is not a database') and \ -1 == e.message.find('or missing database'): raise logging.getLogger(__name__).exception(e.message) return try: newsize = FileUtilities.getsize(self.path) except OSError as e: from errno import ENOENT if e.errno == ENOENT: # file does not exist newsize = 0 else: raise ret['size'] = oldsize - newsize yield ret
def yum_clean(): """Run 'yum clean all' and return size in bytes recovered""" if os.path.exists('/var/run/yum.pid'): msg = _( "%s cannot be cleaned because it is currently running. Close it, and try again.") % "Yum" raise RuntimeError(msg) old_size = FileUtilities.getsizedir('/var/cache/yum') args = ['--enablerepo=*', 'clean', 'all'] invalid = ['You need to be root', 'Cannot remove rpmdb file'] run_cleaner_cmd('yum', args, '^unused regex$', invalid) new_size = FileUtilities.getsizedir('/var/cache/yum') return old_size - new_size
def start_with_computer(enabled): """If enabled, create shortcut to start application with computer. If disabled, then delete the shortcut.""" autostart_path = get_autostart_path() if not enabled: if os.path.lexists(autostart_path): FileUtilities.delete(autostart_path) return if os.path.lexists(autostart_path): return import winshell winshell.CreateShortcut(Path=autostart_path, Target=os.path.join(bleachbit.bleachbit_exe_path, 'bleachbit.exe'))
def delete_mozilla_url_history(path): """Delete URL history in Mozilla places.sqlite (Firefox 3 and family)""" cmds = "" # delete the URLs in moz_places places_suffix = "where id in (select " \ "moz_places.id from moz_places " \ "left join moz_bookmarks on moz_bookmarks.fk = moz_places.id " \ "where moz_bookmarks.id is null); " cols = ('url', 'rev_host', 'title') cmds += __shred_sqlite_char_columns('moz_places', cols, places_suffix) # delete any orphaned annotations in moz_annos annos_suffix = "where id in (select moz_annos.id " \ "from moz_annos " \ "left join moz_places " \ "on moz_annos.place_id = moz_places.id " \ "where moz_places.id is null); " cmds += __shred_sqlite_char_columns( 'moz_annos', ('content', ), annos_suffix) # delete any orphaned favicons fav_suffix = "where id not in (select favicon_id " \ "from moz_places where favicon_id is not null ); " if __sqlite_table_exists(path, 'moz_favicons'): cols = ('url', 'data') cmds += __shred_sqlite_char_columns('moz_favicons', cols, fav_suffix) # delete any orphaned history visits cmds += "delete from moz_historyvisits where place_id not " \ "in (select id from moz_places where id is not null); " # delete any orphaned input history input_suffix = "where place_id not in (select distinct id from moz_places)" cols = ('input', ) cmds += __shred_sqlite_char_columns('moz_inputhistory', cols, input_suffix) # delete the whole moz_hosts table # Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=932036 # Reference: # https://support.mozilla.org/en-US/questions/937290#answer-400987 if __sqlite_table_exists(path, 'moz_hosts'): cmds += __shred_sqlite_char_columns('moz_hosts', ('host',)) cmds += "delete from moz_hosts;" # execute the commands FileUtilities.execute_sqlite3(path, cmds)
def delete_chrome_keywords(path): """Delete keywords table in Chromium/Google Chrome 'Web Data' database""" cols = ('short_name', 'keyword', 'favicon_url', 'originating_url', 'suggest_url') where = "where not date_created = 0" cmds = __shred_sqlite_char_columns('keywords', cols, where) cmds += "update keywords set usage_count = 0;" ver = __get_chrome_history(path, 'Web Data') if 43 <= ver < 49: # keywords_backup table first seen in Google Chrome 17 / Chromium 17 which is Web Data version 43 # In Google Chrome 25, the table is gone. cmds += __shred_sqlite_char_columns('keywords_backup', cols, where) cmds += "update keywords_backup set usage_count = 0;" FileUtilities.execute_sqlite3(path, cmds)
def delete_chrome_autofill(path): """Delete autofill table in Chromium/Google Chrome 'Web Data' database""" cols = ('name', 'value', 'value_lower') cmds = __shred_sqlite_char_columns('autofill', cols) cols = ('first_name', 'middle_name', 'last_name', 'full_name') cmds += __shred_sqlite_char_columns('autofill_profile_names', cols) cmds += __shred_sqlite_char_columns('autofill_profile_emails', ('email',)) cmds += __shred_sqlite_char_columns('autofill_profile_phones', ('number',)) cols = ('company_name', 'street_address', 'dependent_locality', 'city', 'state', 'zipcode', 'country_code') cmds += __shred_sqlite_char_columns('autofill_profiles', cols) cols = ( 'company_name', 'street_address', 'address_1', 'address_2', 'address_3', 'address_4', 'postal_code', 'country_code', 'language_code', 'recipient_name', 'phone_number') cmds += __shred_sqlite_char_columns('server_addresses', cols) FileUtilities.execute_sqlite3(path, cmds)
def update_total_size(self, bytes_removed): """Callback to update the total size cleaned""" context_id = self.status_bar.get_context_id('size') text = FileUtilities.bytes_to_human(bytes_removed) if 0 == bytes_removed: text = "" self.status_bar.push(context_id, text)
def delete_updates(): """Returns commands for deleting Windows Updates files""" windir = bleachbit.expandvars('$windir') dirs = glob.glob(os.path.join(windir, '$NtUninstallKB*')) dirs += [bleachbit.expandvars('$windir\\SoftwareDistribution\\Download')] dirs += [bleachbit.expandvars('$windir\\ie7updates')] dirs += [bleachbit.expandvars('$windir\\ie8updates')] if not dirs: # if nothing to delete, then also do not restart service return import win32serviceutil wu_running = win32serviceutil.QueryServiceStatus('wuauserv')[1] == 4 args = ['net', 'stop', 'wuauserv'] def wu_service(): General.run_external(args) return 0 if wu_running: yield Command.Function(None, wu_service, " ".join(args)) for path1 in dirs: for path2 in FileUtilities.children_in_directory(path1, True): yield Command.Delete(path2) if os.path.exists(path1): yield Command.Delete(path1) args = ['net', 'start', 'wuauserv'] if wu_running: yield Command.Function(None, wu_service, " ".join(args))
def get_commands(self, option_id): # paths for which to run expand_glob_join egj = [] if 'recent_documents' == option_id: egj.append( "user/registry/data/org/openoffice/Office/Histories.xcu") egj.append( "user/registry/cache/org.openoffice.Office.Histories.dat") if 'recent_documents' == option_id and not 'cache' == option_id: egj.append("user/registry/cache/org.openoffice.Office.Common.dat") for egj_ in egj: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, egj_): if 'nt' == os.name: path = os.path.normpath(path) if os.path.lexists(path): yield Command.Delete(path) if 'cache' == option_id: dirs = [] for prefix in self.prefixes: dirs += FileUtilities.expand_glob_join( prefix, "user/registry/cache/") for dirname in dirs: if 'nt' == os.name: dirname = os.path.normpath(dirname) for filename in children_in_directory(dirname, False): yield Command.Delete(filename) if 'recent_documents' == option_id: for prefix in self.prefixes: for path in FileUtilities.expand_glob_join(prefix, "user/registry/data/org/openoffice/Office/Common.xcu"): if os.path.lexists(path): yield Command.Function(path, Special.delete_ooo_history, _('Delete the usage history')) # ~/.openoffice.org/3/user/registrymodifications.xcu # Apache OpenOffice.org 3.4.1 from openoffice.org on Ubuntu 13.04 # %AppData%\OpenOffice.org\3\user\registrymodifications.xcu # Apache OpenOffice.org 3.4.1 from openoffice.org on Windows XP for path in FileUtilities.expand_glob_join(prefix, "user/registrymodifications.xcu"): if os.path.lexists(path): yield Command.Function(path, Special.delete_office_registrymodifications, _('Delete the usage history'))
def delete_chrome_autofill(path): """Delete autofill table in Chromium/Google Chrome 'Web Data' database""" cols = ('name', 'value', 'value_lower') cmds = __shred_sqlite_char_columns('autofill', cols) cols = ('first_name', 'middle_name', 'last_name', 'full_name') cmds += __shred_sqlite_char_columns('autofill_profile_names', cols) cmds += __shred_sqlite_char_columns('autofill_profile_emails', ('email', )) cmds += __shred_sqlite_char_columns('autofill_profile_phones', ('number', )) cols = ('company_name', 'street_address', 'dependent_locality', 'city', 'state', 'zipcode', 'country_code') cmds += __shred_sqlite_char_columns('autofill_profiles', cols) cols = ('company_name', 'street_address', 'address_1', 'address_2', 'address_3', 'address_4', 'postal_code', 'country_code', 'language_code', 'recipient_name', 'phone_number') cmds += __shred_sqlite_char_columns('server_addresses', cols) FileUtilities.execute_sqlite3(path, cmds)
def get_globs_size(paths): """Get the cumulative size (in bytes) of a list of globs""" total_size = 0 for path in paths: from glob import iglob for p in iglob(path): total_size += FileUtilities.getsize(p) return total_size
def wipe_path_func(): # 경로의 여유공간 를 지우는 함수 for ret in FileUtilities.wipe_path(pathname, idle=True): # Yield control to GTK idle because this process # is very slow. Also display progress. # FileUtilities.wipe_path = 경로의 여유 공간 지우는 함수 # 이 기능은 반복기를 사용하여 GUI를 업데이트합니다. yield ret # pathname 의 여유 공간을 지우고 return yield 0
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 setUp(self): """Create test browser files.""" super(SpecialTestCase, self).setUp() self.dir_base = self.mkdtemp(prefix='bleachbit-test-special') dir_google_chrome_default = os.path.join(self.dir_base, 'google-chrome/Default/') os.makedirs(dir_google_chrome_default) # google-chrome/Default/Bookmarks bookmark_path = os.path.join(dir_google_chrome_default, 'Bookmarks') with open(bookmark_path, 'wb') as f: f.write(chrome_bookmarks) # google-chrome/Default/Web Data FileUtilities.execute_sqlite3( os.path.join(dir_google_chrome_default, 'Web Data'), chrome_webdata) # google-chrome/Default/History FileUtilities.execute_sqlite3( os.path.join(dir_google_chrome_default, 'History'), chrome_history_sql) # google-chrome/Default/databases/Databases.db os.makedirs(os.path.join(dir_google_chrome_default, 'databases')) FileUtilities.execute_sqlite3( os.path.join(dir_google_chrome_default, 'databases/Databases.db'), chrome_databases_db) dir_firefox_default = os.path.join(self.dir_base, 'firefox/default-release/') os.makedirs(dir_firefox_default) # firefox/xxxxx.default-release/places.sqlite FileUtilities.execute_sqlite3( os.path.join(dir_firefox_default, 'places.sqlite'), firefox78_places_sql) # firefox/xxxxx.default-release/favicons.sqlite FileUtilities.execute_sqlite3( os.path.join(dir_firefox_default, 'favicons.sqlite'), firefox78_favicons_sql)
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}') if isinstance(msg, str) and isinstance(operation_option, unicode): # if _ haven't found proper translation in locale dir it returns str msg = msg.decode(FSE) 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 = u'' if isinstance(path, str): 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 test_get_sqlite(self): """Unit test for get_sqlite()""" sql = """CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY,value LONGVARCHAR); INSERT INTO "meta" VALUES('version','20'); INSERT INTO "meta" VALUES('version_str','aaa'); """ # create test file filename = self.mkstemp(prefix='bleachbit-test-sqlite') FileUtilities.execute_sqlite3(filename, sql) self.assertExists(filename) # run the test ver = Special.get_sqlite( filename, 'select value from meta where key="version"') self.assertEqual(ver, [20]) ver = Special.get_sqlite( filename, 'select value from meta where key="version_str"', None, str) self.assertEqual(ver, ["aaa"])
def get_commands(self): # Checking allows auto-hide to work for non-APT systems if not FileUtilities.exe_exists('yum'): raise StopIteration yield Command.Function( None, Unix.yum_clean, 'yum clean all')
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_commands(self): # Checking allows auto-hide to work for non-APT systems if not FileUtilities.exe_exists('dnf'): raise StopIteration yield Command.Function( None, Unix.dnf_autoremove, 'dnf autoremove')
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 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 execute(self, really_delete): """Make changes and return results""" if FileUtilities.whitelisted(self.path): yield whitelist(self.path) return ret = { # TRANSLATORS: Parts of this file will be deleted 'label': _('Clean file'), 'n_deleted': 0, 'n_special': 1, 'path': self.path, 'size': None} if really_delete: oldsize = FileUtilities.getsize(self.path) FileUtilities.clean_ini(self.path, self.section, self.parameter) newsize = FileUtilities.getsize(self.path) ret['size'] = oldsize - newsize yield ret
def test_delete(self): """Unit test for --delete option""" filename = self.mkstemp(prefix='bleachbit-test-cli-delete') if 'nt' == os.name: filename = os.path.normcase(filename) # replace delete function for testing save_delete = FileUtilities.delete deleted_paths = [] def dummy_delete(path, shred=False): self.assertExists(path) deleted_paths.append(os.path.normcase(path)) FileUtilities.delete = dummy_delete FileUtilities.delete(filename) self.assertExists(filename) operations = args_to_operations(['system.tmp'], False) preview_or_clean(operations, True) FileUtilities.delete = save_delete self.assertIn(filename, deleted_paths, "%s not found deleted" % filename) os.remove(filename) self.assertNotExists(filename)
def get_recycle_bin(): """Yield a list of files in the recycle bin""" pidl = shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_BITBUCKET) desktop = shell.SHGetDesktopFolder() h = desktop.BindToObject(pidl, None, shell.IID_IShellFolder) for item in h: path = h.GetDisplayNameOf(item, shellcon.SHGDN_FORPARSING) if os.path.isdir(path): # Return the contents of a normal directory, but do # not recurse Windows symlinks in the Recycle Bin. yield from FileUtilities.children_in_directory(path, True) yield path
def setUp(self): """Create test browser files.""" super(SpecialTestCase, self).setUp() self.dir_base = self.mkdtemp(prefix='bleachbit-test-special') self.dir_google_chrome_default = os.path.join(self.dir_base, 'google-chrome/Default/') os.makedirs(self.dir_google_chrome_default) # google-chrome/Default/Bookmarks bookmark_path = os.path.join( self.dir_google_chrome_default, 'Bookmarks') f = open(bookmark_path, 'w') f.write(chrome_bookmarks) f.close() # google-chrome/Default/Web Data FileUtilities.execute_sqlite3(os.path.join(self.dir_google_chrome_default, 'Web Data'), chrome_webdata) # google-chrome/Default/History FileUtilities.execute_sqlite3(os.path.join(self.dir_google_chrome_default, 'History'), chrome_history_sql) # google-chrome/Default/databases/Databases.db os.makedirs(os.path.join(self.dir_google_chrome_default, 'databases')) FileUtilities.execute_sqlite3( os.path.join(self.dir_google_chrome_default, 'databases/Databases.db'), chrome_databases_db)
def __is_broken_xdg_desktop_application(config, desktop_pathname): """Returns boolean whether application deskop entry file is broken""" if not config.has_option('Desktop Entry', 'Exec'): logger.info("is_broken_xdg_menu: missing required option 'Exec': '%s'", desktop_pathname) return True exe = config.get('Desktop Entry', 'Exec').split(" ")[0] if not FileUtilities.exe_exists(exe): logger.info("is_broken_xdg_menu: executable '%s' does not exist '%s'", exe, desktop_pathname) return True if 'env' == exe: # Wine v1.0 creates .desktop files like this # Exec=env WINEPREFIX="/home/z/.wine" wine "C:\\Program # Files\\foo\\foo.exe" execs = shlex.split(config.get('Desktop Entry', 'Exec')) wineprefix = None del execs[0] while True: if 0 <= execs[0].find("="): (name, value) = execs[0].split("=") if 'WINEPREFIX' == name: wineprefix = value del execs[0] else: break if not FileUtilities.exe_exists(execs[0]): logger.info( "is_broken_xdg_menu: executable '%s' does not exist '%s'", execs[0], desktop_pathname) return True # check the Windows executable exists if wineprefix: windows_exe = wine_to_linux_path(wineprefix, execs[1]) if not os.path.exists(windows_exe): logger.info( "is_broken_xdg_menu: Windows executable '%s' does not exist '%s'", windows_exe, desktop_pathname) return True return False
def setUp(self): """Create test browser files.""" super(SpecialTestCase, self).setUp() self.dir_base = self.mkdtemp(prefix='bleachbit-test-special') self.dir_google_chrome_default = os.path.join( self.dir_base, 'google-chrome/Default/') os.makedirs(self.dir_google_chrome_default) # google-chrome/Default/Bookmarks bookmark_path = os.path.join(self.dir_google_chrome_default, 'Bookmarks') f = open(bookmark_path, 'w') f.write(chrome_bookmarks) f.close() # google-chrome/Default/Web Data FileUtilities.execute_sqlite3( os.path.join(self.dir_google_chrome_default, 'Web Data'), chrome_webdata) # google-chrome/Default/History FileUtilities.execute_sqlite3( os.path.join(self.dir_google_chrome_default, 'History'), chrome_history_sql) # google-chrome/Default/databases/Databases.db os.makedirs(os.path.join(self.dir_google_chrome_default, 'databases')) FileUtilities.execute_sqlite3( os.path.join(self.dir_google_chrome_default, 'databases/Databases.db'), chrome_databases_db)
def get_walk_all(top): """Delete files and directories inside a directory but not the top directory""" for expanded in glob.iglob(top): any_match = False for path in FileUtilities.children_in_directory( expanded, True): any_match = True yield path # This is a lint checker because this scenario may # indicate the cleaner developer made a mistake. if not any_match and os.path.isfile(expanded): logger.debug( _('search="walk.all" used with regular file path="%s"'), expanded)
def get_recycle_bin(): """Yield a list of files in the recycle bin""" pidl = shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_BITBUCKET) desktop = shell.SHGetDesktopFolder() h = desktop.BindToObject(pidl, None, shell.IID_IShellFolder) for item in h: path = h.GetDisplayNameOf(item, shellcon.SHGDN_FORPARSING) if os.path.isdir(path): for child in FileUtilities.children_in_directory(path, True): yield child yield path else: yield path
def start_with_computer(enabled): """If enabled, create shortcut to start application with computer. If disabled, then delete the shortcut.""" if not enabled: # User requests to not automatically start BleachBit if os.path.lexists(bleachbit.autostart_path): # Delete the shortcut FileUtilities.delete(bleachbit.autostart_path) return # User requests to automatically start BleachBit if os.path.lexists(bleachbit.autostart_path): # Already automatic, so exit return if not os.path.exists(bleachbit.launcher_path): logger.error('%s does not exist: ', bleachbit.launcher_path) return import shutil General.makedirs(os.path.dirname(bleachbit.autostart_path)) shutil.copy(bleachbit.launcher_path, bleachbit.autostart_path) os.chmod(bleachbit.autostart_path, 0o755) if General.sudo_mode(): General.chownself(bleachbit.autostart_path)
def get_walk_all(top): for expanded in glob.iglob(top): any_match = False for path in FileUtilities.children_in_directory( expanded, True): any_match = True yield path # This is a lint checker because this scenario may # indicate the cleaner developer made a mistake. if not any_match and os.path.isfile(expanded): logger.debug( _('search="walk.all" used with regular file path="%s"' ), expanded)
def get_recycle_bin(): """Yield a list of files in the recycle bin""" pidl = shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_BITBUCKET) desktop = shell.SHGetDesktopFolder() h = desktop.BindToObject(pidl, None, shell.IID_IShellFolder) for item in h: path = h.GetDisplayNameOf(item, shellcon.SHGDN_FORPARSING) if os.path.isdir(path): if not is_link(path): # Return the contents of a normal directory, but do # not recurse Windows symlinks in the Recycle Bin. for child in FileUtilities.children_in_directory(path, True): yield child yield path
def test_delete(self): """Unit test for --delete option""" (fd, filename) = tempfile.mkstemp('bleachbit-test') os.close(fd) if 'nt' == os.name: import win32api filename = os.path.normcase(win32api.GetLongPathName(filename)) # replace delete function for testing save_delete = FileUtilities.delete deleted_paths = [] def dummy_delete(path, shred = False): self.assert_(os.path.exists(path)) deleted_paths.append(os.path.normcase(path)) FileUtilities.delete = dummy_delete FileUtilities.delete(filename) self.assert_(os.path.exists(filename)) operations = args_to_operations(['system.tmp'], False) preview_or_clean(operations, True) FileUtilities.delete = save_delete self.assert_(filename in deleted_paths, \ "%s not found deleted" % filename) os.remove(filename) self.assert_(not os.path.exists(filename))
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 run_cleaner_cmd(cmd, args, freed_space_regex=r'[\d.]+[kMGTE]?B?', error_line_regexes=None): """Runs a specified command and returns how much space was (reportedly) freed. The subprocess shouldn't need any user input and the user should have the necessary rights. freed_space_regex gets applied to every output line, if the re matches, add values captured by the single group in the regex""" if not FileUtilities.exe_exists(cmd): raise RuntimeError(_('Executable not found: %s') % cmd) freed_space_regex = re.compile(freed_space_regex) error_line_regexes = [re.compile(regex) for regex in error_line_regexes or []] output = subprocess.check_output([cmd]+args, stderr=subprocess.STDOUT, universal_newlines=True, env={'LC_ALL': 'C'}) freed_space = 0 for line in output.split('\n'): m = freed_space_regex.match(line) if m is not None: freed_space += FileUtilities.human_to_bytes(m.group(1)) for error_re in error_line_regexes: if error_re.search(line): raise RuntimeError('Invalid output from %s: %s' % (cmd, line)) return freed_space
def delete_mozilla_favicons(path): """Delete favorites icon in Mozilla places.favicons only if they are not bookmarks (Firefox 3 and family)""" # Use list here because some urls contain semicolon and this # way we aviod wrongly splitting by semicolon in execute_sqlite3. cmds = [] # collect bookmarked urls places_path = os.path.join(os.path.dirname(path), 'places.sqlite') bookmark_urls_cmd = ( "select url from moz_places where id in (select distinct fk from moz_bookmarks where fk is not null)" ) bookmark_urls = get_sqlite(places_path, bookmark_urls_cmd, element_type=str) bookmark_urls = ",".join( ["'{}'".format(url.replace("'", "''")) for url in bookmark_urls]) # delete all not bookmarked icon urls urls_where = "where (page_url not in ({}))".format(bookmark_urls) cmds.append( __shred_sqlite_char_columns('moz_pages_w_icons', ('page_url', ), urls_where)) # delete all not bookmarked icons to pages mapping mapping_where = "where (page_id not in (select id from moz_pages_w_icons))" cmds.append( __shred_sqlite_char_columns('moz_icons_to_pages', where=mapping_where)) # delete all not bookmarked icons icons_where = "where (id not in (select icon_id from moz_icons_to_pages))" cols = ('icon_url', 'data') cmds.append( __shred_sqlite_char_columns('moz_icons', cols, where=icons_where)) FileUtilities.execute_sqlite3(path, None, cmds_as_list=cmds)
def run_cleaner_cmd(cmd, args, freed_space_regex=r'[\d.]+[kMGTE]?B?', error_line_regexes=None): """Runs a specified command and returns how much space was (reportedly) freed. The subprocess shouldn't need any user input and the user should have the necessary rights. freed_space_regex gets applied to every output line, if the re matches, add values captured by the single group in the regex""" if not FileUtilities.exe_exists(cmd): raise RuntimeError(_('Executable not found: %s') % cmd) freed_space_regex = re.compile(freed_space_regex) error_line_regexes = [re.compile(regex) for regex in error_line_regexes or []] env = {'LC_ALL': 'C', 'PATH': os.getenv('PATH')} output = subprocess.check_output([cmd] + args, stderr=subprocess.STDOUT, universal_newlines=True, env=env) freed_space = 0 for line in output.split('\n'): m = freed_space_regex.match(line) if m is not None: freed_space += FileUtilities.human_to_bytes(m.group(1)) for error_re in error_line_regexes: if error_re.search(line): raise RuntimeError('Invalid output from %s: %s' % (cmd, line)) return freed_space
def 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 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 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 fill_memory_linux(): report_free() allocbytes = int(physical_free() * 0.4) if allocbytes < 1024: return bytes_str = FileUtilities.bytes_to_human(allocbytes) logger.info('allocating and wiping %s (%d B) of memory', bytes_str, allocbytes) try: buf = '\x00' * allocbytes except MemoryError: pass else: fill_memory_linux() logger.debug('freeing %s of memory" % bytes_str') del buf report_free()
def execute(self, cmd): """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 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 logger.error('%s: %s', e, cmd) else: # For other errors, show the traceback. logger.error('Error in execution of %s', cmd, 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_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 delete_updates(): """Returns commands for deleting Windows Updates files""" windir = os.path.expandvars('%windir%') dirs = glob.glob(os.path.join(windir, '$NtUninstallKB*')) dirs += [os.path.expandvars(r'%windir%\SoftwareDistribution')] dirs += [os.path.expandvars(r'%windir%\SoftwareDistribution.old')] dirs += [os.path.expandvars(r'%windir%\SoftwareDistribution.bak')] dirs += [os.path.expandvars(r'%windir%\ie7updates')] dirs += [os.path.expandvars(r'%windir%\ie8updates')] dirs += [os.path.expandvars(r'%windir%\system32\catroot2')] if not dirs: # if nothing to delete, then also do not restart service return args = [] def run_wu_service(): General.run_external(args) return 0 services = {} all_services = ('wuauserv', 'cryptsvc', 'bits', 'msiserver') for service in all_services: import win32serviceutil services[service] = win32serviceutil.QueryServiceStatus(service)[ 1] == 4 logger.debug('Windows service {} has current state: {}'.format( service, services[service])) if services[service]: args = ['net', 'stop', service] yield Command.Function(None, run_wu_service, " ".join(args)) for path1 in dirs: for path2 in FileUtilities.children_in_directory(path1, True): yield Command.Delete(path2) if os.path.exists(path1): yield Command.Delete(path1) for this_service in all_services: if services[this_service]: args = ['net', 'start', this_service] yield Command.Function(None, run_wu_service, " ".join(args))
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 fill_memory_linux(): """Fill unallocated memory""" report_free() allocbytes = int(physical_free() * 0.4) if allocbytes < 1024: return bytes_str = FileUtilities.bytes_to_human(allocbytes) # TRANSLATORS: The variable is a quantity like 5kB logger.info(_("Allocating and wiping %s of memory."), bytes_str) try: buf = '\x00' * allocbytes except MemoryError: pass else: fill_memory_linux() # TRANSLATORS: The variable is a quantity like 5kB logger.debug(_("Freeing %s of memory."), bytes_str) del buf report_free()
def rotated_logs(): """Yield a list of rotated (i.e., old) logs in /var/log/""" # Ubuntu 9.04 # /var/log/dmesg.0 # /var/log/dmesg.1.gz # Fedora 10 # /var/log/messages-20090118 globpaths = ('/var/log/*.[0-9]', '/var/log/*/*.[0-9]', '/var/log/*.gz', '/var/log/*/*gz', '/var/log/*/*.old', '/var/log/*.old') for globpath in globpaths: for path in glob.iglob(globpath): yield path regex = '-[0-9]{8}$' globpaths = ('/var/log/*-*', '/var/log/*/*-*') for path in FileUtilities.globex(globpaths, regex): whitelist_re = '^/var/log/(removed_)?(packages|scripts)' if re.match(whitelist_re, path) is None: # for Slackware, Launchpad #367575 yield path