Passed
Push — beta ( 1d082f...70e2cb )
by Dean
02:56
created

Mode   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 271
Duplicated Lines 16.24 %

Test Coverage

Coverage 15.29%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 70
c 2
b 0
f 0
dl 44
loc 271
ccs 24
cts 157
cp 0.1529
rs 2.7272

20 Methods

Rating   Name   Duplication   Size   Complexity  
A current() 0 3 1
A checkpoint() 0 5 2
A modes() 0 3 1
A stop() 0 2 1
A __init__() 0 13 3
A construct() 0 2 1
A start() 0 2 1
F get_enabled_data() 0 56 14
B execute_handlers() 0 17 6
A configuration() 0 3 1
A handlers() 0 3 1
A trakt() 0 6 3
A run() 0 2 1
A is_data_enabled() 0 3 1
A get_data() 0 6 3
B execute_children() 0 18 5
A plex() 0 6 3
B sections() 0 27 6
F process_guid_episode() 21 38 9
C process_guid() 23 30 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Mode 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.constants import GUID_SERVICES
2 1
from plugin.core.filters import Filters
3 1
from plugin.core.helpers.variable import try_convert
4 1
from plugin.modules.core.manager import ModuleManager
5 1
from plugin.sync import SyncMedia, SyncData, SyncMode
6
7 1
from oem.media.show import EpisodeMatch
8 1
from plex import Plex
9 1
from plex_metadata import Guid
10 1
import elapsed
11 1
import itertools
12 1
import logging
13
14 1
log = logging.getLogger(__name__)
15
16 1
TRAKT_DATA_MAP = {
17
    SyncMedia.Movies: [
18
        SyncData.Collection,
19
        SyncData.Playback,
20
        SyncData.Ratings,
21
        SyncData.Watched,
22
        # SyncData.Watchlist
23
    ],
24
    SyncMedia.Shows: [
25
        SyncData.Ratings
26
    ],
27
    SyncMedia.Seasons: [
28
        SyncData.Ratings
29
    ],
30
    SyncMedia.Episodes: [
31
        SyncData.Collection,
32
        SyncData.Playback,
33
        SyncData.Ratings,
34
        SyncData.Watched,
35
        # SyncData.Watchlist
36
    ]
37
}
38
39 1
DATA_PREFERENCE_MAP = {
40
    SyncData.Collection:    'sync.collection.mode',
41
    SyncData.Playback:      'sync.playback.mode',
42
    SyncData.Ratings:       'sync.ratings.mode',
43
    SyncData.Watched:       'sync.watched.mode',
44
45
    # Lists
46
    SyncData.Liked:         'sync.lists.liked.mode',
47
    SyncData.Personal:      'sync.lists.personal.mode',
48
    SyncData.Watchlist:     'sync.lists.watchlist.mode',
49
}
50
51
52 1
class Mode(object):
53 1
    data = None
54 1
    mode = None
55
56 1
    children = []
57
58 1
    def __init__(self, task):
59
        self.__task = task
60
61
        self.children = [c(task) for c in self.children]
62
63
        # Retrieve enabled data
64
        self.enabled_data = self.get_enabled_data()
65
66
        # Determine if mode should be enabled
67
        self.enabled = len(self.enabled_data) > 0
68
69
        if not self.enabled:
70
            log.debug('Mode %r disabled on: %r', self.mode, self)
71
72 1
    @property
73
    def current(self):
74
        return self.__task
75
76 1
    @property
77
    def configuration(self):
78
        return self.__task.configuration
79
80 1
    @property
81
    def handlers(self):
82
        return self.__task.handlers
83
84 1
    @property
85
    def modes(self):
86
        return self.__task.modes
87
88 1
    @property
89
    def plex(self):
90
        if not self.current or not self.current.state:
91
            return None
92
93
        return self.current.state.plex
94
95 1
    @property
96
    def trakt(self):
97
        if not self.current or not self.current.state:
98
            return None
99
100
        return self.current.state.trakt
101
102 1
    def construct(self):
103
        pass
104
105 1
    def start(self):
106
        pass
107
108 1
    def run(self):
109
        raise NotImplementedError
110
111 1
    def stop(self):
112
        pass
113
114 1
    def checkpoint(self):
115
        if self.current is None:
116
            return
117
118
        self.current.checkpoint()
119
120 1
    def execute_children(self, name, force=None):
121
        # Run method on children
122
        for c in self.children:
123
            if not force and not c.enabled:
124
                log.debug('Ignoring %s() call on child: %r', name, c)
125
                continue
126
127
            # Find method `name` in child
128
            log.info('Executing %s() on child: %r', name, c)
129
130
            func = getattr(c, name, None)
131
132
            if not func:
133
                log.warn('Unknown method: %r', name)
134
                continue
135
136
            # Run method on child
137
            func()
138
139 1
    @elapsed.clock
140
    def execute_handlers(self, media, data, *args, **kwargs):
141
        if type(media) is not list:
142
            media = [media]
143
144
        if type(data) is not list:
145
            data = [data]
146
147
        for m, d in itertools.product(media, data):
148
            if d not in self.handlers:
149
                log.debug('Unable to find handler for data: %r', d)
150
                continue
151
152
            try:
153
                self.handlers[d].run(m, self.mode, *args, **kwargs)
154
            except Exception, ex:
155
                log.warn('Exception raised in handlers[%r].run(%r, ...): %s', d, m, ex, exc_info=True)
156
157 1
    def get_enabled_data(self):
158
        config = self.configuration
159
160
        # Determine accepted modes
161
        modes = [SyncMode.Full]
162
163
        if self.mode == SyncMode.Full:
164
            modes.extend([
165
                SyncMode.FastPull,
166
                SyncMode.Pull,
167
                SyncMode.Push
168
            ])
169
        elif self.mode == SyncMode.FastPull:
170
            modes.extend([
171
                self.mode,
172
                SyncMode.Pull
173
            ])
174
        else:
175
            modes.append(self.mode)
176
177
        # Retrieve enabled data
178
        result = []
179
180
        if config['sync.watched.mode'] in modes:
181
            result.append(SyncData.Watched)
182
183
        if config['sync.ratings.mode'] in modes:
184
            result.append(SyncData.Ratings)
185
186
        if config['sync.playback.mode'] in modes:
187
            result.append(SyncData.Playback)
188
189
        if config['sync.collection.mode'] in modes:
190
            result.append(SyncData.Collection)
191
192
        # Lists
193
        if config['sync.lists.watchlist.mode'] in modes:
194
            result.append(SyncData.Watchlist)
195
196
        if config['sync.lists.liked.mode'] in modes:
197
            result.append(SyncData.Liked)
198
199
        if config['sync.lists.personal.mode'] in modes:
200
            result.append(SyncData.Personal)
201
202
        # Filter `result` to data provided by this mode
203
        if self.data is None:
204
            log.warn('No "data" property defined on %r', self)
205
            return result
206
207
        if self.data == SyncData.All:
208
            return result
209
210
        return [
211
            data for data in result
212
            if data in self.data
213
        ]
214
215 1
    def get_data(self, media):
216
        for data in TRAKT_DATA_MAP[media]:
217
            if not self.is_data_enabled(data):
218
                continue
219
220
            yield data
221
222 1
    @elapsed.clock
223
    def is_data_enabled(self, data):
224
        return data in self.enabled_data
225
226 1
    @elapsed.clock
227
    def process_guid(self, guid):
228
        if not guid:
229
            return False, guid
230
231 View Code Duplication
        if guid.service not in GUID_SERVICES:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
232
            # Try map show to a supported service (via OEM)
233
            supported, item = ModuleManager['mapper'].match(guid.service, guid.id)
234
235
            if not supported:
236
                return False, guid
237
238
            if item and item.identifiers:
239
                # Retrieve mapped show identifier
240
                service = item.identifiers.keys()[0]
241
                key = try_convert(item.identifiers[service], int, item.identifiers[service])
242
243
                if type(key) not in [int, str]:
244
                    log.info('[%s/%s] - Unsupported key: %r', guid.service, guid.id, key)
245
                    return False, guid
246
247
                log.debug('[%s/%s] - Mapped to: %r', guid.service, guid.id, item)
248
249
                # Return mapped guid
250
                return True, Guid.construct(service, key)
251
252
            log.debug('Unable to find mapping for %r', guid)
253
            return False, guid
254
255
        return True, guid
256
257 1
    @elapsed.clock
258
    def process_guid_episode(self, guid, season_num, episode_num):
259
        if not guid:
260
            return False, guid, season_num, episode_num
261
262
        if guid.service not in GUID_SERVICES:
263
            # Try map episode to a supported service (via OEM)
264
            supported, match = ModuleManager['mapper'].map_episode(guid, season_num, episode_num)
265
266
            if not supported:
267
                return False, guid, season_num, episode_num
268
269 View Code Duplication
            if match and match.identifiers:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
270
                if not isinstance(match, EpisodeMatch):
271
                    log.info('[%s/%s] - Episode -> Movie mappings are not supported', guid.service, guid.id)
272
                    return False, guid, season_num, episode_num
273
274
                if match.absolute_num is not None:
275
                    log.info('[%s/%s] - Episode mappings with absolute numbers are not supported yet', guid.service, guid.id)
276
                    return False, guid, season_num, episode_num
277
278
                # Retrieve mapped show identifier
279
                service = match.identifiers.keys()[0]
280
                key = try_convert(match.identifiers[service], int, match.identifiers[service])
281
282
                if type(key) not in [int, str]:
283
                    log.info('[%s/%s] - Unsupported key: %r', guid.service, guid.id, key)
284
                    return False, guid, season_num, episode_num
285
286
                log.debug('[%s/%s] (S%02dE%02d) - Mapped to: %r', guid.service, guid.id, season_num, episode_num, match)
287
288
                # Return mapped episode result
289
                return True, Guid.construct(service, key), match.season_num, match.episode_num
290
291
            log.debug('Unable to find mapping for %r S%02dE%02d', guid, season_num, episode_num)
292
            return False, guid, season_num, episode_num
293
294
        return True, guid, season_num, episode_num
295
296 1
    def sections(self, section_type=None):
297
        # Retrieve "section" for current task
298
        section_key = self.current.kwargs.get('section', None)
299
300
        # Fetch sections from server
301
        p_sections = Plex['library'].sections()
302
303
        if p_sections is None:
304
            return None
305
306
        # Filter sections, map to dictionary
307
        result = {}
308
309
        for section in p_sections.filter(section_type, section_key):
310
            # Apply section name filter
311
            if not Filters.is_valid_section_name(section.title):
312
                continue
313
314
            try:
315
                key = int(section.key)
316
            except Exception, ex:
317
                log.warn('Unable to cast section key %r to integer: %s', section.key, ex, exc_info=True)
318
                continue
319
320
            result[key] = section.uuid
321
322
        return [(key, ) for key in result.keys()], result
323