def __init__(self, database_file: str, organizer: Organizer, config: ConfigurationFile): self.database_file = database_file self.organizer = organizer self.config = config self._downloader = TorrentDownloader() # associates the episodes to the download handle self._downloads = {} # type: dict[Episode: DownloadHandle] self._thread = None # periodically checks for completed episodes self._stopped = Event() # indicates the downloader is stopped
class Downloader: def __init__(self, database_file: str, organizer: Organizer, config: ConfigurationFile): self.database_file = database_file self.organizer = organizer self.config = config self._downloader = TorrentDownloader() # associates the episodes to the download handle self._downloads = {} # type: dict[Episode: DownloadHandle] self._thread = None # periodically checks for completed episodes self._stopped = Event() # indicates the downloader is stopped def download(self, episode: Episode): handle = self._downloader.start_download(episode.link, self.config.cache_dir) self._downloads[episode] = handle def start(self): """ Starts a new downloader session. The downloader must be started in order to be able to check for completed episodes and organize them using the organizer assign to it. Starting the downloader also restarts downloading all episodes marked as DOWNLOADING. """ self._stopped.clear() database = Database(self.database_file) # restart downloading all episodes marked as DOWNLOADING for episode in database.episodes(EpisodeState.DOWNLOADING): self.download(episode) # manage completed episodes periodically self._thread = Timer(self.config.download_check_period, self.manage_downloads) self._thread.start() def stop(self): """ Stops the downloader current session. All downloads are stopped and removed from the downloader. The service of checking for completed episodes is also stopped. """ self._stopped.set() self._downloader.stop() if self._thread: self._thread.cancel() def manage_downloads(self): for episode, handle in self._completed(): # the downloading directory must be obtained before removing the # episode from the torrent downloader downloading_dir = handle.basename self._remove_episode(episode) self._moveto_downloaded_dir(downloading_dir, episode) self._store_episode(episode) if not self._stopped.is_set(): self._thread = Timer(self.config.download_check_period, self.manage_downloads) self._thread.start() def downloads(self): for episode, handle in self._downloads.items(): yield Download(episode, handle.progress, handle.download_rate) def _completed(self): # this method can not be implemented as a generator since it removes # episodes from the downloads dictionary return [(episode, handle) for episode, handle in self._downloads.items() if handle.is_completed()] def _remove_episode(self, episode: Episode): """ Removes an episode from download system. After removing the episode the its download is stopped. :param episode: episode to remove. """ self._downloader.stop_download(self._downloads[episode]) del self._downloads[episode] def _moveto_downloaded_dir(self, downloading_dir: str, episode: Episode): """ Moves the files in the downloading directory to the downloaded directory of the given episode. """ shutil.move(os.path.join(self.config.cache_dir, downloading_dir), self.config.episode_downloaded_dir(episode)) def _store_episode(self, episode: Episode): """ Stores the given episode through the organizer assign to the downloader. Before storing the episode, it is marked as DOWNLOADED. :param episode: episode to store. """ database = Database(self.database_file) database.set_state(episode, EpisodeState.DOWNLOADED) self.organizer.store(episode)