def test_perspective_primary_draw_metadata(display): perspective = get_primary_perspective(display) feed = Feed(url="feed url", title="feed title", description="feed description", link="feed link", last_build_date="feed last_build_date", copyright="feed copyright", episodes=[]) episode = Episode(feed, title="episode title", description="episode description", link="episode link", pubdate="episode pubdate", copyright="episode copyright", enclosure="episode enclosure") feed.episodes.append(episode) display.feeds["feed url"] = feed perspective._draw_metadata(perspective._metadata_window, feed=feed) perspective._draw_metadata(perspective._metadata_window, episode=episode)
def test_perspective_primary_draw_metadata(display): perspective = get_primary_perspective(display) feed = Feed(url="feed url", title="feed title", description="feed description", link="feed link", last_build_date="feed last_build_date", copyright="feed copyright", episodes=[]) episode = Episode(feed, title="episode title", description="episode description", link="episode link", pubdate="episode pubdate", copyright="episode copyright", enclosure="episode enclosure") display.database.replace_feed(feed) display.database.replace_episode(feed, episode) perspective._draw_metadata(perspective._metadata_window) perspective._draw_metadata(perspective._metadata_window)
def test_display_draw_metadata(display): feed = Feed(url="feed url", title="feed title", description="feed description", link="feed link", last_build_date="feed last_build_date", copyright="feed copyright", episodes=[]) episode = Episode(feed, title="episode title", description="episode description", link="episode link", pubdate="episode pubdate", copyright="episode copyright", enclosure="episode enclosure") feed.episodes.append(episode) display._feeds["feed url"] = feed display._active_window = 0 display._draw_metadata() display._active_window = 1 display._draw_metadata()
def test_display_display_footer_empty(display): display.display() display._footer_window.attron.assert_called_with(curses.A_BOLD) display._footer_window.addstr.assert_called_with( 1, 0, "No feeds added -- Press h for help" ) display._stdscr.reset_mock() feed = Feed(url="feed url", title="feed title", description="feed description", link="feed link", last_build_date="feed last_build_date", copyright="feed copyright", episodes=[]) display._feeds["feed url"] = feed display.display() display._footer_window.attron.assert_called_with(curses.A_BOLD) display._footer_window.addstr.assert_called_with( 1, 0, "Processed 1 feeds with 0 total episodes (avg. 0 episodes, med. 0)" " -- Press h for help" )
def test_perspective_primary_queue_unplayed(display): perspective = get_primary_perspective(display) feed = Feed(url="feed url", title="feed title", description="feed description", link="feed link", last_build_date="feed last_build_date", copyright="feed copyright", episodes=[]) episode1 = Episode(feed, title="episode1 title", description="episode1 description", link="episode1 link", pubdate="episode1 pubdate", copyright="episode1 copyright", enclosure="episode1 enclosure", played=True) episode2 = Episode(feed, title="episode2 title", description="episode2 description", link="episode2 link", pubdate="episode2 pubdate", copyright="episode2 copyright", enclosure="episode2 enclosure") display.display() display.database.replace_feed(feed) display.database.replace_episodes(feed, [episode1, episode2]) perspective._feed_menu.update_items(None) perspective._episode_menu.update_items(feed) perspective._active_window = 0 perspective._queue_unplayed_feed_episodes = False perspective._create_player_from_selected() assert display.queue.length == 2 display.queue.clear() perspective._queue_unplayed_feed_episodes = True perspective._create_player_from_selected() assert display.queue.length == 1
def feeds(self) -> List[Feed]: """Retrieve the list of Feeds. Returns: List[Feed]: all Feed's in the database """ cursor = self._conn.cursor() cursor.execute(self.SQL_FEEDS_ALL) feeds = [] for row in cursor.fetchall(): feed = Feed(url=row[0] if row[0].startswith('http') else None, file=row[0] if not row[0].startswith('http') else None, title=row[1], description=row[2], link=row[3], last_build_date=row[4], copyright=row[5]) if feed.title: feeds.append(feed) else: self.delete_feed(feed) return feeds
def feed(self, key) -> Feed: """Retrieve a feed by key. :param key the key of the Feed to retrieve, which is the feed's primary key in the database :returns Feed: the matching Feed, if it exists, or None """ cursor = self._conn.cursor() cursor.execute(self.SQL_FEED_BY_KEY, (key, )) result = cursor.fetchone() if result is None: return None else: return Feed( url=result[0] if result[0].startswith("http") else None, file=result[0] if not result[0].startswith("http") else None, title=result[1], description=result[2], link=result[3], last_build_date=result[4], copyright=result[5], episodes=[], )
def test_display_execute_command(display): fname = "test_display_execute_command_output.mp3" myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title="episode title", description="episode description", link="episode link", pubdate="episode pubdate", copyright="episode copyright", enclosure=fname) castero.config.Config.data = {'execute_command': 'touch {file}'} if os.path.exists(fname): os.remove(fname) display.execute_command(myepisode) successful = False for i in range(10000): if os.path.exists(fname): successful = True break if successful: os.remove(fname) assert successful
def test_episode_missing_property_pubdate(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert myepisode.pubdate == "Publish date not available."
def test_episode_missing_property_description(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert myepisode.description == "Description not available."
def test_episode_str_description(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, description=description) assert str(myepisode) == description
def test_episode_only_description(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, description=description) assert isinstance(myepisode, Episode)
def test_episode_playable_remote(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") episode = myfeed.parse_episodes()[0] playable = episode.get_playable() assert not episode.downloaded assert playable == "http://example.com/myfeed_item1_title.mp3"
def test_display_nonempty(display): myfeed = Feed(file=my_dir + "/feeds/valid_enclosures.xml") display._feeds[myfeed._file] = myfeed display.create_menus() display.display()
def reload(self, display=None, feeds=None) -> None: """Reload feeds in the database. To preserve user metadata for episodes (such as played/marked status), we use Episode.replace_from() which "manually" copies such fields to the new downloaded episode. This is necessary because downloaded episodes are new Episode instances and we can't guarantee they have any of the same properties. Therefore, Episode.replace_from() _must_ be updated if any new user metadata fields are added. Also: to determine which episodes are the same in order to copy user metadata, we simply check whether the string representation of the two episodes are matching (usually the episodes' titles). This could cause issues if a feed has multiple episodes with the same title, although it does not require episode titles to be globally unique (that is, episodes with the same name in different feeds will never have issues). This method adheres to the max_episodes config parameter to limit the number of episodes saved per feed. Args: display: (optional) the display to write status updates to feeds: (optional) a list of feeds to reload. If not specified, all feeds in the database will be reloaded """ if feeds is None: feeds = self.feeds() total_feeds = len(feeds) completed_feeds = 0 reqs = [] url_pairs = {} file_feeds = [] # Create async requests for each URL feed. We also keep a map from # each feed's URL to the Feed object itself in order to access the # object when a request completes (since the response object is all # that we are given). # We also keep track of file-based feeds, which are handled afterwards. for feed in feeds: if feed.key.startswith("http"): url_pairs[feed.key] = feed reqs.append(Net.GGet(feed.key)) else: file_feeds.append(feed) # handle each response as downloads complete asynchronously for response in grequests.imap(reqs, size=3): if display is not None: display.change_status("Reloading feeds (%d/%d)" % (completed_feeds, total_feeds)) old_feed = url_pairs[response.request.url] new_feed = Feed(url=response.request.url, response=response) self._reload_feed_data(old_feed, new_feed) completed_feeds += 1 # handle each file-based feed for old_feed in file_feeds: new_feed = Feed(file=old_feed.key) self._reload_feed_data(old_feed, new_feed) completed_feeds += 1 if display is not None: display.change_status("Reloading feeds (%d/%d)" % (completed_feeds, total_feeds)) if display is not None: display.change_status("Successfully reloaded %d feeds" % total_feeds) display.menus_valid = False
def test_episode_missing_property_enclosure(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert myepisode.enclosure == "Enclosure not available."
def test_episode_metadata_with_progress_no_error(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") episode = myfeed.parse_episodes()[0] episode._progress = 1000 assert isinstance(episode.metadata, str)
def test_episode_only_title(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert isinstance(myepisode, Episode)
def test_episode_progress(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") episode = myfeed.parse_episodes()[0] episode._progress = 1000 assert episode.progress == 1000
def test_episode_str_title(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert str(myepisode) == title
def reload(self, display=None) -> None: """Reload all feeds in the database. To preserve user metadata for episodes (such as played/marked status), we use Episode.replace_from() which "manually" copies such fields to the new downloaded episode. This is necessary because downloaded episodes are new Episode instances and we can't guarantee they have any of the same properties. Therefore, Episode.replace_from() _must_ be updated if any new user metadata fields are added. Also: to determine which episodes are the same in order to copy user metadata, we simply check whether the string representation of the two episodes are matching (usually the episodes' titles). This could cause issues if a feed has multiple episodes with the same title, although it does not require episode titles to be globally unique (that is, episodes with the same name in different feeds will never have issues). This method adheres to the max_episodes config parameter to limit the number of episodes saved per feed. Args: display: (optional) the display to write status updates to """ feeds = self.feeds() total_feeds = len(feeds) current_feed = 1 for feed in self.feeds(): if display is not None: display.change_status("Reloading feeds (%d/%d)" % (current_feed, total_feeds)) # assume urls have http in them if "http" in feed.key: new_feed = Feed(url=feed.key) else: new_feed = Feed(file=feed.key) # keep user metadata for episodes intact new_episodes = new_feed.parse_episodes() old_episodes = self.episodes(feed) for new_ep in new_episodes: matching_olds = [ old_ep for old_ep in old_episodes if str(old_ep) == str(new_ep) ] if len(matching_olds) == 1: new_ep.replace_from(matching_olds[0]) # limit number of episodes, if necessary max_episodes = int(Config["max_episodes"]) if max_episodes != -1: new_episodes = new_episodes[:max_episodes] self.replace_feed(new_feed) self.replace_episodes(new_feed, new_episodes) if display is not None: display.change_status("Feeds successfully reloaded") display.menus_valid = False
def test_episode_missing_property_title(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, description=description) assert myepisode.title == "Title not available."
import os from unittest import mock from castero.downloadqueue import DownloadQueue from castero.episode import Episode from castero.feed import Feed my_dir = os.path.dirname(os.path.realpath(__file__)) feed = Feed(file=my_dir + "/feeds/valid_enclosures.xml") episode1 = Episode(feed=feed, title="episode1 title") episode2 = Episode(feed=feed, title="episode2 title") def test_downloadqueue_init(): mydownloadqueue = DownloadQueue() assert isinstance(mydownloadqueue, DownloadQueue) def test_downloadqueue_add(): mydownloadqueue = DownloadQueue() assert mydownloadqueue.length == 0 mydownloadqueue.add(episode1) assert mydownloadqueue.length == 1 mydownloadqueue.add(episode1) assert mydownloadqueue.length == 1 mydownloadqueue.add(episode2) assert mydownloadqueue.length == 2 def test_downloadqueue_start():
def test_episode_missing_property_link(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert myepisode.link == "Link not available."
def test_display_nonempty(display): myfeed = Feed(file=my_dir + "/feeds/valid_enclosures.xml") display.database.feeds = mock.MagicMock(return_value=[myfeed]) display.menus_valid = False display.display()
def test_episode_missing_property_copyright(): myfeed = Feed(file=my_dir + "/feeds/valid_basic.xml") myepisode = Episode(myfeed, title=title) assert myepisode.copyright == "No copyright specified."
import os from unittest import mock from castero.config import Config from castero.feed import Feed from castero.queue import Queue from castero.player import Player my_dir = os.path.dirname(os.path.realpath(__file__)) feed = Feed(file=my_dir + "/feeds/valid_basic.xml") def test_queue_init(display): myqueue = Queue(display) assert isinstance(myqueue, Queue) def test_queue_first(display): myqueue = Queue(display) player1 = mock.MagicMock(spec=Player) myqueue.add(player1) assert myqueue.first == player1 def test_queue_get(display): myqueue = Queue(display) player1 = mock.MagicMock(spec=Player) player2 = mock.MagicMock(spec=Player)