Passed
Push — master ( 64ad20...04166b )
by Dean
02:49
created

Table.__init__()   A

Complexity

Conditions 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1
Metric Value
cc 1
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 1
rs 9.4285
1 1
from plugin.core.helpers.variable import try_convert
2 1
from plugin.core.backup import BackupManager
3 1
from plugin.core.constants import GUID_SERVICES
4 1
from plugin.core.database import Database
5
6 1
from stash import ApswArchive
7 1
from trakt_sync.cache.backends import StashBackend
0 ignored issues
show
Bug introduced by
The name backends does not seem to exist in module trakt_sync.cache.
Loading history...
Configuration introduced by
Unable to import 'trakt_sync.cache.backends' (invalid syntax (<string>, line 11))

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8 1
from trakt_sync.cache.main import Cache
9 1
from trakt_sync.differ.core.base import KEY_AGENTS
0 ignored issues
show
Unused Code introduced by
Unused KEY_AGENTS imported from trakt_sync.differ.core.base
Loading history...
10 1
import elapsed
11 1
import logging
12 1
import os
13 1
import trakt.objects
14
15 1
IGNORED_DATA = [
16
    Cache.Data.get(Cache.Data.Liked),
17
    Cache.Data.get(Cache.Data.Personal)
18
]
19
20 1
log = logging.getLogger(__name__)
21
22
23 1
class SyncStateTrakt(object):
24 1
    def __init__(self, state):
25 1
        self.state = state
26 1
        self.task = state.task
27
28 1
        self.cache = None
29
30 1
        self.changes = None
31 1
        self.table = Table(self.task)
32
33 1
    def load(self):
34
        # Construct cache
35
        self.cache = self._build_cache()
36
37
        # Load table handler
38
        self.table.load()
39
40 1
    def _build_cache(self):
41
        def storage(name):
42
            return StashBackend(
43
                ApswArchive(Database.cache('trakt'), name),
44
                'lru:///?capacity=500&compact_threshold=1500',
45
                'pickle:///?protocol=2'
46
            )
47
48
        cache = Cache(self.task.media, self.task.data, storage)
49
50
        # Bind to cache events
51
        cache.events.on([
52
            'refresh.sync.progress',
53
            'refresh.list.progress'
54
        ], self.on_refresh_progress)
55
56
        return cache
57
58 1
    def on_refresh_progress(self, source, current):
0 ignored issues
show
Unused Code introduced by
The argument current seems to be unused.
Loading history...
59
        # Step refresh progress for `source`
60
        self.task.progress.group(SyncStateTrakt, 'refresh:%s' % source).step()
61
62 1
    def __getitem__(self, key):
63
        collection = [
64
            self.task.account.trakt.username,
65
            Cache.Media.get(key[0]),
66
            Cache.Data.get(key[1])
67
        ]
68
69
        if len(key) > 2:
70
            # Include extra parameters (list id)
71
            collection.extend(key[2:])
72
73
        return self.cache[collection]
74
75 1
    def invalidate(self, *key):
76
        """Invalidate collection in trakt cache"""
77
        username = self.task.account.trakt.username
78
79
        # Invalidate collection
80
        self.cache.invalidate([username] + list(key))
81
82
        log.debug('Invalidated trakt cache %r for account: %r', key, username)
83
84 1
    @elapsed.clock
85
    def refresh(self):
86
        # Task checkpoint
87
        self.task.checkpoint()
88
89
        # Construct progress groups
90
        def setup_progress_group(source):
91
            # Retrieve steps from cache source
92
            steps = self.cache.source(source).steps()
93
94
            # Setup progress group with total steps
95
            self.task.progress.group(SyncStateTrakt, 'refresh:%s' % source).add(steps)
96
97
        setup_progress_group('list')
98
        setup_progress_group('sync')
99
100
        # Refresh cache for account, store changes
101
        self.changes = self.cache.refresh(self.task.account.trakt.username)
102
103
        # Resolve changes
104
        self.changes = list(self.changes)
105
106
        # Reset current table
107
        self.table.reset()
108
109 1
    @elapsed.clock
110
    def build_table(self):
111
        # Build table from cache
112
        self.table.build(self.cache)
113
114 1
    @elapsed.clock
115
    def flush(self):
116
        with elapsed.clock(SyncStateTrakt, 'flush:collections'):
117
            # Flush trakt collections to disk
118
            self.cache.collections.flush()
119
120
        with elapsed.clock(SyncStateTrakt, 'flush:stores'):
121
            # Flush trakt stores to disk
122
            for key, store in self.cache.stores.items():
123
                log.debug('[%-38s] Flushing collection...', '/'.join(key))
124
125
                store.flush()
126
127
        # Store backup of trakt data
128
        group = os.path.join('trakt', str(self.task.account.id))
129
130
        BackupManager.database.backup(group, Database.cache('trakt'), self.task.id, {
131
            'account': {
132
                'id': self.task.account.id,
133
                'name': self.task.account.name,
134
135
                'trakt': {
136
                    'username': self.task.account.trakt.username
137
                }
138
            }
139
        })
140
141
142 1
class Table(object):
143 1
    def __init__(self, task):
144 1
        self.task = task
145
146 1
        self.table = None
147
148 1
        self.movies = None
149 1
        self.shows = None
150 1
        self.episodes = None
151
152 1
        self._data = None
153 1
        self._media = None
154
155 1
    def load(self):
156
        # Parse data/media enums into lists
157
        self._data = [
158
            Cache.Data.get(d)
159
            for d in Cache.Data.parse(self.task.data)
160
        ]
161
162
        self._media = [
163
            Cache.Media.get(m)
164
            for m in Cache.Media.parse(self.task.media)
165
        ]
166
167 1
    def reset(self):
168
        self.table = None
169
170
        self.movies = None
171
        self.shows = None
172
        self.episodes = None
173
174 1
    def build(self, cache):
175
        # Map item `keys` into a table
176
        self.table = {}
177
178
        self.movies = set()
179
        self.shows = set()
180
        self.episodes = {}
181
182
        log.debug('Building table...')
183
184
        log.debug(' - Data: %s', ', '.join([
185
            '/'.join(x) if type(x) is tuple else x
186
            for x in self._data
187
        ]))
188
189
        log.debug(' - Media: %s', ', '.join([
190
            '/'.join(x) if type(x) is tuple else x
191
            for x in self._media
192
        ]))
193
194
        # Construct progress group
195
        self.task.progress.group(Table, 'build').add(len(cache.collections))
196
197
        # Map each item in cache collections
198
        for key in cache.collections:
199
            # Increment one step
200
            self.task.progress.group(Table, 'build').step()
201
202
            # Parse `key`
203
            if len(key) == 3:
204
                # Sync
205
                username, media, data = key
206
            elif len(key) == 4:
207
                # Lists
208
                username, media, data = tuple(key[0:3])
209
            else:
210
                log.warn('Unknown key: %r', key)
211
                continue
212
213
            if username != self.task.account.trakt.username:
214
                # Collection isn't for the current account
215
                continue
216
217
            if media and media not in self._media:
218
                log.debug('[%-38s] Media %r has not been enabled', '/'.join(key), media)
219
                continue
220
221
            if data not in self._data:
222
                log.debug('[%-38s] Data %r has not been enabled', '/'.join(key), data)
223
                continue
224
225
            # Map store items
226
            if data not in IGNORED_DATA:
227
                self.map_items(key, cache[key], media)
228
229
        log.debug(
230
            'Built table with %d keys (movies: %d, shows: %d, episodes: %d)',
231
            len(self.table),
232
            len(self.movies),
233
            len(self.shows),
234
            len(self.episodes)
235
        )
236
237 1
    def get_keys(self, key, media):
0 ignored issues
show
Unused Code introduced by
The argument key seems to be unused.
Loading history...
238
        if media == 'movies':
239
            return self.movies
240
241
        if media in ['shows', 'seasons', 'episodes']:
242
            return self.shows
243
244
        return None
245
246 1
    @staticmethod
247
    def get_media(item):
248
        i_type = type(item)
249
250
        if i_type is trakt.objects.Movie:
251
            return 'movies'
252
253
        if i_type is trakt.objects.Show:
254
            return 'shows'
255
256
        if i_type is trakt.objects.Season:
257
            return 'seasons'
258
259
        if i_type is trakt.objects.Episode:
260
            return 'episodes'
261
262
        log.warn('Unknown item type: %r', i_type)
263
        return None
264
265 1
    def map_items(self, key, store, media=None):
266
        # Retrieve key map
267
        if media is not None:
268
            keys = self.get_keys(key, media)
269
270
            if keys is None:
271
                log.debug('[%-38s] Collection has been ignored (unknown/unsupported media)', '/'.join(key))
272
                return
273
        else:
274
            keys = None
275
276
        # Map each item in store
277
        log.debug('[%-38s] Building table from collection...', '/'.join(key))
278
279
        for pk, item in store.iteritems():
280
            # Trim `pk` season/episode values
281
            if len(pk) > 2:
282
                pk = tuple(pk[:2])
283
284
            if pk[0] not in GUID_SERVICES:
285
                log.info('Ignoring item %r with an unknown primary agent: %r', item, pk)
286
                continue
287
288
            # Detect media type from `item`
289
            if media is not None:
290
                i_media = media
291
                i_keys = keys
292
            else:
293
                i_media = self.get_media(item)
294
                i_keys = self.get_keys(key, i_media)
295
296
            # Store `pk` in `keys
297
            if i_keys is not None:
298
                i_keys.add(pk)
299
300
            # Map `item.keys` -> `pk`
301
            for key in item.keys:
302
                # Expand `key`
303
                if type(key) is not tuple or len(key) != 2:
304
                    continue
305
306
                service, id = key
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
307
308
                # Check if agent is supported
309
                if service not in GUID_SERVICES:
310
                    continue
311
312
                # Cast service id to integer
313
                if service in ['tvdb', 'tmdb', 'tvrage']:
314
                    id = try_convert(id, int, id)
315
316
                # Store key in table
317
                key = (service, id)
318
319
                if key in self.table:
320
                    continue
321
322
                self.table[key] = pk
323
324
            # Map episodes in show
325
            if i_media == 'episodes':
326
                if type(item) is trakt.objects.Show:
327
                    if pk not in self.episodes:
328
                        self.episodes[pk] = set()
329
330
                    for identifier, _ in item.episodes():
331
                        self.episodes[pk].add(identifier)
332
                elif type(item) is trakt.objects.Episode:
333
                    # TODO
334
                    pass
335
                else:
336
                    log.debug('Unknown episode item: %r', item)
337
338
        # Task checkpoint
339
        self.task.checkpoint()
340
341 1
    def get(self, key, default=None):
342 1
        return self.table.get(key, default)
343
344 1
    def __getitem__(self, key):
345
        return self.table[key]
346