• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bleachbit / bleachbit / 23171475269

16 Mar 2026 11:50PM UTC coverage: 73.166% (+2.0%) from 71.186%
23171475269

push

github

az0
Gracefully handle missing urllib3

- Do not crash on startup in case of missing urllib3.
- Check for missing packages.
- Notify the user.

Affects BleachBit 5.1.0 and 5.1.1 in Flatpak

Closes https://github.com/bleachbit/bleachbit/issues/2056

20 of 25 new or added lines in 2 files covered. (80.0%)

1105 existing lines in 26 files now uncovered.

7043 of 9626 relevant lines covered (73.17%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.44
/bleachbit/DeepScan.py
1
# vim: ts=4:sw=4:expandtab
2
# -*- coding: UTF-8 -*-
3

4
# BleachBit
5
# Copyright (C) 2008-2025 Andrew Ziem
6
# https://www.bleachbit.org
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20

21

22
"""
23
Scan directory tree for files to delete
24
"""
25

26
import logging
1✔
27
import os
1✔
28
import platform
1✔
29
import re
1✔
30
import time
1✔
31
import unicodedata
1✔
32
from collections import namedtuple
1✔
33
from bleachbit import fs_scan_re_flags
1✔
34
from . import Command
1✔
35

36

37
def normalized_walk(top, **kwargs):
1✔
38
    """
39
    macOS uses decomposed UTF-8 to store filenames. This functions
40
    is like `os.walk` but recomposes those decomposed filenames on
41
    macOS
42
    """
43
    try:
1✔
44
        from scandir import walk
1✔
45
    except:
1✔
46
        # there is a warning in FileUtilities, so don't warn again here
47
        from os import walk
1✔
48
    if 'Darwin' == platform.system():
1✔
49
        for dirpath, dirnames, filenames in walk(top, **kwargs):
×
UNCOV
50
            yield dirpath, dirnames, [
×
51
                unicodedata.normalize('NFC', fn)
52
                for fn in filenames
53
            ]
54
    else:
55
        yield from walk(top, **kwargs)
1✔
56

57

58
Search = namedtuple(
1✔
59
    'Search', ['command', 'regex', 'nregex', 'wholeregex', 'nwholeregex'])
60
Search.__new__.__defaults__ = (None,) * len(Search._fields)
1✔
61

62

63
class CompiledSearch:
1✔
64
    """Compiled search condition"""
65

66
    def __init__(self, search):
1✔
67
        self.command = search.command
1✔
68

69
        def re_compile(regex):
1✔
70
            return re.compile(regex, fs_scan_re_flags) if regex else None
1✔
71

72
        self.regex = re_compile(search.regex)
1✔
73
        self.nregex = re_compile(search.nregex)
1✔
74
        self.wholeregex = re_compile(search.wholeregex)
1✔
75
        self.nwholeregex = re_compile(search.nwholeregex)
1✔
76

77
    def match(self, dirpath, filename):
1✔
78
        full_path = os.path.join(dirpath, filename)
1✔
79

80
        if self.regex and not self.regex.search(filename):
1✔
81
            return None
1✔
82

83
        if self.nregex and self.nregex.search(filename):
1✔
UNCOV
84
            return None
×
85

86
        if self.wholeregex and not self.wholeregex.search(full_path):
1✔
87
            return None
1✔
88

89
        if self.nwholeregex and self.nwholeregex.search(full_path):
1✔
UNCOV
90
            return None
×
91

92
        return full_path
1✔
93

94

95
class DeepScan:
1✔
96

97
    """Advanced directory tree scan"""
98

99
    def __init__(self, searches):
1✔
100
        self.roots = []
1✔
101
        self.searches = searches
1✔
102

103
    def scan(self):
1✔
104
        """Perform requested searches and yield each match"""
105
        logging.getLogger(__name__).debug(
1✔
106
            'DeepScan.scan: searches=%s', str(self.searches))
107
        yield_time = time.time()
1✔
108

109
        for (top, searches) in self.searches.items():
1✔
110
            compiled_searches = [CompiledSearch(s) for s in searches]
1✔
111
            for (dirpath, _dirnames, filenames) in normalized_walk(top):
1✔
112
                for c in compiled_searches:
1✔
113
                    # fixme, don't match filename twice
114
                    for filename in filenames:
1✔
115
                        full_name = c.match(dirpath, filename)
1✔
116
                        if full_name is not None:
1✔
117
                            # fixme: support other commands
118
                            if c.command == 'delete':
1✔
119
                                yield Command.Delete(full_name)
1✔
120
                            elif c.command == 'shred':
1✔
121
                                yield Command.Shred(full_name)
1✔
122

123
                if time.time() - yield_time > 0.25:
1✔
124
                    # allow GTK+ to process the idle loop
125
                    yield True
1✔
126
                    yield_time = time.time()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc