| 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 |