Test Failed
Push — beta ( 7d3e07...a06fd2 )
by Dean
03:10
created

Mode.process_guid_episode()   F

Complexity

Conditions 12

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 144.9345

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 68
ccs 1
cts 38
cp 0.0263
rs 2.5212
cc 12
crap 144.9345

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Mode.process_guid_episode() 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, False, guid
230
231
        if guid.service in GUID_SERVICES:
232
            return True, True, guid
233
234
        # Try map show to a supported service (via OEM)
235
        supported, (id_service, id_key) = ModuleManager['mapper'].id(
236
            guid.service, guid.id,
237
            resolve_mappings=False
238
        )
239
240
        if not supported:
241
            return False, False, guid
242
243
        if not id_service or not id_key:
244
            return True, False, guid
245
246
        # Validate identifier
247
        if type(id_key) is list:
248
            log.info('[%s/%s] - List keys are not supported', guid.service, guid.id)
249
            return True, False, guid
250
251
        if type(id_key) not in [int, str]:
252
            log.info('[%s/%s] - Unsupported key: %r', guid.service, guid.id, id_key)
253
            return True, False, guid
254
255
        log.debug('[%s/%s] - Mapped to: %s/%s', guid.service, guid.id, id_service, id_key)
256
257 1
        # Return mapped guid
258
        return True, True, Guid.construct(id_service, id_key)
259
260
    @elapsed.clock
261
    def process_guid_episode(self, guid, season_num, episode_num):
262
        episodes = [(season_num, episode_num)]
263
264
        if not guid:
265
            return False, False, guid, episodes
266
267
        if guid.service in GUID_SERVICES:
268
            return True, True, guid, episodes
269
270
        # Try map episode to a supported service (via OEM)
271
        supported, match = ModuleManager['mapper'].map_episode(
272
            guid, season_num, episode_num,
273
            resolve_mappings=False
274
        )
275
276
        if not supported:
277
            return False, False, guid, episodes
278
279
        if match and match.identifiers:
280
            if not isinstance(match, EpisodeMatch):
281
                log.info('[%s/%s] - Episode -> Movie mappings are not supported', guid.service, guid.id)
282
                return True, False, guid, episodes
283
284
            if match.absolute_num is not None:
285
                log.info('[%s/%s] - Episode mappings with absolute numbers are not supported yet', guid.service, guid.id)
286
                return True, False, guid, episodes
287
288
            # Retrieve mapped show identifier
289
            id_service = match.identifiers.keys()[0]
290
            id_key = match.identifiers[id_service]
291
292
            # Parse list keys (episode -> movie mappings)
293
            if type(id_key) is list:
294
                log.info('[%s/%s] - List keys are not supported', guid.service, guid.id)
295
                return True, False, guid, episodes
296 1
297
                # if episode_num - 1 >= len(id_key):
298
                #     log.info('[%s/%s] - Episode %r was not found in the key %r', guid.service, guid.id, episode_num, id_key)
299
                #     return True, False, guid, episodes
300
301
                # id_key = id_key[episode_num - 1]
302
303
            # Cast `id_key` numbers to integers
304
            id_key = try_convert(id_key, int, id_key)
305
306
            if type(id_key) not in [int, str]:
307
                log.info('[%s/%s] - Unsupported key: %r', guid.service, guid.id, id_key)
308
                return True, False, guid, episodes
309
310
            # Update `episodes` list
311
            if match.mappings:
312
                # Use match mappings
313
                episodes = []
314
315
                for mapping in match.mappings:
316
                    log.debug('[%s/%s] (S%02dE%02d) - Mapped to: %r', guid.service, guid.id, season_num, episode_num, mapping)
317
                    episodes.append((mapping.season, mapping.number))
318
            else:
319
                # Use match identifier
320
                log.debug('[%s/%s] (S%02dE%02d) - Mapped to: %r', guid.service, guid.id, season_num, episode_num, match)
321
                episodes = [(match.season_num, match.episode_num)]
322
323
            # Return mapped episode result
324
            return True, True, Guid.construct(id_service, id_key), episodes
325
326
        log.debug('Unable to find mapping for %r S%02dE%02d', guid, season_num, episode_num)
327
        return True, False, guid, episodes
328
329
    def sections(self, section_type=None):
330
        # Retrieve "section" for current task
331
        section_key = self.current.kwargs.get('section', None)
332
333
        # Fetch sections from server
334
        p_sections = Plex['library'].sections()
335
336
        if p_sections is None:
337
            return None
338
339
        # Filter sections, map to dictionary
340
        result = {}
341
342
        for section in p_sections.filter(section_type, section_key):
343
            # Apply section name filter
344
            if not Filters.is_valid_section_name(section.title):
345
                continue
346
347
            try:
348
                key = int(section.key)
349
            except Exception, ex:
350
                log.warn('Unable to cast section key %r to integer: %s', section.key, ex, exc_info=True)
351
                continue
352
353
            result[key] = section.uuid
354
355
        return [(key, ) for key in result.keys()], result
356