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