Passed
Push — beta ( 2462f8...02f296 )
by Dean
02:59
created

Mapper   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
wmc 36
c 6
b 0
f 1
dl 0
loc 209
ccs 0
cts 97
cp 0
rs 8.8

11 Methods

Rating   Name   Duplication   Size   Complexity  
A request_movie() 0 9 2
A map_movie() 0 7 2
B match() 0 16 5
A start() 0 8 1
A request_episode() 0 9 2
A __init__() 0 2 1
C _build_episode_request() 0 59 8
A map_episode() 0 13 2
B _iter_services() 0 15 5
A _build_movie_request() 0 21 3
B map() 0 15 5
1
from oem.media.movie import MovieMatch
2
from oem.media.show import EpisodeMatch
3
4
from plugin.core.environment import Environment
5
from plugin.core.helpers.variable import try_convert
6
from plugin.modules.core.base import Module
7
8
from oem import OemClient
9
from oem.media.show.identifier import EpisodeIdentifier
10
from oem.providers import IncrementalReleaseProvider
11
from oem_storage_codernitydb.main import CodernityDbStorage
12
from plex_metadata import Guid
13
import logging
14
import os
15
16
log = logging.getLogger(__name__)
17
18
19
class Mapper(Module):
20
    __key__ = 'mapper'
21
22
    services = {
23
        'anidb': ['imdb', 'tvdb']  # Try match against imdb database first
24
    }
25
26
    def __init__(self):
27
        self._client = None
28
29
    def start(self):
30
        # Construct oem client
31
        self._client = OemClient(
32
            provider=IncrementalReleaseProvider(
33
                fmt='minimize+msgpack',
34
                storage=CodernityDbStorage(os.path.join(
35
                    Environment.path.plugin_caches,
36
                    'oem'
37
                ))
38
            )
39
        )
40
41
    #
42
    # Movie
43
    #
44
45
    def map_movie(self, guid, movie):
46
        # Ensure guid has been parsed
47
        if type(guid) is str:
48
            guid = Guid.parse(guid)
49
50
        # Try match movie against database
51
        return self.map(guid.service, guid.id)
52
53
    def request_movie(self, guid, movie):
54
        # Try match movie against database
55
        supported, match = self.map_movie(guid, movie)
56
57
        if not match:
58
            return supported, None
59
60
        # Build request for Trakt.tv
61
        return supported, self._build_movie_request(match, movie)
62
63
    #
64
    # Shows
65
    #
66
67
    def map_episode(self, guid, season_num, episode_num):
68
        # Ensure guid has been parsed
69
        if type(guid) is str:
70
            guid = Guid.parse(guid)
71
72
        # Build episode identifier
73
        identifier = EpisodeIdentifier(
74
            season_num=season_num,
75
            episode_num=episode_num
76
        )
77
78
        # Try match episode against database
79
        return self.map(guid.service, guid.id, identifier)
80
81
    def request_episode(self, guid, episode):
82
        # Try match episode against database
83
        supported, match = self.map_episode(guid, episode.season.index, episode.index)
84
85
        if not match:
86
            return supported, None
87
88
        # Build request for Trakt.tv
89
        return supported, self._build_episode_request(match, episode)
90
91
    #
92
    # Helper methods
93
    #
94
95
    def map(self, source, key, identifier=None):
96
        if source not in self.services:
97
            return False, None
98
99
        for target, service in self._iter_services(source):
100
            try:
101
                match = service.map(key, identifier)
102
            except Exception, ex:
103
                log.warn('Unable to retrieve mapping for %r (%s -> %s) - %s', key, source, target, ex, exc_info=True)
104
                continue
105
106
            if match:
107
                return True, match
108
109
        return True, None
110
111
    def match(self, source, key):
112
        if source not in self.services:
113
            return False, None
114
115
        for target, service in self._iter_services(source):
116
            try:
117
                result = service.get(key)
118
            except Exception, ex:
119
                log.warn('Unable to retrieve item for %r (%s -> %s) - %s', key, source, target, ex, exc_info=True)
120
                continue
121
122
            if result:
123
                return True, result
124
125
        log.warn('Unable to find item for %s: %r', source, key)
126
        return True, None
127
128
    def _build_episode_request(self, match, episode):
129
        if isinstance(match, MovieMatch):
130
            # Retrieve mapped identifier
131
            service = match.identifiers.keys()[0]
132
            key = try_convert(match.identifiers[service], int, match.identifiers[service])
133
134
            if type(key) not in [int, str]:
135
                log.info('Unsupported key: %r', key)
136
                return None
137
138
            return {
139
                'movie': {
140
                    'title': episode.show.title,
141
                    'year': episode.show.year,
142
143
                    'ids': {
144
                        service: key
145
                    }
146
                }
147
            }
148
149
        if isinstance(match, EpisodeMatch):
150
            if match.absolute_num is not None:
151
                # TODO support for absolute episode scrobbling
152
                log.info('Absolute season mappings are not supported yet')
153
                return None
154
155
            if match.season_num is None or match.episode_num is None:
156
                log.warn('Missing season or episode number in %r', match)
157
                return None
158
159
            # Retrieve mapped identifier
160
            service = match.identifiers.keys()[0]
161
            key = try_convert(match.identifiers[service], int, match.identifiers[service])
162
163
            if type(key) not in [int, str]:
164
                log.info('Unsupported key: %r', key)
165
                return None
166
167
            # Build request
168
            return {
169
                'show': {
170
                    'title': episode.show.title,
171
                    'year': episode.year,
172
173
                    'ids': {
174
                        service: key
175
                    }
176
                },
177
                'episode': {
178
                    'title': episode.title,
179
180
                    'season': match.season_num,
181
                    'number': match.episode_num
182
                }
183
            }
184
185
        log.warn('Unknown match returned: %r', match)
186
        return None
187
    
188
    def _build_movie_request(self, match, movie):
189
        if not isinstance(match, MovieMatch):
190
            log.warn('Invalid match returned for movie: %r')
191
            return None
192
193
        # Retrieve mapped identifier
194
        service = movie.identifiers.keys()[0]
195
        key = try_convert(movie.identifiers[service], int, movie.identifiers[service])
196
197
        if type(key) not in [int, str]:
198
            log.info('Unsupported key: %r', key)
199
            return None
200
201
        # Build request
202
        return {
203
            'movie': {
204
                'title': movie.title,
205
                'year': movie.year,
206
207
                'ids': {
208
                    service: key
209
                }
210
            }
211
        }
212
213
    def _iter_services(self, source):
214
        if source not in self.services:
215
            return
216
217
        for target in self.services[source]:
218
            try:
219
                service = self._client[source].to(target)
220
            except KeyError:
221
                log.warn('Unable to find service: %s -> %s', source, target)
222
                continue
223
            except Exception, ex:
224
                log.warn('Unable to retrieve service: %s -> %s - %s', source, target, ex, exc_info=True)
225
                continue
226
227
            yield target, service
228