Passed
Push — develop ( 6fb855...7cfc9a )
by Dean
02:39
created

SyncStateTrakt.flush()   B

Complexity

Conditions 4

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

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