Пример #1
0
def move_old_locations(destination, dryrun=False):
    fetcher = ProgramFetcher(None, None, destination)
    for program_info in fetcher.get_all_program_infos():
        if program_info.version >= 1:
            logger.info('Skipping %s', program_info)
            continue
        program = program_info.program
        logger.info('Targeting %s', program['title'])
        seasons = program_info.seasons
        files_to_move = []
        for season, entries in seasons.items():
            season_folder = Entry.get_season_folder(destination, program,
                                                    season)
            for i, target_entry in enumerate(entries.sorted()):
                if target_entry.episode.number is None:
                    raise RuntimeError(
                        'You need to attempt sync for this program once '
                        'before running this migration.')
                src_entry = copy.deepcopy(target_entry)
                src_entry.episode = Episode(None)
                src_entry.episode.number = i + 1

                src_dest = os.path.join(
                    season_folder,
                    src_entry.get_target_basename(program, season),
                )

                target_dest = os.path.join(
                    season_folder,
                    target_entry.get_target_basename(program, season),
                )

                if src_dest != target_dest and os.path.isfile(src_dest):
                    files_to_move.append((src_dest, target_dest))
        while files_to_move:
            i = 0
            src_dest, target_dest = files_to_move.pop(0)
            if dryrun:
                logger.info('Would move %s to %s', src_dest, target_dest)
            elif os.path.isfile(target_dest):
                temp_path = next(tempfile._get_candidate_names())
                temp_path = os.path.join(
                    os.path.dirname(target_dest),
                    temp_path,
                )
                logger.info(
                    '%s exists. Moving temporarily to %s and postponing',
                    target_dest,
                    temp_path,
                )
                shutil.move(src_dest, temp_path)
                files_to_move.append((temp_path, target_dest))
            else:
                os.makedirs(os.path.dirname(target_dest), exist_ok=True)
                logger.info('Moving %s to %s', src_dest, target_dest)
                shutil.move(src_dest, target_dest)

        if not dryrun:
            program_info.version = 1
            program_info.write()
Пример #2
0
def test_entry_to_dict():
    e = Entry(
        'some_fn',
        'some_url',
        datetime.datetime(2017, 6, 14),
        'some_etag',
        {'id': 'some_episode'},
    )
    assert e.to_dict() == {
        'fn': 'some_fn',
        'url': 'some_url',
        'date': '2017/06/14',
        'etag': 'some_etag',
        'episode': {
            'id': 'some_episode'
        },
    }
Пример #3
0
def test_entry_from_dict():
    data = {
        'fn': 'some_fn',
        'url': 'some_url',
        'date': '2017/06/14',
        'etag': 'some_etag',
        'episode': {
            'id': 'some_episode'
        },
    }
    e = Entry.from_dict(data)
    assert e.fn == data['fn']
    assert e.url == data['url']
    assert e.date == datetime.datetime(2017, 6, 14)
    assert e.etag == data['etag']
    assert e.episode.data == data['episode']

    del data['episode']
    e = Entry.from_dict(data)
    assert e.episode.data == {}
Пример #4
0
def test_entry_equality():
    assert Entry('fdsa', 'fdsa', None,
                 'etag') == Entry('asdf', 'asdf', datetime.datetime.min,
                                  'etag')
    assert Entry('fdsa', 'fdsa', None,
                 'etag') == Entry('fdsa', 'fdsa', None, 'etag')
    assert Entry('fdsa', 'fdsa', None, 'etag') != Entry(
        'fdsa', 'fdsa', None, 'not-same-etag')
Пример #5
0
def create_program_info(location='/tv/Program', seasons=None):
    pi = ProgramInfo(location, initialize_empty=True)
    pi.program = {'id': 'some-id', 'title': 'Program'}
    pi.seasons = {
        key: EntrySet([
            Entry(
                '',
                '',
                datetime.datetime(2020, 1, 10),
                f'e{number}',
                episode={'number': number},
            ) for number in value
        ])
        for key, value in (seasons or {}).items()
    }
    pi.write()
    return pi
Пример #6
0
def test_choose_best_item():
    s = EntrySet()
    episode_real = {
        'id': 'some_id_from_ruv_api',
        'number': 1,
    }
    episode_generated = {
        'number': 2,
    }
    item1 = Entry('fn1', 'url1', datetime.datetime.min, 'etag1', None)
    item2 = Entry('fn2', 'url2', datetime.datetime.min, 'etag2', episode_real)
    assert s._choose_best_item(item1, item2).episode.to_dict() == episode_real
    assert s._choose_best_item(item2, item1).episode.to_dict() == episode_real

    item1.episode = Episode(episode_generated)
    assert s._choose_best_item(item1, item2).episode.to_dict() == episode_real
    assert s._choose_best_item(item2, item1).episode.to_dict() == episode_real

    item2.episode = None
    assert (s._choose_best_item(item1,
                                item2).episode.to_dict() == episode_generated)
    assert (s._choose_best_item(item2,
                                item1).episode.to_dict() == episode_generated)
Пример #7
0
 def get_entry(self, date, fn, episode=None):
     cache_key = f'{date.strftime(DATE_FORMAT)}-{fn}'
     if not self.cache.has(cache_key):
         try:
             r = requests.head(
                 URL_TEMPLATE.format(
                     date=date.strftime(DATE_FORMAT),
                     fn=fn,
                     openclose='opid' if self.prefer_open else 'lokad',
                 )
             )
         except Exception as e:
             logger.error('Error getting entry: %s', e)
             return None
         logger.info(
             'Checking %s - %s - %s (is_open: %s)'
             % (date.strftime(DATE_FORMAT), fn, r.ok, self.prefer_open,)
         )
         if r.ok:
             self.cache.set(
                 cache_key,
                 {
                     'success': True,
                     'url': r.url,
                     'etag': r.headers['ETag'],
                     'checked_at': datetime.datetime.now().strftime(
                         DATETIME_FORMAT,
                     ),
                 },
             )
         else:
             self.cache.set(
                 cache_key,
                 {
                     'success': False,
                     'status_code': r.status_code,
                     'checked_at': datetime.datetime.now().strftime(
                         DATETIME_FORMAT,
                     ),
                 },
             )
     info = self.cache.get(cache_key)
     checked_at = parse_datetime(info['checked_at'])
     if info['success']:
         return Entry(
             fn=fn,
             url=info['url'],
             date=date,
             etag=info['etag'],
             episode=episode,
         )
     elif (
         # Don't remove unless we last checked before the show was aired
         checked_at <= (date + datetime.timedelta(1))
         and
         # And we haven't checked this link for over 1 hours
         abs((checked_at - datetime.datetime.now()).total_seconds() / 3600)
         > 1
         and
         # And the show should have been aired
         date <= (datetime.datetime.now() + datetime.timedelta(1))
     ):
         self.cache.remove(cache_key)
         return self.get_entry(date, fn)
Пример #8
0
 def seasons(self):
     return {
         int(key): EntrySet(Entry.from_dict(entry) for entry in entries)
         for key, entries in self._data.items()
         if key not in NON_SEASON_FIELDS and key.isdigit()
     }
Пример #9
0
    def organize(self):
        # TODO: Use ProgramInfo class
        logger.info(f'Organizing {self.program["title"]}')
        info_fn = os.path.join(
            self.destination,
            self.program['title'],
        )
        os.makedirs(info_fn, exist_ok=True)
        try:
            program_info = ProgramInfo(info_fn)
        except FileNotFoundError:
            program_info = ProgramInfo(info_fn, initialize_empty=True)
        seasons = program_info.seasons
        program_info.program = self.program
        # seasons = {
        #     1: EntrySet({entry, entry, entry}),
        #     2: EntrySet({entry, entry, entry}),
        # }
        # Sort episodes into seasons
        for entry in sorted(self.episode_entries,
                            key=lambda entry: entry.date):
            for season in seasons.keys():
                if any(
                        abs((e.date - entry.date).days) < 10
                        for e in seasons[season]):
                    seasons[season].add(entry)
                    break
            else:
                int_season_numbers = [
                    int(season) for season in seasons
                    if isinstance(season, int) or season.isdigit()
                ]
                season = max(int_season_numbers or [0]) + 1
                seasons[season] = EntrySet([entry])
        # Calculate target paths for entries
        for season, entries in seasons.items():
            season_folder = Entry.get_season_folder(self.destination,
                                                    self.program, season)
            os.makedirs(season_folder, exist_ok=True)
            for i, entry in enumerate(entries.sorted()):
                if not entry.episode.number:
                    entry.episode.number = EntrySet.find_target_number(
                        entries, i)
                basename = entry.get_target_basename(
                    self.program,
                    season,
                )
                target_path = os.path.join(
                    season_folder,
                    basename,
                )
                entry.set_target_path(target_path)
        # Finally, make sure we don't have the same etag multiple times,
        # prefer the first one in chronological order
        found_etags = []
        for season, entries in seasons.items():
            for entry in [
                    entry for entry in entries if entry.etag in found_etags
            ]:
                entries.remove(entry)
            found_etags += [entry.etag for entry in entries]

        program_info.seasons = seasons
        if not settings.dryrun:
            program_info.write()

        missing_migrations = range(
            program_info.version,
            PROGRAM_INFO_VERSION,
        )
        for migration_entry in missing_migrations:
            logger.error(
                'Missing migration %d. Run `ruv-dl migrate %d`. You can '
                'supply `--dryrun` (e.g. `ruv-dl --dryrun migrate ...`) to '
                'see what will be done.',
                migration_entry + 1,
                migration_entry + 1,
            )
        if missing_migrations:
            return []
        return [
            entry for entry in itertools.chain(*seasons.values())
            if not entry.exists_on_disk()
        ]