Total Complexity | 70 |
Total Lines | 271 |
Duplicated Lines | 16.24 % |
Coverage | 15.29% |
Changes | 2 | ||
Bugs | 0 | Features | 0 |
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 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 |
|
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: |
|
|
|||
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: |
|
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 |