Пример #1
0
    def __init__(self, username, password, cache_path="./sdcache.db"):
        self._logger = logging.getLogger(__name__)

        self._username = username
        """:type: unicode"""

        self._password = hashlib.sha1(password).hexdigest()
        """:type: unicode"""

        self._cache = SchedulesDirectCache(cache_path)
        """:type: SchedulesDirectCache"""

        self._cache.init_database()

        self._force_program_refresh = False
        """:type: bool"""

        self._subscribed_lineups = None
        """:type: list[Lineup]"""

        self._token = None
        """:type: unicode"""

        self._status = None
        """:type: Status"""
Пример #2
0
 def __init__(self, username, password, cache_path = './sdcache.db'):
     self._logger = logging.getLogger(__name__)
     self._username = username
     self._password = password
     self._api = SchedulesDirectApi(self._username, self._password)
     self._cache = SchedulesDirectCache(cache_path)
     self._cache.init_database()
     self._force_lineup_refresh = False
     self._force_program_refresh = False
     self._subscribed_lineups = None
Пример #3
0
class SchedulesDirect(object):

    def __init__(self, username, password, cache_path = './sdcache.db'):
        self._logger = logging.getLogger(__name__)
        self._username = username
        self._password = password
        self._api = SchedulesDirectApi(self._username, self._password)
        self._cache = SchedulesDirectCache(cache_path)
        self._cache.init_database()
        self._force_lineup_refresh = False
        self._force_program_refresh = False
        self._subscribed_lineups = None

    def get_token(self):
        self._api.get_token()

    def get_status(self):
        return self._api.get_status()

    def is_online(self):
        return self._api.is_online()

    def get_headends_by_postal_code(self, country, postal_code):
        """

        :param country:
        :param postal_code:
        :return:
        :rtype: list[Headend]
        """
        headends = []
        headends_dict = self._api.get_headends_by_postal_code(country, postal_code)
        for headend in headends_dict:
            headends.append(Headend.decode(headend))

        return headends

    def get_subscribed_lineups(self):
        """

        :return:
        :rtype: list[Lineup]
        """
        if self._subscribed_lineups is not None:
            return self._subscribed_lineups
        self._subscribed_lineups = []
        lineups_dict = self._api.get_subscribed_lineups()
        for lineup_dict in lineups_dict:
            self._subscribed_lineups.append(Lineup.decode(lineup_dict))

        return self._subscribed_lineups

    def add_lineup(self, lineup_id):
        response = self._api.add_lineup(lineup_id)
        self._subscribed_lineups = None
        return AddRemoveLineupResponse.decode(response)

    def remove_lineup(self, lineup_id):
        response = self._api.remove_lineup(lineup_id)
        self._subscribed_lineups = None
        return AddRemoveLineupResponse.decode(response)

    def get_lineup_mapping(self, lineup_id, modified=None):
        lineup_mapping = None

        if not self._force_lineup_refresh and modified is not None:
            lineup_mapping = self._cache.get_lineup(lineup_id, modified.strftime('%Y-%m-%dT%H:%M:%SZ'))

        if lineup_mapping is None:
            lineup_mapping = self._api.get_lineup(lineup_id)
            self._cache.add_lineup(lineup_id, lineup_mapping['metadata']['modified'], lineup_mapping)

        return LineupMapping.decode(lineup_mapping)

    def get_lineup_mappings(self, status_lineups):
        """

        :param status_lineups:
        :return:
        :rtype: list[LineupMapping]
        """
        lineup_mappings = []

        if self._force_lineup_refresh:
            self._logger.info('WARNING: Force refreshing lineups.')

        for (lineup_id, modified) in status_lineups:
            lineup_mapping = self.get_lineup_mapping(lineup_id, modified)
            lineup_mappings.append(lineup_mapping)

        return lineup_mappings

    def cache_programs(self, programs):
        """

        :param programs:
        :type programs: list[(str, str)]
        :return:
        """
        self._logger.debug('Searching for uncached programs...')

        program_ids = set()

        if self._force_program_refresh:
            self._logger.info('WARNING: Force refreshing programs.')

        for (program_id, md5) in programs:
            if program_id in program_ids:
                continue
            if self._force_program_refresh or not self._cache.program_exists(program_id, md5):
                program_ids.add(program_id)

        self._logger.info('Found %s program(s) missing from cache.' % (len(program_ids)))

        if len(program_ids) == 0:
            return

        for batch in enumerate_batch(program_ids, 5000):
            self._logger.info('Requesting %s programs from SchedulesDirect.' % (len(batch)))
            programs = self._api.get_programs(batch)
            self._logger.info('Adding program(s) to program cache.')
            self._cache.add_programs(programs)
            self._logger.info('Added %s program(s) to program cache.' % (len(programs)))

    def get_program(self, program_id, md5=None):
        program = self._cache.get_program(program_id, md5)
        if program is None:
            return None
        return Program.decode(program)

    def get_schedules(self, station_ids, dates=None):
        """

        :param station_ids:
        :type station_ids: list[str]
        :return:
        :rtype: list[Schedule]
        """
        self._logger.debug('_get_schedules("%s","%s")' % (station_ids, dates))
        schedules = []

        if dates is None:
            schedules_request = [{'stationID':station} for station in station_ids]
        else:
            schedules_request = [{'stationID':station, 'date':dates} for station in station_ids]

        schedules_response = self._api.get_schedules(schedules_request)

        for schedule in schedules_response:
            schedules.append(Schedule.decode(schedule))

        return schedules
Пример #4
0
class SchedulesDirect(object):
    def __init__(self, username, password, cache_path="./sdcache.db"):
        self._logger = logging.getLogger(__name__)

        self._username = username
        """:type: unicode"""

        self._password = hashlib.sha1(password).hexdigest()
        """:type: unicode"""

        self._cache = SchedulesDirectCache(cache_path)
        """:type: SchedulesDirectCache"""

        self._cache.init_database()

        self._force_program_refresh = False
        """:type: bool"""

        self._subscribed_lineups = None
        """:type: list[Lineup]"""

        self._token = None
        """:type: unicode"""

        self._status = None
        """:type: Status"""

    def get_token(self):
        self._token = api.get_token(self._username, self._password)["token"]

    def get_status(self):
        self._status = Status.from_dict(api.get_status(self._token))
        return self._status

    def is_online(self):
        return self._status.system_status.status == u"Online"

    def get_headends_by_postal_code(self, country, postal_code):
        """

        :param country:
        :param postal_code:
        :return:
        :rtype: list[Headend]
        """
        headends_dict = api.get_headends_by_postal_code(self._token, country, postal_code)

        return [Headend.from_dict(headend) for headend in headends_dict]

    def get_subscribed_lineups(self):
        """

        :return:
        :rtype: list[Lineup]
        """
        if self._subscribed_lineups is not None:
            return self._subscribed_lineups

        lineups_dict = api.get_subscribed_lineups(self._token)

        self._subscribed_lineups = [Lineup.from_dict(lineup_dict) for lineup_dict in lineups_dict]

        return self._subscribed_lineups

    def add_lineup(self, lineup_id):
        response = api.add_lineup(self._token, lineup_id)
        self._subscribed_lineups = None
        return ChangeLineupResponse.from_dict(response)

    def remove_lineup(self, lineup_id):
        response = api.remove_lineup(self._token, lineup_id)
        self._subscribed_lineups = None
        return ChangeLineupResponse.from_dict(response)

    def get_lineup_map(self, lineup_id, modified=None):
        """

        :param lineup_id:
        :param modified:
        :return:
        :rtype: LineupMap
        """
        lineup_map = self._cache.get_lineup(lineup_id, modified)

        if lineup_map is None:
            lineup_map = api.get_lineup(self._token, lineup_id)
            self._cache.add_lineup(lineup_id, parse_datetime(lineup_map["metadata"]["modified"]), lineup_map)

        return LineupMap.from_dict(lineup_map)

    def get_lineup_map_list(self, lineups):
        """

        :param lineups:
        :return:
        :rtype: LineupMapList
        """
        lineup_map_list = LineupMapList()

        for lineup in lineups:
            lineup_map = self.get_lineup_map(lineup.lineup_id, lineup.modified)
            lineup_map_list.append(lineup_map)

        return lineup_map_list

    def cache_programs(self, program_hash_list):
        self._logger.debug(u"Searching for uncached programs...")

        self._cache.add_program_hashes(program_hash_list)

        programs_to_fetch = self._cache.get_program_delta()

        #self._logger.info(u"Found %s program(s) missing from cache.", len(programs_to_fetch))

        #if len(programs_to_fetch) == 0:
        #    return

        for batch in batched(programs_to_fetch, 5000):
            self._logger.info(u"Requesting %s programs from SchedulesDirect.", len(batch))
            programs = api.get_programs(self._token, batch)

            self._logger.info(u"Adding %s program(s) to program cache.", len(programs))
            self._cache.add_programs(programs)

    def cache_artwork(self):
        artwork_to_fetch = self._cache.get_artwork_delta()

        #self._logger.info(u"Found %s program artwork missing from cache.", len(artwork_to_fetch))

        #if len(artwork_to_fetch) == 0:
        #    return

        for batch in batched(artwork_to_fetch, 500):
            self._logger.info(u"Requesting %s program artwork from SchedulesDirect.", len(batch))
            artwork = api.get_metadata(batch)

            artwork_errors = (art for art in artwork if isinstance(art[u"data"], dict))

            for artwork_error in artwork_errors:
                self._logger.warn(u"Artwork for %s returned %s %s", artwork_error["programID"], artwork_error["data"]["errorCode"], artwork_error["data"]["errorMessage"])

            self._logger.info(u"Adding program artwork to cache.")
            self._cache.add_artwork(art for art in artwork if isinstance(art[u"data"], list))

    def refresh_cache(self, schedule_hash_set):
        with self._cache:
            changed_schedule_list = self.cache_schedules(schedule_hash_set)

            if len(changed_schedule_list) != 0:
                self.cache_programs(changed_schedule_list.get_program_hash_list())
                self._cache.update_program_max_schedule_dates(changed_schedule_list.get_program_max_schedule_dates())

            self._logger.info(u"Caching artwork...")
            self.cache_artwork()

            self._logger.info(u"Deleting expired schedules...")
            self._cache.delete_expired_schedules()

            self._logger.info(u"Deleting expired programs...")
            self._cache.delete_expired_programs()

            self._logger.info(u"Compressing cache database...")
            self._cache.compress_database()

    def get_cached_programs(self, program_ids):
        return {program.program_id: program for program in self._cache.get_programs(program_ids)}

    def get_cached_artwork(self, artwork_ids):
        return {program_artwork.artwork_id: program_artwork for program_artwork in self._cache.get_artwork(artwork_ids)}

    def get_schedule_hash_list(self, station_ids):
        self._logger.info(u"Requesting schedule hashes for %s stations...", len(station_ids))
        schedule_md5s_request = [{u"stationID": station} for station in station_ids]

        result = api.get_schedule_md5s(self._token, schedule_md5s_request)

        schedule_hash_list = [(station_id, parse_date(date), result[station_id][date]["md5"]) for station_id in result for date in result[station_id]]

        return schedule_hash_list

    def cache_schedules(self, schedule_hash_list):
        """

        :param schedule_hash_list:
        :return:
        :rtype: ScheduleList
        """
        self._cache.add_schedule_hashes(schedule_hash_list)

        schedules_to_fetch = self._cache.get_schedule_delta()

        #self._logger.info(u"Found %s schedule(s) missing from cache.", len(schedules_to_fetch))

        schedules_request = {}
        for (station_id, schedule_date) in schedules_to_fetch:
            if station_id not in schedules_request:
                schedules_request[station_id] = []
            schedules_request[station_id].append(schedule_date.strftime("%Y-%m-%d"))

        if len(schedules_request) != 0:
            #self._logger.info(u"Requesting %s schedules from SchedulesDirect.", len(schedules_to_fetch))
            schedules_response = api.get_schedules(self._token, [{"stationID": station_id, "date": schedules_request[station_id]} for station_id in schedules_request])
            self._cache.add_schedules(schedules_response)
            return ScheduleList.from_iterable(schedules_response)

        return ScheduleList()

    def get_cached_schedules(self, schedule_keys):
        """

        :param station_ids:
        :type station_ids: list[unicode]
        :param schedule_dates:
        :type schedule_dates: list[date]
        :return:
        :rtype: ScheduleList
        """
        return ScheduleList(self._cache.get_schedules(schedule_keys))

    def read_filter(self, lineup_map_list):
        import os.path
        import json
        filter = None

        if not os.path.isfile("./filter.json"):
            filter = {}

            for lineup_map in lineup_map_list:
                lineup_id = lineup_map.lineup.lineup_id
                filter[lineup_id] = {"default_import": 1}

                for channel in lineup_map.channels:
                    filter[lineup_id][channel.channel] = {"callsign": channel.station.callsign, "import": 1}

            f = open("./filter.json", "w")
            json.dump(filter, f, indent=4, sort_keys=True)
            f.close()

            return filter

        filter_changed = False
        f = open("./filter.json", "r")
        filter = json.load(f)
        f.close()

        for lineup_map in lineup_map_list:
            lineup_id = lineup_map.lineup.lineup_id

            if lineup_id not in filter:
                filter[lineup_id] = {"default_import": 1}
                filter_changed = True
            elif "default_import" not in filter[lineup_id]:
                filter[lineup_id]["default_import"] = 1
                filter_changed = True

            for channel in lineup_map.channels:
                channel_num = channel.channel

                if channel_num not in filter[lineup_id]:
                    filter[lineup_id][channel_num] = filter[lineup_id]["default_import"]
                    filter_changed = True
                elif filter[lineup_id][channel_num]["callsign"] != channel.station.callsign:
                    filter[lineup_id][channel_num]["callsign"] = channel.station.callsign
                    filter_changed = True

        if filter_changed:
            f = open("./filter.json", "w")
            json.dump(filter, f, indent=4, sort_keys=True)
            f.close()

        return filter

    def manage(self):
        while True:
            print(u"\nManage Account Options:\n")
            print(u"1. List subscribed lineups")
            print(u"2. Add lineup")
            print(u"3. Remove lineup")
            print(u"4. List lineup channels.")
            print(u"\nChoose an option or 'x' to exit.")
            choice = raw_input("> ")
            if choice == "x":
                break
            elif choice == "1":
                self._list_subscribed_lineups()
            elif choice == "2":
                self._add_lineup()
            elif choice == "3":
                self._remove_lineup()
            elif choice == "4":
                self._list_lineup_channels()

    def _list_subscribed_lineups(self):
        lineups = self.get_subscribed_lineups()
        print(u"\nSubscribed Lineups:\n")
        for lineup in lineups:
            print(u"Lineup:\t{0}".format(lineup.lineup_id))
            print(u"Name:\t{0}".format(lineup.name))
            print(u"Transport:\t{0}".format(lineup.transport))
            print(u"Location:\t{0}".format(lineup.location))
            print(u"")

    def _add_lineup(self):
        while True:
            print(u"\nAdd Lineup\n")
            print(u"Enter 5-digit zip or postal code or 'x' to cancel:")
            postal_code = raw_input("> ")
            if postal_code == "x":
                break

            # TODO: Handle more than just USA and CAN
            country = "USA"
            if postal_code[0].isalpha():
                country = "CAN"

            headends = self.get_headends_by_postal_code(country, postal_code)

            while True:
                subscribed_lineups = self.get_subscribed_lineups()
                subscribed_lineup_ids = [lineup.lineup_id for lineup in subscribed_lineups]

                headend_lineups = [(headend, lineup) for headend in headends for lineup in headend.lineups if lineup.lineup_id not in subscribed_lineup_ids]

                transport_set = {headend.type for (headend, lineup) in headend_lineups}

                options = []
                count = 0
                for transport in transport_set:
                    print(u"\nTransport: {0}\n".format(transport))
                    for (headend, lineup) in [(headend, lineup) for (headend, lineup) in headend_lineups if headend.type == transport]:
                        options.append((headend, lineup))
                        count += 1
                        print(u"\t{0}. {1.name} ({1.location})".format(count, lineup))

                print(u"\nChoose a lineup to add or 'x' to cancel.")
                choice = raw_input("> ")

                if choice == "x":
                    break

                choice = int(choice) - 1
                (headend, lineup) = options[choice]

                print(u"Are you sure you want to add '{0} ({1})'? (y/n)".format(lineup.name, headend.location))
                if raw_input("> ") != "y":
                    continue

                response = self.add_lineup(lineup.lineup_id)

                print(u"Schedules Direct returned '{0}'.".format(response.response_status.message))
                print(u"{0} lineup changes remaining.\n".format(response.changes_remaining))

    def _list_lineup_channels(self):

        while True:
            print(u"\nList Lineup Channels\n")

            subscribed_lineups = self.get_subscribed_lineups()

            options = []
            count = 0
            for lineup in subscribed_lineups:
                count += 1
                options.append(lineup)
                print(u"{0}. {1.name} ({1.location})".format(count, lineup))

            print(u"\nChoose a lineup to list channels or 'x' to cancel.")
            choice = raw_input("> ")
            if choice == "x":
                break

            choice = int(choice) - 1
            lineup = options[choice]

            lineup_map = self.get_lineup_map(lineup.lineup_id)

            for channel in lineup_map.channels:
                print(u"{0}\t{1.callsign} '{1.name}'".format(channel.channel, channel.station))

    def _remove_lineup(self):

        while True:
            print(u"\nRemove Lineup\n")

            subscribed_lineups = self.get_subscribed_lineups()

            options = []
            count = 0
            for lineup in subscribed_lineups:
                count += 1
                options.append(lineup)
                print(u"{0}. {1.name} ({1.location})".format(count, lineup))

            print(u"\nChoose a lineup to remove or 'x' to cancel.")
            choice = raw_input("> ")
            if choice == "x":
                break

            choice = int(choice) - 1
            lineup = options[choice]

            print(u"Are you sure you want to remove '{0.name} ({0.location})'? (y/n)".format(lineup))
            if raw_input("> ") != "y":
                continue

            response = self.remove_lineup(lineup.lineup_id)

            print(u"\nSchedules Direct returned '{0}'.".format(response.response_status.message))
            print(u"{0} lineup changes remaining.\n".format(response.changes_remaining))