Filters.match()   F
last analyzed

Complexity

Conditions 12

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 22
cts 22
cp 1
rs 2.7855
c 0
b 0
f 0
cc 12
crap 12

How to fix   Complexity   

Complexity

Complex classes like Filters.match() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1 1
from plugin.core.environment import Environment
2 1
from plugin.core.helpers.variable import flatten
3
4 1
import ipaddress
5 1
import logging
6
7 1
log = logging.getLogger(__name__)
8
9
10 1
class Filters(object):
11 1
    @classmethod
12 1
    def get(cls, key, normalize_values=True):
13 1
        value = Environment.get_pref(key)
14 1
        if not value:
15 1
            return None, None
16
17 1
        value = value.strip()
18
19
        # Allow all if wildcard (*) or blank
20 1
        if not value or value == '*':
21 1
            return None, None
22
23 1
        values = value.split(',')
24
25 1
        allow, deny = [], []
26
27 1
        for value in [v.strip() for v in values]:
28 1
            inverted = False
29
30
            # Check if this is an inverted value
31 1
            if value.startswith('-'):
32 1
                inverted = True
33 1
                value = value[1:]
34
35
            # Normalize values (if enabled)
36 1
            if normalize_values:
37 1
                value = cls.normalize(value)
38
39
            # Append value to list
40 1
            if not inverted:
41 1
                allow.append(value)
42
            else:
43 1
                deny.append(value)
44
45 1
        return allow, deny
46
47 1
    @classmethod
48 1
    def match(cls, key, f_current, f_validate, f_check=None, f_transform=None, normalize_values=True):
49 1
        if Environment.prefs[key] is None:
50 1
            log.debug('[%s] no preference found', key)
51 1
            return True
52
53 1
        if f_check and f_check():
54 1
            return True
55
56 1
        value = f_current()
57
58
        # Normalize value
59 1
        if value and normalize_values:
60 1
            value = cls.normalize(value)
61
62
        # Fetch filter
63 1
        f_allow, f_deny = cls.get(key, normalize_values=normalize_values)
64
65
        # Wildcard
66 1
        if f_allow is None and f_deny is None:
67 1
            log.debug('[%s] %r - wildcard', key, value)
68 1
            return True
69
70 1
        if f_transform:
71
            # Transform filter values
72 1
            f_allow = [f_transform(x) for x in f_allow]
73 1
            f_deny = [f_transform(x) for x in f_deny]
74
75 1
        log.debug('[%s] validate - value: %r, allow: %s, deny: %s', key, value, f_allow, f_deny)
76
77 1
        if f_validate(value, f_allow, f_deny):
78 1
            log.info('[%s] %r - filtered', key, value)
79 1
            return False
80
81 1
        return True
82
83 1
    @classmethod
84
    def normalize(cls, value):
85 1
        if type(value) is list:
86 1
            return [cls.normalize(v) for v in value]
87
88
        # Function option
89 1
        if value.startswith('#'):
90 1
            value = flatten(value)
91
92 1
            if value:
93 1
                return '#' + value
94
95 1
            return value
96
97
        # Basic option
98 1
        if value:
99 1
            value = value.strip()
100
101 1
        return flatten(value)
102
103 1
    @classmethod
104
    def is_valid_user(cls, user):
105 1
        return cls.match(
106
            'scrobble_names',
107
            f_current=lambda: user.get('title') if user else None,
108
            f_validate=lambda value, f_allow, f_deny: (
109
                (f_allow and (
110
                    not user or
111
                    value not in f_allow
112
                )) or
113
                value in f_deny
114
            )
115
        )
116
117 1
    @classmethod
118
    def is_valid_client(cls, player):
119 1
        log.debug('Filters.is_valid_client(%r)', player)
120
121 1
        def f_current():
122 1
            if not player:
123 1
                return None
124
125 1
            values = [player.get('title')]
126
127
            # Product
128 1
            product = player.get('product', '').lower()
129
130 1
            if product == 'dlna':
131 1
                values.append('#dlna')
132
133 1
            return values
134
135 1
        def f_validate(values, f_allow, f_deny):
136 1
            if f_allow:
137
                # Check if player details exist
138 1
                if not player:
139 1
                    return True
140
141
                # Check if player is allowed
142 1
                if not cls._contains_one(values, f_allow):
143 1
                    return True
144
145
            # Check if player is denied
146 1
            if cls._contains_one(values, f_deny):
147 1
                return True
148
149 1
            return False
150
151 1
        return cls.match(
152
            'scrobble_clients',
153
            f_current=f_current,
154
            f_validate=f_validate
155
        )
156
157 1
    @classmethod
158
    def is_valid_metadata_section(cls, metadata):
159 1
        return cls.match(
160
            'filter_sections',
161
            f_current=lambda: metadata.section.title,
162
            f_validate=lambda value, f_allow, f_deny: (
163
                (f_allow and value not in f_allow) or
164
                value in f_deny
165
            ),
166
            f_check=lambda: (
167
                not metadata or
168
                not metadata.section.title
169
            )
170
        )
171
172 1
    @classmethod
173
    def is_valid_section_name(cls, section_name):
174 1
        return cls.match(
175
            'filter_sections',
176
            f_current=lambda: section_name,
177
            f_validate=lambda value, f_allow, f_deny: (
178
                (f_allow and value not in f_allow) or
179
                value in f_deny
180
            ),
181
            f_check=lambda: (
182
                not section_name
183
            )
184
        )
185
186 1
    @classmethod
187
    def is_valid_address(cls, client):
188 1
        def f_current():
189 1
            if not client or not client['address']:
190 1
                return None
191
192 1
            value = client['address']
193
194 1
            try:
195 1
                return ipaddress.ip_address(unicode(value))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'unicode'
Loading history...
196 1
            except ValueError:
197 1
                log.warn('validate "filter_networks" - unable to parse IP Address: %s', repr(value))
198 1
                return None
199
200 1
        def f_validate(value, f_allow, f_deny):
201 1
            if not value:
202 1
                return True
203
204 1
            allowed = any([
205
                value in network
206
                for network in f_allow
207
                if network is not None
208
            ])
209
210 1
            denied = any([
211
                 value in network
212
                 for network in f_deny
213
                 if network is not None
214
             ])
215
216 1
            return not allowed or denied
217
218 1
        def f_transform(value):
219 1
            if not value:
220 1
                return None
221
222 1
            try:
223 1
                return ipaddress.ip_network(unicode(value))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'unicode'
Loading history...
224 1
            except ValueError:
225 1
                log.warn('validate "filter_networks" - unable to parse IP Network: %s', repr(value))
226 1
                return None
227
228 1
        return cls.match(
229
            'filter_networks',
230
            normalize_values=False,
231
            f_current=f_current,
232
            f_validate=f_validate,
233
            f_transform=f_transform
234
        )
235
236 1
    @classmethod
237
    def _contains_one(cls, values, f_items):
238 1
        for value in values:
239 1
            if value in f_items:
240 1
                return True
241
242
        return False
243