Example #1
0
    def __init__(self, main):
        super(SyncSource, self).__init__(main)

        # Differs
        self._movie_differ = MovieDiffer()
        self._show_differ = ShowDiffer()
Example #2
0
class SyncSource(Source):
    def __init__(self, main):
        super(SyncSource, self).__init__(main)

        # Differs
        self._movie_differ = MovieDiffer()
        self._show_differ = ShowDiffer()

    def refresh(self, username):
        # Emit "started" event
        self.events.emit('refresh.sync.started', source='sync', total=self.steps())
        current_step = 0

        # Fetch sync activity timestamps
        activities = Trakt['sync'].last_activities(exceptions=True)

        # Refresh each data set
        for m in self.media:
            media = enums.Media.get(m)

            for d in self.data:
                data = enums.Data.get(d)

                if not self.exists(d, m):
                    # Unsupported media + data combination
                    continue

                # Update `current` progress, emit "progress" event
                self.events.emit('refresh.sync.progress', source='sync', current=current_step)
                current_step += 1

                # Retrieve collection from database
                collection = self.get_collection(username, media, data)
                timestamp_key = enums.Data.get_timestamp_key(d)

                # Retrieve current timestamp from trakt.tv
                current = self._get_timestamp(activities, d, m)

                # Determine if cached items are still valid
                last = collection['timestamps'][media].get(timestamp_key)

                if last and last.tzinfo is None:
                    # Missing "tzinfo", assume UTC
                    last = last.replace(tzinfo=tzutc())

                if last and last == current:
                    # Latest data already cached
                    continue

                # Fetch latest data
                store = self.fetch(d, m)

                if store is None:
                    # Unable to retrieve data
                    continue

                # Find changes
                changes = self.diff(m, d, collection['store'], store)

                # Update collection
                self.update_store((username, media, data), store)

                collection['timestamps'][media][timestamp_key] = current

                if changes is None:
                    # No changes detected
                    continue

                # Return collection changes
                yield self._collection_key(m, d), changes

        # Emit "finished" event
        self.events.emit('refresh.sync.finished', source='sync', current=current_step)

    def steps(self):
        result = 0

        # Iterate over each data set and increment `result`
        for m in self.media:
            for d in self.data:
                if not self.exists(d, m):
                    # Unsupported media + data combination
                    continue

                result += 1

        return result

    def diff(self, media, data, base, current):
        if not base:
            if not current:
                return None

            # No `base` data stored, assume all the `current` items have been added
            if media == enums.Media.Movies:
                result = MovieResult(self._movie_differ)
            elif media in [enums.Media.Shows, enums.Media.Seasons, enums.Media.Episodes]:
                result = ShowResult(self._show_differ)
            else:
                raise Exception('Unknown media type: %r', media)

            # Update `result` with current items
            result.add(current)

            return result

        data_name = enums.Data.get(data)

        if media == enums.Media.Movies:
            result = self._movie_differ.run(base, current, handlers=[data_name])
        elif media in [enums.Media.Shows, enums.Media.Seasons, enums.Media.Episodes]:
            result = self._show_differ.run(base, current, handlers=[data_name])
        else:
            raise Exception('Unknown media type: %r', media)

        return result

    def fetch(self, data, media):
        # Ignore "sync/playback/shows" requests (not supported)
        if data == enums.Data.Playback and media == enums.Media.Shows:
            log.debug('Ignoring unsupported collection - data: %r, media: %r', data, media)
            return None

        # Find function to fetch the collection
        interface = enums.Data.get_interface(data)
        method = enums.Media.get(media)

        func = self.fetch_func(data, media)

        if func is None:
            return None

        # Execute `func` (fetch data from trakt.tv)
        path = '/'.join([interface, method])

        log.info('Fetching "%s"', path)

        try:
            return func(exceptions=True)
        except NotImplementedError:
            log.warn('Unable to fetch "%s", not implemented', path)

        return None

    @staticmethod
    def fetch_func(data, media):
        if type(data) is not str:
            data = enums.Data.get_interface(data)

        if type(media) is not str:
            media = enums.Media.get(media)

        # Retrieve function (`method`) from `interface`
        return getattr(Trakt[data], media, None)

    @classmethod
    def exists(cls, data, media):
        func = cls.fetch_func(data, media)

        return func is not None

    @staticmethod
    def _get_timestamp(activities, data, media):
        method = enums.Media.get(media)

        if media in [enums.Media.Movies, enums.Media.Seasons, enums.Media.Episodes]:
            timestamps = activities[method]
        elif media == enums.Media.Shows:
            if data in [enums.Data.Collection, enums.Data.Playback, enums.Data.Watched, enums.Data.Watchlist]:
                # Map shows (collection, playback, watched, watchlist) -> episodes
                timestamps = activities['episodes']
            else:
                timestamps = activities[method]
        else:
            # unknown data/media combination
            raise ValueError()

        # Retrieve timestamp
        value = timestamps.get(
            enums.Data.get_timestamp_key(data)
        )

        # Parse timestamp
        return from_iso8601(value)

    def get_collection(self, username, media, data):
        key = self._storage_key(username, media, data)

        collection = super(SyncSource, self).get_collection(*key)

        if media not in collection['timestamps']:
            collection['timestamps'][media] = {}

        return collection

    @staticmethod
    def _collection_key(media, data):
        if media in COLLECTION_ENUM_MAP and data in COLLECTION_ENUM_MAP[media]:
            # Apply collection map
            media, data = COLLECTION_ENUM_MAP[media][data]

        return media, data

    @classmethod
    def _storage_key(cls, username, media, data):
        if media in COLLECTION_NAME_MAP and data in COLLECTION_NAME_MAP[media]:
            # Apply collection map
            media, data = COLLECTION_NAME_MAP[media][data]

        return username, media, data