diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a654dbe0af75bb8f9d9e61f33af9830723746e2..ad17d63f9cbcf5cf00490bbb529d446ee2cbe648 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ tester: script: - apt-get update -qy - apt-get install -y python3-dev python3-pip python3-pyflakes python3-pytest + - pip3 install -r requirements.txt - PYTHONPATH=python pyflakes3 python/podcast/*py - PYTHONPATH=python pyflakes3 qml/components/*py - PYTHONPATH=./python python3 -m pytest test diff --git a/README.md b/README.md index dc7b31e12fee5de0857af4685d458f30a08507d8..7be98320b7f2f2aefb17a47dc84d8d49a49c9032 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # PodQast A queue based podcast player for SailfishOS +# Testing +Tests can be run with pytest from the test directory. + ## Author Thomas Renard [podqast@g3la.de](mailto:podqast@g3la.de) diff --git a/harbour-podqast.pro b/harbour-podqast.pro index fad40acb521d4d791c821c831f4dda26b14d6669..4d5a26b52008f40c2d22ba7dbf485904c3596e9a 100644 --- a/harbour-podqast.pro +++ b/harbour-podqast.pro @@ -17,7 +17,9 @@ CONFIG += sailfishapp_qml DISTFILES += qml/podqast.qml \ qml/components/FyydDe.py \ qml/components/FyydDePython.qml \ + qml/components/MigrationHandler.qml \ qml/cover/CoverPage.qml \ + qml/pages/DataMigration.qml \ qml/pages/DiscoverFyyd.qml \ rpm/podqast.changes.in \ rpm/podqast.changes.run.in \ @@ -44,7 +46,7 @@ DISTFILES += qml/podqast.qml \ python/mygpoclient/simple_test.py \ python/mygpoclient/testing.py \ python/mygpoclient/util.py \ - python/feedparser.py \ + python/feedparser/ \ qml/params.yml \ qml/cover/CoverPage.qml \ qml/pages/GpodderNetPython.qml \ @@ -90,7 +92,8 @@ DISTFILES += qml/podqast.qml \ qml/pages/FilePicker.qml \ qml/pages/DiscoverExport.qml \ qml/pages/External.qml \ - qml/components/ExternalHandlerPython.qml + qml/components/ExternalHandlerPython.qml \ + qml/components/BackupButtons.qml SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 diff --git a/python/podcast/__init__.py b/python/podcast/__init__.py index 5d07396771dbdab99c8578ebd286f6dba14bf387..0c5c949f032d6ca539bf9060f78e20d69641cfac 100644 --- a/python/podcast/__init__.py +++ b/python/podcast/__init__.py @@ -1,3 +1,5 @@ """ podcast classes """ +import logging +logging.basicConfig(level=logging.INFO) diff --git a/python/podcast/data_migration.py b/python/podcast/data_migration.py new file mode 100644 index 0000000000000000000000000000000000000000..b278fa04cbbc93b08d517faf4ba8a7c6e51d6713 --- /dev/null +++ b/python/podcast/data_migration.py @@ -0,0 +1,104 @@ +import logging +import os +import pyotherside + +from podcast.factory import Factory +from podcast.podcast import PodcastFactory, Podcast +from podcast.podcastlist import PodcastListFactory +from podcast.podpost import Podpost + +logger = logging.getLogger(__name__) + + +def needs_migration(): + target_data_version = 1 + def app_is_pristine(): + return len(list(PodcastListFactory().get_podcast_list().get_podcasts())) == 0 + if get_versionnumber() >= target_data_version: + return False + else: + if app_is_pristine(): + set_versionnumber(target_data_version) + return False + else: + return True + +def run_migrations(): + current_version = get_versionnumber() + if(current_version < 1): + logger.info("Migrating from version %d to version 1",current_version) + podcasts = list(PodcastListFactory().get_podcast_list().get_podcasts()) + pyotherside.send("migrationStart", len(podcasts)) + i : int = 0 + for podcast_url in podcasts: + podcast = PodcastFactory().get_podcast(podcast_url) + if podcast != None: + migrate_podcast_v0_v1(podcast) + for entry_id in podcast.entry_ids_old_to_new: + migrate_podpost_v0_v1(Factory().get_podpost(entry_id)) + i += 1 + pyotherside.send("migrationProgress", i) + set_versionnumber(1) + pyotherside.send("migrationDone") + + + +def get_versionnumber() -> int: + versionfilepath = get_versionfile_path() + if os.path.isfile(versionfilepath): + with open(versionfilepath, 'r') as versionfile: + return int(versionfile.read()) + else: + return 0 + + +def set_versionnumber(number: int): + versionfilepath = get_versionfile_path() + with open(versionfilepath, 'w') as versionfile: + versionfile.write(str(number)) + + +def get_versionfile_path(): + return os.path.join(Factory().data_home, "dataversion") + +def migrate_podpost_v0_v1(self: Podpost): + self.isaudio = False + self.save() + +def migrate_podcast_v0_v1(self: Podcast): + logger.info("migrating podcast '%s' to new format version 1", self.title) + try: + from podcast.factory import Factory + if self.logo_path: + image = self.logo_path + else: + image = self.logo_url + entry_ids_new_to_old = [] + old_entry_count = 0 + if self.feed is not None: + old_entry_count = len(self.feed.entries) + logger.info("Found %d entries in old format", old_entry_count) + for entry in self.feed.entries: + post = Podpost(entry, self.url, image) + p2 = Factory().get_podpost(post.id) + if p2: + post = p2 + post.save() + entry_ids_new_to_old.append(post.id) + self._set_alt_feeds(self.feed) + else: + logger.warning("feed for %s was None. Will still continue migration but leave entries empty.", + self.title) + entry_ids_new_to_old.reverse() + self.entry_ids_old_to_new = entry_ids_new_to_old + logger.info("Found %d entries in new format", len(self.entry_ids_old_to_new)) + self.feed = None + if old_entry_count == len(entry_ids_new_to_old): + logger.info("migration done") + self.save() + else: + logger.error("Could not complete migration. old count: %d, new: %d", old_entry_count, + len(entry_ids_new_to_old)) + except: + logger.exception("error while migrating podcast %s to new format", self.title) + diff --git a/python/podcast/factory.py b/python/podcast/factory.py index 01a4109f47222f3f517d80620cf5e74ec1d6c4a9..15d7dca4e3f04cff5b5e2083035fb8cafa562878 100644 --- a/python/podcast/factory.py +++ b/python/podcast/factory.py @@ -24,6 +24,7 @@ class Factory(metaclass=Singleton): """ Factory for """ + data_home: str def __init__(self, progname="harbour-podqast"): """ @@ -36,19 +37,7 @@ class Factory(metaclass=Singleton): self.feedcache = Cache() self.object = None - home = os.path.expanduser("~") - xdg_data_home = os.environ.get( - "XDG_DATA_HOME", os.path.join(home, ".local", "share") - ) - xdg_config_home = os.environ.get( - "XDG_CONFIG_HOME", os.path.join(home, ".config") - ) - xdg_cache_home = os.path.join( - "XDG_CACHE_HOME", os.path.join(home, ".cache") - ) - self.data_home = os.path.join(xdg_data_home, progname) - self.config_home = os.path.join(xdg_config_home, progname) - self.cache_home = os.path.join(xdg_cache_home, progname) + self._set_paths(progname) if "PODQAST_HOME" in os.environ: home = os.environ["PODQAST_HOME"] @@ -66,7 +55,23 @@ class Factory(metaclass=Singleton): self.store = Store(storepath) self.podpostcache = Cache(limit=500) - def get_object(self, object): + def _set_paths(self, progname): + home = os.path.expanduser("~") + xdg_data_home = os.environ.get( + "XDG_DATA_HOME", os.path.join(home, ".local", "share") + ) + xdg_config_home = os.environ.get( + "XDG_CONFIG_HOME", os.path.join(home, ".config") + ) + xdg_cache_home = os.environ.get( + "XDG_CACHE_HOME", os.path.join(home, ".cache") + ) + self.data_home = os.path.join(xdg_data_home, progname) + self.config_home = os.path.join(xdg_config_home, progname) + self.cache_home = os.path.join(xdg_cache_home, progname) + + + def init_from_qml(self, object): """ Get QML object """ @@ -78,12 +83,11 @@ class Factory(metaclass=Singleton): pass self.object = object - home = os.path.expanduser("~") - self.media_home = os.path.join(self.get_val("musicHomeVal"), "podqast") self.favorites_home = os.path.join(self.media_home, "favorites") self.external_home = os.path.join(self.media_home, "external") + xdg_media_home = os.path.join( "XDG_DATA_DIRS", os.path.join(home, "podqast") ) @@ -158,6 +162,10 @@ class Factory(metaclass=Singleton): return podpost + def delete_podpost(self, index): + self.podpostcache.delete(index) + self.store.delete(index) + def get_store(self): """ get the store diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py index 9d771339c871afff7c3c06451dee60b3951d4b85..645acf7dac203481f747c9ac06953dfd03ba53f9 100644 --- a/python/podcast/podcast.py +++ b/python/podcast/podcast.py @@ -3,6 +3,9 @@ Podcast """ import sys +from typing import List, Dict, Optional, Generator + +from podcast.factory import Factory sys.path.append("/usr/share/podqast/python") @@ -16,70 +19,156 @@ from email.utils import mktime_tz, parsedate_tz import time import hashlib import os +import logging import feedparser import pyotherside +logger = logging.getLogger(__name__) + + +class FeedFetchingError(Exception): + pass + class Podcast: """ A saved podcast """ - def __init__(self, url, feed=None): + alt_feeds: List[Dict[str, str]] + logo_path: Optional[str] + logo_url: str + description: str + title: Optional[str] + #: the ids of all entries of this feed from oldest [0] to newest [-1] + entry_ids_old_to_new: List[str] + url: str + version: int + subscribed: bool + + def __init__(self, url, preview=False): """ The podcast itself only needs it's url url: Url of the podcast """ + self.subscribed = False + self.entry_ids_old_to_new = [] + try: + self.__fetch_and_parse_initial(url, store_episodes=not preview) + except ValueError: + logger.exception("error while parsing") + return + except FeedFetchingError: + logger.exception("error while parsing") + return + self.move = -1 + def __fetch_and_parse_initial(self, url, store_episodes=True): + logger.info("initializing podcast %s", url) self.url = url - if url.find("tumblr.com") >= 0: - agent = util.user_agent2 - else: - agent = util.user_agent - - self.feed = feedparser.parse(url, agent=agent) - - if self.feed.bozo != 0: - exc = self.feed.bozo_exception - if type(exc) != feedparser.CharacterEncodingOverride: - pyotherside.send( - "Podcast init: error in parsing feed" + str(type(exc)) - ) - self.title = None - return - pyotherside.send( - "podcast init size of entries: %d" % len(self.feed.entries) - ) - + feed = self._fetch(url) try: - self.title = self.feed.feed.title + self.title = feed.feed.title except: - pyotherside.send("podcast: no title") - self.title = None - return - - self.link = self.feed.feed.link + logger.exception("podcast: no title") + raise ValueError("Podcast has no name") + try: + self.link = feed.feed.link + except: + self.link = url try: - self.description = self.feed.feed.description + self.description = feed.feed.description except: self.description = "" - try: - self.logo_url = self.feed.feed.image.href - pyotherside.send("podcast logo_url: " + self.logo_url) + self.logo_url = feed.feed.image.href + logger.debug("podcast logo_url: %s", self.logo_url) except: self.logo_url = "../../images/podcast.png" - pyotherside.send( - "podcast logo_url error: " + str(sys.exc_info()[0]) - ) - + logger.exception("podcast logo_url error") self.logo_path = None + self.__set_data_from_feed(feed) + if store_episodes: + self.__process_episodes(feed) + else: + self.__process_episodes(feed, limit=1, supress_limit_notification=True) - if "updated_parsed" in self.feed: - self.published = time.mktime(self.feed.updated_parsed) - elif "Date" in self.feed.headers: - self.published = mktime_tz(parsedate_tz(self.feed.headers["Date"])) - self.move = -1 + def __process_episodes(self, feed, limit=0, supress_limit_notification=False): + """ + returns the episodes in order from newest to oldest + properly sets the entry_ids_old_to_new + """ + + logger.debug("fetching episodes for %s", self.title) + + if self.logo_path: + image = self.logo_path + else: + image = self.logo_url + + old_first_id = self.entry_ids_old_to_new[-1] if len(self.entry_ids_old_to_new) > 0 else "" + + def entry_already_known(entry): + thehash = hashlib.sha256(entry.id.encode()).hexdigest() + logger.debug("current entry id %s", thehash) + logger.debug("old first id %s", old_first_id) + return thehash == old_first_id + + try: + if self.autolimit: + limit = self.autolimit + except: + self.autolimit = None + all_episode_count = len(self.entry_ids_old_to_new) + new_posts = [] + for entry in feed.entries: + if entry_already_known(entry): + pyotherside.send("refreshPost", None) + break + all_episode_count += 1 + post = Podpost(entry, self.url, image) + post.save() + new_posts.append(post) + # todo: limit stuff probably broken + if limit != 0 and all_episode_count >= limit: + if not supress_limit_notification: + pyotherside.send("apperror", "Auto post limit reached for %s!" % self.title) + break + new_post_ids = [post.id for post in new_posts] + new_post_ids.reverse() + self.entry_ids_old_to_new.extend(new_post_ids) + self.save() + return new_posts + + def __set_data_from_feed(self, feed): + logger.debug("setting instance values from feed") + self.__set_times_from_feeds(feed) + self._set_alt_feeds(feed) + + def __set_times_from_feeds(self, feed): + if "updated_parsed" in feed: + self.published = time.mktime(feed.updated_parsed) + elif "Date" in feed.headers: + self.published = mktime_tz(parsedate_tz(feed.headers["Date"])) + + def _fetch(self, url): + if url.find("tumblr.com") >= 0: + agent = util.user_agent2 + else: + agent = util.user_agent + feed = feedparser.parse(url, agent=agent) + if feed.bozo != 0: + exc = feed.bozo_exception + if type(exc) != feedparser.CharacterEncodingOverride: + logger.warning( + "Podcast init: error in parsing feed %s", str(type(exc)) + ) + self.title = None + raise FeedFetchingError("error in feed") + logger.info( + "podcast init size of entries: %d", len(feed.entries) + ) + return feed def feedinfo(self): """ @@ -108,31 +197,23 @@ class Podcast: "logo_url": image, } - def get_alt_feeds(self): + def _set_alt_feeds(self, feed): """ return all alternative feeds """ - - altfeeds = [] - - for feed in self.feed.feed.links: - try: - if ( - feed.type == "application/rss+xml" - or feed.type == "application/atom+xml" - ): - if "title" in feed: - title = feed.title + logger.info("setting alt_feeds") + self.alt_feeds = [] + if "links" in feed: + for link in feed.links: + if link.type == "application/rss+xml" or link.type == "application/atom+xml": + if "title" in link: + title = link.title else: title = "" + self.alt_feeds.append({"url": link.href, "title": title}) + logger.debug("setting alt feed: %s %s", title, link.href) - altfeeds.append({"url": feed.href, "title": title}) - except: - pass - - return {"altfeeds": altfeeds} - - def get_entry(self, id): + def get_entry(self, id) -> Podpost: """ Get an entry from podcast by id """ @@ -141,66 +222,24 @@ class Podcast: entry = Factory().get_podpost(id) return entry - def get_entries(self): + def get_entries(self) -> Generator[dict, None, None]: """ Return a list of entries """ - from podcast.factory import Factory + logger.debug("Podcast get_entries started.") + for id in self.entry_ids_old_to_new: + yield Factory().get_podpost(id).get_data() - pyotherside.send("Podcast get_entries started.") - - entries = [] - - if self.logo_path: - image = self.logo_path - else: - image = self.logo_url - - for entry in self.feed.entries: - post = Podpost(entry, self.url, image) - pyotherside.send("pid: " + post.id) - p2 = Factory().get_podpost(post.id) - if p2: - post = p2 - entries.append(post.get_data()) - - pyotherside.send("Podcast get_entries ended.") - return entries - - def get_first_entry(self): + def get_latest_entry(self) -> Optional[Podpost]: """ - Get the first entry from podcast + Get the newest entry from podcast + returns None if there are no entries """ - - from podcast.factory import Factory - - if len(self.feed.entries) < 1: + if len(self.entry_ids_old_to_new) > 0: + return self.get_entry(self.entry_ids_old_to_new[-1]) + else: return None - entry = Podpost(self.feed.entries[0], self.url, self.logo_url) - p2 = Factory().get_podpost(entry.id) - if p2: - entry = p2 - Factory().podpostcache.store(entry.id, entry) - return entry - - def get_last_entry(self): - """ - Get the last entry from podcast - """ - - from podcast.factory import Factory - - entry = Podpost( - self.feed.entries[len(self.feed.entries) - 1], - self.url, - self.logo_url, - ) - p2 = Factory().get_podpost(entry.id) - if p2: - entry = p2 - Factory().podpostcache.store(entry.id, entry) - return entry def save(self): """ @@ -231,12 +270,12 @@ class Podcast: rdot = self.logo_url.rfind(".") extexcl = self.logo_url.rfind("?") if extexcl > 0: - ext = self.logo_url[rdot + 1 : extexcl] + ext = self.logo_url[rdot + 1: extexcl] else: - ext = self.logo_url[rdot + 1 :] + ext = self.logo_url[rdot + 1:] if ext == "jpg" or ext == "jpeg" or ext == "png" or ext == "gif": name = ( - hashlib.sha256(self.logo_url.encode()).hexdigest() + "." + ext + hashlib.sha256(self.logo_url.encode()).hexdigest() + "." + ext ) logo_path = os.path.join(iconpath, name) @@ -244,8 +283,8 @@ class Podcast: util.dl_from_url(self.logo_url, logo_path) except: logo_path = "" - pyotherside.send( - "could not load icon: " + str(sys.exc_info()[0]) + logger.info( + "could not load icon: %s", str(sys.exc_info()[0]) ) self.logo_path = logo_path else: @@ -255,82 +294,42 @@ class Podcast: """ Refresh podcast and return list of new entries """ - pyotherside.send("refreshPost", self.title) + old_published = self.published + feed = self._fetch(self.url) + self.__set_data_from_feed(feed) - feed = feedparser.parse(self.url) - if feed.bozo != 0: - exc = self.feed.bozo_exception - if type(exc) != feedparser.CharacterEncodingOverride: - pyotherside.send("refreshPost", None) - return [] - - if "updated_parsed" in feed: - published = time.mktime(feed.updated_parsed) - elif "Date" in feed.headers: - published = mktime_tz(parsedate_tz(feed.headers["Date"])) - - first = self.get_first_entry() - pyotherside.send("refresh published:", published, self.published) - if published <= self.published: + logger.info("refresh published: %s %s", self.published, old_published) + if self.published <= old_published: pyotherside.send("refreshPost", None) return [] - self.feed = feed - self.published = published self.save() - entries = [] - if self.logo_path: - image = self.logo_path - else: - try: - image = self.logo_url - except: - image = self.logo_url = "../../images/podcast.png" - - pyotherside.send("refresh first.id", first.id) - move = self.move if move < 0: move = moveto - pyotherside.send("Moveto is %d" % move) + logger.debug("Moveto is %d", move) - for entry in self.feed.entries: - thehash = hashlib.sha256(entry.id.encode()).hexdigest() - pyotherside.send("refresh entry.id", thehash) - pyotherside.send("first.id", first.id) - if thehash == first.id: - pyotherside.send("refreshPost", None) - break - - post = Podpost(entry, self.url, image) - post.save() - entries.append( - { - "id": post.id, - "post": post.get_data(), - "pctitle": self.title, - "pstitle": post.title, - "move": move, - } - ) - try: - if self.autolimit: - limit = self.autolimit - except: - self.autolimit = None - - if limit != 0 and len(entries) >= limit: - pyotherside.send( - "apperror", "Auto post limit reached for %s!" % self.title - ) - break - - pyotherside.send("Fount %d new entries." % len(entries)) + yield from self.__process_refreshed_feedentries(feed, limit, move) pyotherside.send("refreshPost", None) - return entries + + def __process_refreshed_feedentries(self, feed, limit, move): + """ + saves new entries for this feed + yields all new entries + """ + new_posts = self.__process_episodes(feed, limit) + for post in new_posts: + yield { + "id": post.id, + "post": post.get_data(), + "pctitle": self.title, + "pstitle": post.title, + "move": move + } + logger.info("Fount %d new entries.", len(new_posts)) def set_params(self, params): """ @@ -375,6 +374,8 @@ class PodcastFactory(metaclass=Singleton): """ Helping Factory """ + store: Store + podcastcache: Cache def __init__(self, progname="harbour-podqast"): """ @@ -409,25 +410,34 @@ class PodcastFactory(metaclass=Singleton): self.store = Store(storepath) - def get_podcast(self, url): + def get_podcast(self, url, preview: bool = False) -> Optional[Podcast]: """ Get a new podcast instance with name url """ podcast = self.podcastcache.get(url) if not podcast: - pyotherside.send("PodcastFactory: not in cache") + logger.debug("PodcastFactory: not in cache %s", url) podcast = self.store.get(url) if podcast: - pyotherside.send("PodcastFactory: From disk") + logger.debug("PodcastFactory: From disk %s", url) else: - podcast = Podcast(url) + podcast = Podcast(url, preview=preview) if podcast.title: - pyotherside.send("PodcastFactory: New instance") + logger.debug("PodcastFactory: New instance %s", url) else: return None - pyotherside.send("PodcastFactory: store to cache") + logger.debug("PodcastFactory: store to cache %s", url) self.podcastcache.store(url, podcast) else: - pyotherside.send("PodcastFactory: from cache") + logger.debug("PodcastFactory: from cache %s", url) return podcast + + def remove_podcast(self, url): + logger.info("deleting podcast and entries %s", url) + to_delete = self.get_podcast(url) + if to_delete != None: + for post in to_delete.entry_ids_old_to_new: + Factory().delete_podpost(post) + self.podcastcache.delete(url) + self.store.delete(url) diff --git a/python/podcast/podcastlist.py b/python/podcast/podcastlist.py index cdd3be47cef1dba7518268967692438f67401386..5fe15d623e6d0fac922cfc4454a9c730a95b195a 100644 --- a/python/podcast/podcastlist.py +++ b/python/podcast/podcastlist.py @@ -3,6 +3,7 @@ List of subscribed podcasts """ import sys +from typing import List sys.path.append("../") @@ -12,6 +13,9 @@ from podcast.podcast import PodcastFactory from podcast import util from podcast import gpodder_import import pyotherside +import logging + +logger = logging.getLogger(__name__) listname = "the_podcast_list" @@ -20,6 +24,7 @@ class PodcastList: """ This element holds all podcasts """ + podcasts: List[str] def __init__(self): """ @@ -58,15 +63,14 @@ class PodcastList: pc.delete() self.save() - def get_podcasts(self): + def get_podcasts(self) -> str: """ return the list of subscribed podcasts """ - for podcast in self.podcasts: pc = PodcastFactory().get_podcast(podcast) if pc: - if pc.subscribed: + if hasattr(pc, "subscribed") and pc.subscribed: yield podcast def refresh(self, moveto, limit=0): @@ -84,8 +88,8 @@ class PodcastList: for post in pc.refresh(moveto, limit): yield post except: - pyotherside.send( - "error", "podcast refresh failed for %s." % pc.title + logger.exception( + "podcast refresh failed for %s." % pc.title ) count += 1 diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 3b24e5771b4fe245316de1e4012105383c634498..b13c9af556b43ee3c2746a7a74a54ffec5dcba51 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -11,6 +11,7 @@ import os from urllib.parse import urlparse import html2text from email.utils import mktime_tz, parsedate_tz +from typing import Optional, List from podcast.factory import Factory from podcast.util import s_to_hr, tx_to_s, dl_from_url, dl_from_url_progress @@ -20,6 +21,9 @@ from podcast.queue import QueueFactory import pyotherside import time import re +import logging + +logger = logging.getLogger(__name__) PLAY = 1 PAUSE = 2 @@ -30,6 +34,29 @@ class Podpost: """ One podcast post. It has all information needed for playing and information. """ + file_path: Optional[str] + plainpart: Optional[str] + htmlpart: Optional[str] + title: str + percentage: float + position: int + isaudio: bool + id: str + state: int + favorite: bool + author: str + logo_url: str + logo_path: Optional[str] + href: str + duration: Optional[int] + length: int + published: Optional[float] + insert_date: object + link: Optional[str] + type: str + podurl: str + chapters: List + version: int def __init__(self, entry, podurl, logo_url, data=None, isaudio=False): """ @@ -39,12 +66,16 @@ class Podpost: entry: the entry took from feedparser podcast: podcast url """ + self.version = 1 self.isaudio = isaudio self.position = 0 self.state = STOP self.percentage = 0 self.favorite = False + self.length = 0 + self.type = "" + self.href = "" if isaudio: self.title = data["title"] @@ -78,7 +109,7 @@ class Podpost: self.published = None try: if not self.published: - pyotherside.send("no published_parsed") + logger.debug("no published_parsed") self.published = mktime_tz(parsedate_tz(entry.published)) except: self.published = 0 @@ -125,7 +156,7 @@ class Podpost: if entry.id != "": self.id = hashlib.sha256(entry.id.encode()).hexdigest() else: - pyotherside.send("podpost: empty id") + logger.debug("podpost: empty id") self.id = hashlib.sha256(entry.summary.encode()).hexdigest() else: self.id = hashlib.sha256(entry.summary.encode()).hexdigest() @@ -201,7 +232,7 @@ class Podpost: queue = QueueFactory().get_queue() if position: self.position = position - self.save() + self.save() if self.duration: if self.position > 0: add = "" @@ -244,36 +275,15 @@ class Podpost: else: asection = "" adate = 0 - pyotherside.send("adate, asection", adate, asection) - pyotherside.send("date, section", date, section) + logger.debug("adate, asection %s, %s", adate, asection) + logger.debug("date, section %s, %s", date, section) if self.duration: duration = s_to_hr(self.duration) else: duration = "" - if self.link: - link = self.link - else: - link = "" - - try: - length = self.length - except: - length = 0 - - try: - favorite = self.favorite - except: - favorite = False - - try: - isaudio = self.isaudio - except: - isaudio = self.isaudio = False - loaded = False - - if isaudio: + if self.isaudio: try: loaded = os.path.exists(self.href) except: @@ -284,13 +294,13 @@ class Podpost: else: haschapters = False - pyotherside.send("loaded", loaded) + logger.debug("loaded %s", loaded) return { "id": self.id, "title": self.title, "url": self.podurl, "logo_url": image, - "link": link, + "link": self.link, "description": self.plainpart, "detail": self.htmlpart, "timestring": timestring, @@ -299,12 +309,12 @@ class Podpost: "section": section, "adate": adate, "asection": asection, - "length": length, + "length": self.length, "duration": duration, "dlperc": self.percentage, - "favorite": favorite, + "favorite": self.favorite, "inqueue": inqueue, - "isaudio": isaudio, + "isaudio": self.isaudio, "loaded": loaded, "haschapters": haschapters, } @@ -314,12 +324,12 @@ class Podpost: Save this element. If self.insert_date = None: set date """ - pyotherside.send("storing podpost %s" % self.id) + logger.info("storing podpost %s", self.id) if not self.logo_path: self.download_icon() store = Factory().get_store() store.store(self.id, self) - pyotherside.send("...done") + logger.debug("...done") def download_audio(self): """ @@ -333,7 +343,7 @@ class Podpost: afilepath = Factory().afilepath filename = os.path.basename(urlparse(self.href).path) rdot = filename.rfind(".") - ext = filename[rdot + 1 :] + ext = filename[rdot + 1:] name = hashlib.sha256(self.href.encode()).hexdigest() + "." + ext namepart = name + ".part" @@ -379,10 +389,10 @@ class Podpost: iconpath = Factory().iconpath rdot = self.logo_url.rfind(".") - ext = self.logo_url[rdot + 1 :] + ext = self.logo_url[rdot + 1:] if ext == "jpg" or ext == "jpeg" or ext == "png" or ext == "gif": name = ( - hashlib.sha256(self.logo_url.encode()).hexdigest() + "." + ext + hashlib.sha256(self.logo_url.encode()).hexdigest() + "." + ext ) logo_path = os.path.join(iconpath, name) @@ -512,11 +522,11 @@ class Podpost: def get_topath(): rdot = self.file_path.rfind(".") - ext = self.file_path[rdot + 1 :] + ext = self.file_path[rdot + 1:] tn = ( str(self.title.encode("ascii", "ignore")) - .strip() - .replace(" ", "_")[1:] + .strip() + .replace(" ", "_")[1:] ) tnb = re.sub(r"(?u)[^-\w.]", "", tn) + "." + ext tnb = "".join(x for x in tnb if x.isalnum() or x in "._-()") @@ -526,7 +536,7 @@ class Podpost: return topath if self.favorite and do_download: - pyotherside.send("need to download file") + logger.info("need to download file") for perc in self.download_audio(): pyotherside.send("downloading", self.id, perc) @@ -549,7 +559,7 @@ class Podpost: try: self.chapters[0].selected except AttributeError: - pyotherside.send("setting chapter selection flags") + logger.info("setting chapter selection flags") for chapter in self.chapters: chapter["selected"] = True self.save() @@ -577,3 +587,5 @@ class Podpost: favorite = False return favorite + + diff --git a/qml/components/BackupButtons.qml b/qml/components/BackupButtons.qml new file mode 100644 index 0000000000000000000000000000000000000000..e8a1befeb409a7293832b3bd956969e7a86e5ed4 --- /dev/null +++ b/qml/components/BackupButtons.qml @@ -0,0 +1,68 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Column { + id: backupb + property bool backupRunning: false + property bool opmlWriteRunning: false + + spacing: Theme.paddingMedium + + Connections { + target: feedparserhandler + onBackupDone: { + console.log("Notify backup done") + appNotification.previewSummary = qsTr("Backup done") + appNotification.previewBody = qsTr("Backup done to ") + tarpath + appNotification.body = qsTr("Backup done to ") + tarpath + appNotification.publish() + backupRunning = false + } + + onOpmlSaveDone: { + console.log("Notify opml save done") + appNotification.previewSummary = qsTr("OPML file saved") + appNotification.previewBody = qsTr("OPML file saved to ") + opmlpath + appNotification.body = qsTr("OPML file saved to ") + opmlpath + appNotification.publish() + opmlWriteRunning = false + } + } + + Button { + id: importbutton + text: qsTr("Backup") + enabled: !backupRunning + width: parent.width + onClicked: { + Remorse.popupAction(migrationpage, qsTr("Start Backup"), function() { + backupRunning = true + feedparserhandler.doBackup() + }) + } + BusyIndicator { + size: BusyIndicatorSize.Medium + anchors.centerIn: importbutton + running: backupRunning + } + } + + Button { + id: opmlbutton + text: qsTr("Save to OPML") + enabled: !opmlWriteRunning + width: parent.width + onClicked: { + Remorse.popupAction(migrationpage, qsTr("Start OPML export"), function() { + opmlWriteRunning = true + feedparserhandler.doWriteOpml() + }) + } + + BusyIndicator { + size: BusyIndicatorSize.Medium + anchors.centerIn: parent + running: opmlWriteRunning + } + } +} diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py index 2763fe032563f55f3bc3e6c89a15188ba20ebc2a..39984ae32d21403487a7eb548e86c2bc0535b420 100644 --- a/qml/components/FeedParser.py +++ b/qml/components/FeedParser.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import logging import pyotherside import threading @@ -18,40 +19,37 @@ from podcast.inbox import InboxFactory from podcast.factory import Factory from podcast.util import create_opml +logger = logging.getLogger(__name__) + cachevar = {} -def get_feedinfo(url): - podcast = PodcastFactory().get_podcast(url) +def get_feedinfo(url, preview=False): + logger.info("getting feedinfo %s for %s", "in previewmode" if preview else "", url) + podcast = PodcastFactory().get_podcast(url, preview=preview) + logger.info("fetched preview, sending feedinfo now.") pyotherside.send("feedinfo", podcast.feedinfo()) def get_alternatives(url): podcast = PodcastFactory().get_podcast(url) - pyotherside.send("alternatives", podcast.get_alt_feeds()) + pyotherside.send("alternatives", {"altfeeds": podcast.alt_feeds}) -def get_first_entry(url): +def get_latest_entry(url): """ Get first Podcast entry """ podcast = PodcastFactory().get_podcast(url) - entry = podcast.get_first_entry() - data = entry.get_data() - pyotherside.send("fevent", data) - + entry = podcast.get_latest_entry() + if entry != None: + data = entry.get_data() + pyotherside.send("firstentryevent", data) -def get_last_entry(url): - - podcast = PodcastFactory().get_podcast(url) - entry = podcast.get_last_entry() - data = entry.get_data() - pyotherside.send("levent", data) - -def get_object(object): - Factory().get_object(object) +def init_from_qml(object): + Factory().init_from_qml(object) def get_entries(url): @@ -61,15 +59,9 @@ def get_entries(url): podcast = PodcastFactory().get_podcast(url) - feeds = [] - - for feed_data in podcast.get_entries(): - feeds.append(feed_data) + feeds = list(podcast.get_entries()) - try: - link = podcast.link - except: - link = podcast.link = podcast.feed.feed.link + link = podcast.link feeds.sort(key=lambda r: r["date"], reverse=True) pyotherside.send("createPodpostsList", feeds, podcast.title, link) @@ -79,7 +71,7 @@ def subscribe_podcast(url): """ Subscribe a podcast """ - + PodcastFactory().remove_podcast(url) podcast_list = PodcastListFactory().get_podcast_list() podcast_list.add(url) pyotherside.send("subscribed", url) @@ -315,7 +307,13 @@ class FeedParser: def getfeedinfo(self, theurl): if self.bgthread.is_alive(): return - self.bgthread = threading.Thread(target=get_feedinfo, args=[theurl]) + self.bgthread = threading.Thread(target=get_feedinfo, args=[theurl, False]) + self.bgthread.start() + + def getpodcastpreview(self, theurl): + if self.bgthread.is_alive(): + return + self.bgthread = threading.Thread(target=get_feedinfo, args=[theurl, True]) self.bgthread.start() def getalternatives(self, theurl): @@ -326,10 +324,10 @@ class FeedParser: ) self.bgthread.start() - def getfirstentry(self, theurl): + def getlatestentry(self, theurl): if self.bgthread.is_alive(): return - self.bgthread = threading.Thread(target=get_first_entry, args=[theurl]) + self.bgthread = threading.Thread(target=get_latest_entry, args=[theurl]) self.bgthread.start() def getentries(self, theurl): @@ -338,12 +336,6 @@ class FeedParser: self.bgthread = threading.Thread(target=get_entries, args=[theurl]) self.bgthread.start() - def getlastentry(self, theurl): - if self.bgthread.is_alive(): - return - self.bgthread = threading.Thread(target=get_last_entry, args=[theurl]) - self.bgthread.start() - def getpodcasts(self): if self.bgthread.is_alive(): return diff --git a/qml/components/FeedParserPython.qml b/qml/components/FeedParserPython.qml index 40ab69a0fb7c39044f06fb700e4997c3d9bf754f..46b7782aeb1d951e5451f279b749704a759f5e06 100644 --- a/qml/components/FeedParserPython.qml +++ b/qml/components/FeedParserPython.qml @@ -6,8 +6,7 @@ Python { id: feedparserhandler signal feedInfo(var pcdata) - signal firstEvent(var pcdata) - signal lastEvent(var pcdata) + signal latestEvent(var pcdata) signal alternatives(var pcdata) signal podcastsList(var pcdata) signal createPodpostsList(var pcdata, string podtitle, string podlink) @@ -25,8 +24,7 @@ Python { Component.onCompleted: { setHandler("feedinfo", feedInfo) - setHandler("fevent", firstEvent) - setHandler("levent", lastEvent) + setHandler("firstentryevent", latestEvent) setHandler("alternatives", alternatives) setHandler("podcastslist", podcastsList) setHandler("createPodpostsList", createPodpostsList) @@ -44,22 +42,27 @@ Python { addImportPath(Qt.resolvedUrl('.')); importModule('FeedParser', function () { - console.log('FeedParser is now imported') - call("FeedParser.get_object", [podqast], function() {}); + console.log('FeedParser is now imported'); + call("FeedParser.init_from_qml", [podqast], function() {}); }); } + function getPodcast(url) { console.log("url: " + url) call("FeedParser.feedparse.getfeedinfo", [url], function() {}); - // call("FeedParser.get_feedinfo", [url], function() {}); + } + + function getPodcastPreview(url) { + console.log("fetching preview for " + url) + call("FeedParser.feedparse.getpodcastpreview", [url], function() {}); } function getPodcasts() { call("FeedParser.feedparse.getpodcasts", function() {}); // call("FeedParser.get_podcasts", function() {}); } + function getPodcastsPreCache() { call("FeedParser.feedparse.getpodcastscache", function() {}); - // call("FeedParser.get_podcastscache", function() {}); } function getEntries(url) { @@ -67,18 +70,11 @@ Python { call("FeedParser.feedparse.getentries", [url], function() {}); // call("FeedParser.get_entries", [url], function() {}); } - function getFirstEvent(url) { - console.log("getFirstElement") - call("FeedParser.feedparse.getfirstentry", [url], function() {}); - // call("FeedParser.get_first_entry", [url], function() {}); + function getLatestEvent(url) { + console.log("getLatestEvent from feed " + url) + call("FeedParser.feedparse.getlatestentry", [url], function() {}); } - function getLastEvent(url) { - console.log("getLastElement") - console.log(url) - call("FeedParser.feedparse.getlastentry", [url], function() {}); - // call("FeedParser.get_last_entry", [url], function() {}); - } function getAlternatives(url) { call("FeedParser.feedparse.getalternatives", [url], function() {}) @@ -172,9 +168,6 @@ Python { console.log('python error: ' + traceback); } - onReceived: { - console.log('got message from python: ' + data); - } onAppError: { console.log("Notify error " + errmessage) appNotification.previewSummary = qsTr("Error") diff --git a/qml/components/MigrationHandler.py b/qml/components/MigrationHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..660a9b355cd0e3d6f0155f4e22a975bfd235408d --- /dev/null +++ b/qml/components/MigrationHandler.py @@ -0,0 +1,10 @@ +import sys +sys.path.append("/usr/share/harbour-podqast/python") + +from podcast import data_migration + +def needs_migration(): + return data_migration.needs_migration() + +def run_migrations(): + data_migration.run_migrations() \ No newline at end of file diff --git a/qml/components/MigrationHandler.qml b/qml/components/MigrationHandler.qml new file mode 100644 index 0000000000000000000000000000000000000000..9e49d73947031daee7251742ccbf9e4b7d802f61 --- /dev/null +++ b/qml/components/MigrationHandler.qml @@ -0,0 +1,42 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import io.thp.pyotherside 1.4 + +Python { + id: migrationhandler + + signal migrationNeeded(bool is_needed) + signal migrationDone() + signal migrationStart(int miglen) + signal migrationProgress(int migpos) + + property int progress_max: 1 + property int progress_current: 1 + property double progress: progress_current/progress_max + property bool running: false + + Component.onCompleted: { + setHandler("migrationStart", migrationStart) + setHandler("migrationProgress", migrationProgress) + setHandler("migrationDone", migrationDone) + + addImportPath(Qt.resolvedUrl('.')); + importModule('MigrationHandler', function () { + console.log("MigrationHandler is now imported") + call('MigrationHandler.needs_migration', [], function(is_needed){ + console.log("Migration is needed: " + is_needed) + migrationNeeded(is_needed) + }) + }) + } + + function start_migration(){ + call('MigrationHandler.run_migrations', function(){}) + } + + function migration_ended(){ + console.log("migration done") + running = false + migrationDone() + } +} diff --git a/qml/components/QueueHandlerPython.qml b/qml/components/QueueHandlerPython.qml index fd06ae6631516894d9a9fdd45e8087426a6c056b..1bb53a408220c93abced4311a421df4eca9981bd 100644 --- a/qml/components/QueueHandlerPython.qml +++ b/qml/components/QueueHandlerPython.qml @@ -109,9 +109,6 @@ Python { console.log('python error: ' + traceback); } - onReceived: { - console.log('got message from python: ' + data); - } onDoStop: { console.log("stopped") playerHandler.stop() diff --git a/qml/harbour-podqast.qml b/qml/harbour-podqast.qml index 9cbf181684b0f2a9ede8244b65e84edecbadd88b..e48cac004e1d57b77c93baada60999bd511f56d2 100644 --- a/qml/harbour-podqast.qml +++ b/qml/harbour-podqast.qml @@ -16,13 +16,30 @@ ApplicationWindow property bool doWizzard: doWizzardConf.value // property bool doWizzard: true - initialPage: { - var pagename - if (podqast.doWizzard === true) - pagename = "pages/Wizzard1.qml" - else - pagename = "pages/Queue.qml" - return Qt.resolvedUrl(pagename) + // postponed until migrationhandler answers + initialPage: Page { + BusyIndicator { + size: BusyIndicatorSize.Large + anchors.centerIn: parent + running: true + } + } + + MigrationHandler{ + id: migrationhandler + onMigrationNeeded: + if(!is_needed){ + var pagename + if (podqast.doWizzard === true) + pagename = "pages/Wizzard1.qml" + else + pagename = "pages/Queue.qml" + console.log("Opening " + pagename + " as first page") + pageStack.replace( Qt.resolvedUrl(pagename)) + } else { + console.log("Opening migrationpage") + pageStack.replace( Qt.resolvedUrl("pages/DataMigration.qml")) + } } diff --git a/qml/pages/Archive.qml b/qml/pages/Archive.qml index 7d36fa7104914adfc9be04bbe5c0e8d3d33ae897..1f957b59762c77a57ef1c96995237089cf61a050 100644 --- a/qml/pages/Archive.qml +++ b/qml/pages/Archive.qml @@ -120,7 +120,6 @@ Page { topage: "External.qml"}) } for (var i = 0; i < pcdata.length; i++) { - console.log(pcdata[i]) pcdata[i]["bethead"] = "Podcast" podcastsModel.append(pcdata[i]); } diff --git a/qml/pages/DataMigration.qml b/qml/pages/DataMigration.qml new file mode 100644 index 0000000000000000000000000000000000000000..7f6daedb2cc732f231b186dba6c8ec4d82c01de0 --- /dev/null +++ b/qml/pages/DataMigration.qml @@ -0,0 +1,115 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "../components" + +Page { + + id: migrationpage + property bool running: false + property bool errorHappened: false + property var errorText : "" + + Connections{ + target: migrationhandler + onMigrationDone: { + appNotification.previewSummary = qsTr("Update done") + appNotification.previewBody = qsTr("Successfully migrated data to the current version") + appNotification.body = qsTr("Successfully migrated data to the current version") + appNotification.publish() + pageStack.replace( Qt.resolvedUrl("Queue.qml")) + } + onMigrationStart: { + console.log("migration started with steps: " + miglen) + running = true + migbar.maximumValue = miglen + migbar.value = 1 + } + + onMigrationProgress: { + console.log("migration progressed to " + migpos) + migbar.value = migpos + } + + onError: { + errorHappened = true + errorText = traceback + } + } + + Column { + id: migrationcomponent + width: parent.width + clip: true + spacing: Theme.paddingLarge + visible: !errorHappened + anchors { + fill: parent + leftMargin: Theme.horizontalPageMargin + rightMargin: Theme.horizontalPageMargin + } + PageHeader{ + title: qsTr("Data Migration") + } + + Label { + width: parent.width + wrapMode: Text.WordWrap + text: qsTr("Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong.") + } + + Button { + text: qsTr("Start Migration") + onClicked: migrationhandler.start_migration() + visible: !running + enabled: !backupcomponent.backupRunning && !backupcomponent.opmlWriteRunning + width: parent.width + backgroundColor: Theme.highlightBackgroundColor + } + + ProgressBar { + id: migbar + width: parent.width + height: Theme.itemSizeSmall + minimumValue: 1 + visible: running + } + + Item { + height: Theme.paddingMedium + width: parent.width + } + + BackupButtons { + id: backupcomponent + visible: !running + width: parent.width + } + } + + Column { + visible: errorHappened + spacing: Theme.paddingLarge + anchors { + fill: parent + leftMargin: Theme.horizontalPageMargin + rightMargin: Theme.horizontalPageMargin + } + + Label{ + text: qsTr("An error has happened.") + } + + Button { + text: qsTr("Copy to clipboard") + onClicked: Clipboard.text = errorText + } + + TextArea { + id: errortextarea + readOnly: true + wrapMode: Text.Wrap + text: errorText + width: parent.width + } + } +} diff --git a/qml/pages/DiscoverExport.qml b/qml/pages/DiscoverExport.qml index 6894a357b84aa1db025015763044562a6504f675..befc3c039e0f449aac21f045661c2c77ee2e88cf 100644 --- a/qml/pages/DiscoverExport.qml +++ b/qml/pages/DiscoverExport.qml @@ -9,94 +9,31 @@ Page { allowedOrientations: Orientation.All property int margin: Theme.paddingMedium - SilicaFlickable { id: mainflick anchors.fill: parent - // contentHeight: page.height - - property bool backupRunning: false - property bool opmlWriteRunning: false - - Connections { - target: feedparserhandler - onBackupDone: { - console.log("Notify backup done") - appNotification.previewSummary = qsTr("Backup done") - appNotification.previewBody = qsTr("Backup done to ") + tarpath - appNotification.body = qsTr("Backup done to ") + tarpath - appNotification.publish() - mainflick.backupRunning = false - } - onOpmlSaveDone: { - console.log("Notify opml save done") - appNotification.previewSummary = qsTr("OPML file saved") - appNotification.previewBody = qsTr("OPML file saved to ") + opmlpath - appNotification.body = qsTr("OPML file saved to ") + opmlpath - appNotification.publish() - mainflick.opmlWriteRunning = false - } - } AppMenu { thispage: "Backup" } PrefAboutMenu {} - Row { - id: discovertitle + Column { + id: column width: page.width - Column { - id: column - width: page.width - spacing: Theme.paddingLarge + spacing: Theme.paddingLarge - PageHeader { - title: qsTr("Export") - } + PageHeader { + title: qsTr("Export") } - } - Column { - id: backupb - height: Theme.itemSizeLarge - anchors.top: discovertitle.bottom - visible: experimentalConf.value - Button { - id: importbutton - text: qsTr("Backup") - enabled: !mainflick.backupRunning - onClicked: { - Remorse.popupAction(page, qsTr("Start Backup"), function() { - mainflick.backupRunning = true - feedparserhandler.doBackup() - }) - } - BusyIndicator { - size: BusyIndicatorSize.Medium - anchors.centerIn: parent - running: mainflick.backupRunning - } - } - } - Column { - id: opmlwrite - height: Theme.itemSizeLarge - anchors.top: backupb.bottom - Button { - id: opmlbutton - text: qsTr("Save to OPML") - enabled: !mainflick.opmlWriteRunning - onClicked: { - Remorse.popupAction(page, qsTr("Start OPML export"), function() { - mainflick.opmlWriteRunning = true - feedparserhandler.doWriteOpml() - }) - } - BusyIndicator { - size: BusyIndicatorSize.Medium - anchors.centerIn: parent - running: mainflick.opmlWriteRunning - } + + BackupButtons { + width: page.width + anchors { + leftMargin: Theme.horizontalPageMargin + rightMargin: Theme.horizontalPageMargin + } } } + PlayDockedPanel { } } } diff --git a/qml/pages/Podcast.qml b/qml/pages/Podcast.qml index 7082bc15bd301d28dd8c89469b725dc57d91640c..2b480e3691159a866086cf9e8c2c5836fc39c10c 100644 --- a/qml/pages/Podcast.qml +++ b/qml/pages/Podcast.qml @@ -35,37 +35,27 @@ Page { PrefAboutMenu {} Component.onCompleted: { - feedparserhandler.getPodcast(url) + feedparserhandler.getPodcastPreview(url) } Connections { target: feedparserhandler onFeedInfo: { - console.log(pcdata.title) - console.log(pcdata.logo_url) - console.log(pcdata.description) - console.log(pcdata.descriptionfull) + console.debug(pcdata.title) + console.debug(pcdata.logo_url) + console.debug(pcdata.description) + console.debug(pcdata.descriptionfull) podimage.source=pcdata.logo_url poddescription.text=pcdata.description description=pcdata.descriptionfull titlefull=pcdata.title - console.log(description) + console.debug(description) subscribebutton.enabled=true feedparserhandler.getAlternatives(url) } - onFirstEvent: { - console.log("fevent"); - console.log(pcdata.title) - fpostlabel.visible = true - fentryListModel.clear(); - fentryListModel.append(pcdata) - feedparserhandler.getLastEvent(url) - } - - onLastEvent: { - console.log("levent"); + onLatestEvent: { console.log(pcdata.title) lpostlabel.visible = true lentryListModel.clear(); @@ -93,7 +83,7 @@ Page { } altcombo.visible=true } - feedparserhandler.getFirstEvent(url) + feedparserhandler.getLatestEvent(url) } onSubscribed: { subscribebutton.subscribed = true @@ -200,7 +190,7 @@ Page { BusyIndicator { size: BusyIndicatorSize.Medium anchors.centerIn: subscribebutton - running: subscribebutton.subscribing + running: subscribebutton.subscribing || !subscribebutton.enabled } } ComboBox { @@ -237,29 +227,6 @@ Page { width: parent.width height: Theme.itemSizeExtraLarge * 1.1 - model: ListModel { - id: fentryListModel - } - delegate: PodcastEntryItem {} - } - Row { - id: fpostlabel - height: Theme.itemSizeExtraSmall - anchors.top: fpodentries.bottom - visible: false - Label { - anchors.verticalCenter: parent.verticalCenter - font.pixelSize: Theme.fontSizeSmall - color: Theme.highlightColor - text: qsTr("First Post") - } - } - SilicaListView { - id: lpodentries - anchors.top: fpostlabel.bottom - width: parent.width - height: Theme.itemSizeExtraLarge * 1.1 - model: ListModel { id: lentryListModel } diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b5399aaba2b1bc44e59015f9f35e75f3519e92cf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +httpretty +pytest \ No newline at end of file diff --git a/test/test_archive.py b/test/test_archive.py index 758e5320cc6177783cc87bf1e9bc67df799a353b..e3d187d7f2efd82cadb3c001a724fb73f20384c0 100644 --- a/test/test_archive.py +++ b/test/test_archive.py @@ -3,6 +3,12 @@ test the archive """ import sys + +import httpretty +from httpretty import HTTPretty, httprettified + +from test.test_podcast import read_testdata, xml_headers + sys.path.append("../python") from podcast.archive import ArchiveFactory, Archive @@ -30,17 +36,20 @@ def test_archive_save(): a = ArchiveFactory().get_archive() a.save() +@httprettified def test_insert(): """ """ + HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/', + body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers) a = ArchiveFactory().get_archive() p = Podcast('https://freakshow.fm/feed/opus/') - e = p.get_first_entry() + e = p.get_entry(p.entry_ids_old_to_new[0]) e.save() a.insert(e.id) - e2 = p.get_last_entry() + e2 = p.get_entry(p.entry_ids_old_to_new[1]) e2.save() a.insert(e2.id) @@ -58,5 +67,5 @@ def test_get_archives(): assert type(post) == str assert type(Factory().get_podpost(post)) == Podpost count += 1 - + # make sure to clean ~/.local/share/harbour-podqast if this fails assert count == 2 diff --git a/test/test_migration_to_v1.py b/test/test_migration_to_v1.py new file mode 100644 index 0000000000000000000000000000000000000000..4f9e587c1fe064fb3c8b1cb746b5c2a6ce2f9509 --- /dev/null +++ b/test/test_migration_to_v1.py @@ -0,0 +1,41 @@ +import sys +import os +import tempfile +from distutils.dir_util import copy_tree + +from podcast import data_migration +from podcast.archive import ArchiveFactory +from podcast.external import ExternalFactory +from podcast.factory import Factory +from podcast.inbox import InboxFactory +from podcast.podcast import Podcast, PodcastFactory +from podcast.podcastlist import PodcastListFactory +from podcast.queue import QueueFactory + +sys.path.append("../python") + +def test_migration(): + with tempfile.TemporaryDirectory() as tmpdir: + copy_tree(os.path.join(os.path.dirname(__file__), "testdata/migrationtests_v1/"), tmpdir) + os.environ["PODQAST_HOME"] = tmpdir + resetSingletons() + data_migration.run_migrations() + assert os.path.exists(os.path.join(tmpdir, "dataversion")) + podcasts = list(PodcastListFactory().get_podcast_list().get_podcasts()) + assert len(podcasts) == 1 + podcast:Podcast = PodcastFactory().get_podcast(podcasts[0]) + assert podcast != None + assert len(podcast.entry_ids_old_to_new) == 22 + assert len(list(podcast.get_entries())) == 22 + del os.environ["PODQAST_HOME"] + resetSingletons() + + +def resetSingletons(): + Factory._instances = {} + PodcastListFactory._instances = {} + PodcastFactory._instances = {} + QueueFactory._instances = {} + ArchiveFactory._instances = {} + ExternalFactory._instances = {} + InboxFactory._instances = {} diff --git a/test/test_podcast.py b/test/test_podcast.py index e955d017873deacbcb479b5f03dfb47e49688e54..23dda8c35e72bb315c6daa986b75b92885f1c328 100644 --- a/test/test_podcast.py +++ b/test/test_podcast.py @@ -2,33 +2,59 @@ Test Podcast functions """ import sys +import os + +import httpretty +from httpretty import HTTPretty + sys.path.append("../python") from podcast.podcast import Podcast -from podcast.podpost import Podpost +xml_headers = {"content-type": "application/rss+xml; charset=UTF-8"} +def read_testdata(filename): + data = "" + with open(os.path.join(os.path.dirname(__file__), filename), 'r') as file: + data = file.read() + return data + +@httpretty.activate def test_feed_entry(): """ Get a feed entry """ + HTTPretty.register_uri(HTTPretty.GET, "http://feeds.twit.tv/sn.xml", + body=read_testdata('testdata/twittv.xml'),adding_headers=xml_headers) + HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/', + body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers) - for e in ['https://freakshow.fm/feed/opus', - 'http://feeds.twit.tv/sn.xml', - 'http://www.bbc.co.uk/programmes/p02nrspf/episodes/downloads.rss']: - p = Podcast(e) - e = p.get_first_entry() - if e != None: - assert type(e) == Podpost - - d = e.get_data() - assert 'id' in d.keys() - assert 'logo_url' in d.keys() - assert 'description' in d.keys() - print(d['id'], d['title']) - print(d['logo_url'], d['description']) - assert len(d['id']) > 0 - assert type(d['id']) == str - assert len(d['logo_url']) > 0 or d['logo_url'] == "" - assert type(d['logo_url']) == str - e2 = p.get_entry(d['id']) - assert e == e2 + for data in [ 'https://freakshow.fm/feed/opus/', + 'http://feeds.twit.tv/sn.xml', + ]: + podcast = Podcast(data) + assert podcast.alt_feeds != None + for feed in podcast.alt_feeds: + assert type(feed['url']) == str + assert type(feed['title']) == str + feedinfo = podcast.feedinfo() + assert type(feedinfo["url"]) == str + assert type(feedinfo["title"]) == str + assert type(feedinfo["description"]) == str + assert type(feedinfo["descriptionfull"]) == str + assert type(feedinfo["logo_url"]) == str + assert podcast.get_latest_entry() != None + entries = podcast.get_entries() + for data in entries: + assert data != None + assert type(data) == dict + assert 'id' in data.keys() + assert 'logo_url' in data.keys() + assert 'description' in data.keys() + print(data['id'], data['title']) + print(data['logo_url'], data['description']) + assert len(data['id']) > 0 + assert type(data['id']) == str + assert len(data['logo_url']) > 0 or data['logo_url'] == "" + assert type(data['logo_url']) == str + assert podcast.get_entry(data['id']) != None + assert data == podcast.get_entry(data['id']).get_data() diff --git a/test/test_podpost.py b/test/test_podpost.py index e16675eddb6bcb364748062e801c060dba0349b7..2d40f3ce8734e07515f4c66f3eb931eba3f2cc4f 100644 --- a/test/test_podpost.py +++ b/test/test_podpost.py @@ -17,7 +17,7 @@ def test_podpost_save(): f1 = Factory() p = Podcast('https://freakshow.fm/feed/opus/') - e = p.get_first_entry() + e = p.get_latest_entry() id1 = e.id e.save() n = Factory().get_store().get(e.id) diff --git a/test/test_queue.py b/test/test_queue.py index 140a6792ea17243a8230e2021538aa14e6662920..76343369e664089900482ce9b5d256e6e1511b38 100644 --- a/test/test_queue.py +++ b/test/test_queue.py @@ -3,6 +3,7 @@ test the queue """ import sys + sys.path.append("../python") from podcast.queue import QueueFactory @@ -12,6 +13,7 @@ from podcast.podcast import Podcast from podcast.podpost import Podpost import podcast + def test_create_queue(): """ def if a queue is created @@ -19,10 +21,11 @@ def test_create_queue(): q1 = QueueFactory().get_queue() assert type(q1) == Queue - + q2 = QueueFactory().get_queue() assert q1 == q2 + def test_queue_save(): """ does the queue save itself? @@ -31,6 +34,7 @@ def test_queue_save(): q = QueueFactory().get_queue() q.save() + def test_insert_top(): """ """ @@ -38,8 +42,8 @@ def test_insert_top(): q = QueueFactory().get_queue() l1 = len(q.podposts) p = Podcast('https://freakshow.fm/feed/opus/') - e = p.get_first_entry() - e2 = p.get_last_entry() + e = p.get_entry(p.entry_ids_old_to_new[1]) + e2 = p.get_entry(p.entry_ids_old_to_new[0]) e.save() q.insert_top(e.id) @@ -53,16 +57,17 @@ def test_insert_top(): q.save() + def test_get_podposts(): """ Test listing of podposts """ - q = QueueFactory().get_queue() - count = 0 - for post in q.get_podposts(): + queue = QueueFactory().get_queue() + queue.podposts = ['85140fc8e16ba62517877ef4857a85f5bbf83e6a236c659a73489f093e51f154', + 'ce656a3888a282fe9df727e0729fa7d375704e4bd6c0d50e9c0fe7ddafa232ec'] + postids = list(queue.get_podposts()) + assert len(postids) == 2 + for post in postids: assert type(post) == str assert type(Factory().get_podpost(post)) == Podpost - count += 1 - - assert count == 2 diff --git a/test/testdata/freakshow.rss b/test/testdata/freakshow.rss new file mode 100644 index 0000000000000000000000000000000000000000..c81a8fccf91a314a6c96eb557bb53cdfbf2f2e2f --- /dev/null +++ b/test/testdata/freakshow.rss @@ -0,0 +1,15599 @@ + + + + + + en + Freak Show + https://freakshow.fm + Menschen! Technik! Sensationen! + Thu, 24 Dec 2020 21:53:57 +0000 + + https://meta.metaebene.me/media/mm/freakshow-logo-1.0.jpg + Freak Show + https://freakshow.fm + + + + + + + diff --git a/test/testdata/migrationtests_v1/store/0276e064a8d26db15546e98870e057c831ef302ed5eee2e1fa742545f8df1a9b.pickle b/test/testdata/migrationtests_v1/store/0276e064a8d26db15546e98870e057c831ef302ed5eee2e1fa742545f8df1a9b.pickle new file mode 100644 index 0000000000000000000000000000000000000000..8540a512850f217a374714da5d07e5d4335830fd Binary files /dev/null and b/test/testdata/migrationtests_v1/store/0276e064a8d26db15546e98870e057c831ef302ed5eee2e1fa742545f8df1a9b.pickle differ diff --git a/test/testdata/migrationtests_v1/store/0ea380c65dcd0a6f0ea7206b8dac131dde918ab0e51b88c769762b850a130f6e.pickle b/test/testdata/migrationtests_v1/store/0ea380c65dcd0a6f0ea7206b8dac131dde918ab0e51b88c769762b850a130f6e.pickle new file mode 100644 index 0000000000000000000000000000000000000000..57933ec7b5ce6e873000a3e24a48f17e83bceb25 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/0ea380c65dcd0a6f0ea7206b8dac131dde918ab0e51b88c769762b850a130f6e.pickle differ diff --git a/test/testdata/migrationtests_v1/store/139021a0cf219244600abec83dddddd016777dd39012a4be4fbda44c3ee50fd3.pickle b/test/testdata/migrationtests_v1/store/139021a0cf219244600abec83dddddd016777dd39012a4be4fbda44c3ee50fd3.pickle new file mode 100644 index 0000000000000000000000000000000000000000..6dfe75d028aea18ca149fcda3b31782a6d68faa5 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/139021a0cf219244600abec83dddddd016777dd39012a4be4fbda44c3ee50fd3.pickle differ diff --git a/test/testdata/migrationtests_v1/store/141b27be9d148bc9a39fb03e6a764d39f5e0c8c44aa4350b958eddb0c4089e89.pickle b/test/testdata/migrationtests_v1/store/141b27be9d148bc9a39fb03e6a764d39f5e0c8c44aa4350b958eddb0c4089e89.pickle new file mode 100644 index 0000000000000000000000000000000000000000..a6c0b011027e67cebe7eca15cc534c788e1cf6a2 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/141b27be9d148bc9a39fb03e6a764d39f5e0c8c44aa4350b958eddb0c4089e89.pickle differ diff --git a/test/testdata/migrationtests_v1/store/24ddbd926a2c2ed3f35bcb21546ab3bf9302dc9c78b1ddd07e9e95e9da9aea67.pickle b/test/testdata/migrationtests_v1/store/24ddbd926a2c2ed3f35bcb21546ab3bf9302dc9c78b1ddd07e9e95e9da9aea67.pickle new file mode 100644 index 0000000000000000000000000000000000000000..7d86576b3887cd5319b8c2be8d52edf915aea0a4 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/24ddbd926a2c2ed3f35bcb21546ab3bf9302dc9c78b1ddd07e9e95e9da9aea67.pickle differ diff --git a/test/testdata/migrationtests_v1/store/2d03e1e2022b57ab347f1636e73d41d2fb3e172e83a76f04d132704d7193fed0.pickle b/test/testdata/migrationtests_v1/store/2d03e1e2022b57ab347f1636e73d41d2fb3e172e83a76f04d132704d7193fed0.pickle new file mode 100644 index 0000000000000000000000000000000000000000..a534b72ed255275764b1529b0f2235b425c687dd Binary files /dev/null and b/test/testdata/migrationtests_v1/store/2d03e1e2022b57ab347f1636e73d41d2fb3e172e83a76f04d132704d7193fed0.pickle differ diff --git a/test/testdata/migrationtests_v1/store/4f4e1792ffb65b5e90a37a170627355e47d3c85f1e02780b8464f930d983b05c.pickle b/test/testdata/migrationtests_v1/store/4f4e1792ffb65b5e90a37a170627355e47d3c85f1e02780b8464f930d983b05c.pickle new file mode 100644 index 0000000000000000000000000000000000000000..31eaf50479c542623da37d52c3537eb3f888e64e Binary files /dev/null and b/test/testdata/migrationtests_v1/store/4f4e1792ffb65b5e90a37a170627355e47d3c85f1e02780b8464f930d983b05c.pickle differ diff --git a/test/testdata/migrationtests_v1/store/4fad46ee786878615b7a61d3ae4ae06b6400de11671733e85344d76ae5633fc5.pickle b/test/testdata/migrationtests_v1/store/4fad46ee786878615b7a61d3ae4ae06b6400de11671733e85344d76ae5633fc5.pickle new file mode 100644 index 0000000000000000000000000000000000000000..4a0335eb82d161d6ef650380a9a48507618bbc7a Binary files /dev/null and b/test/testdata/migrationtests_v1/store/4fad46ee786878615b7a61d3ae4ae06b6400de11671733e85344d76ae5633fc5.pickle differ diff --git a/test/testdata/migrationtests_v1/store/635bfd0ba43e4a90181c64205cc0153a985ecad7636f8dfbbb3a9b2162c9929e.pickle b/test/testdata/migrationtests_v1/store/635bfd0ba43e4a90181c64205cc0153a985ecad7636f8dfbbb3a9b2162c9929e.pickle new file mode 100644 index 0000000000000000000000000000000000000000..c394871b1fcafad28e47af9014d76cd125501749 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/635bfd0ba43e4a90181c64205cc0153a985ecad7636f8dfbbb3a9b2162c9929e.pickle differ diff --git a/test/testdata/migrationtests_v1/store/6cb0966c9ed8070c6db0d691964a50dd9c43e484cdd7186070f184aa7b53d7f2.pickle b/test/testdata/migrationtests_v1/store/6cb0966c9ed8070c6db0d691964a50dd9c43e484cdd7186070f184aa7b53d7f2.pickle new file mode 100644 index 0000000000000000000000000000000000000000..1e07603a1875726ff70843ed2a732bc03fdd5350 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/6cb0966c9ed8070c6db0d691964a50dd9c43e484cdd7186070f184aa7b53d7f2.pickle differ diff --git a/test/testdata/migrationtests_v1/store/74f1495e5c2dfcafbb174dcc2cf2b3c3bfd37126c1973951501a024e74d5b34e.pickle b/test/testdata/migrationtests_v1/store/74f1495e5c2dfcafbb174dcc2cf2b3c3bfd37126c1973951501a024e74d5b34e.pickle new file mode 100644 index 0000000000000000000000000000000000000000..ea7affd681ab64079b7e874b30bdcc33b0c2befc Binary files /dev/null and b/test/testdata/migrationtests_v1/store/74f1495e5c2dfcafbb174dcc2cf2b3c3bfd37126c1973951501a024e74d5b34e.pickle differ diff --git a/test/testdata/migrationtests_v1/store/76f5290fe79908572f7dabe92e0d3008d743a87497aac04c063b4b2142be9577.pickle b/test/testdata/migrationtests_v1/store/76f5290fe79908572f7dabe92e0d3008d743a87497aac04c063b4b2142be9577.pickle new file mode 100644 index 0000000000000000000000000000000000000000..6c201cc9da15d7dbea2607fe90a67f90ccb2e275 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/76f5290fe79908572f7dabe92e0d3008d743a87497aac04c063b4b2142be9577.pickle differ diff --git a/test/testdata/migrationtests_v1/store/7e52e95530e18916808d507b41cf471865e7a9e090a8a17d6dcd8ec05d9fbf52.pickle b/test/testdata/migrationtests_v1/store/7e52e95530e18916808d507b41cf471865e7a9e090a8a17d6dcd8ec05d9fbf52.pickle new file mode 100644 index 0000000000000000000000000000000000000000..a004b16f8bb0dd4e97fdf37b4b7695e65ca39a85 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/7e52e95530e18916808d507b41cf471865e7a9e090a8a17d6dcd8ec05d9fbf52.pickle differ diff --git a/test/testdata/migrationtests_v1/store/a994b2d5ad8846017f225560e13d7778d9bb68fe05e198ffcb03d44776e88225.pickle b/test/testdata/migrationtests_v1/store/a994b2d5ad8846017f225560e13d7778d9bb68fe05e198ffcb03d44776e88225.pickle new file mode 100644 index 0000000000000000000000000000000000000000..890e3f4272afc0b6dbbef4dc174334251edcbebf Binary files /dev/null and b/test/testdata/migrationtests_v1/store/a994b2d5ad8846017f225560e13d7778d9bb68fe05e198ffcb03d44776e88225.pickle differ diff --git a/test/testdata/migrationtests_v1/store/a9d010758f07750089e5e33bd34ece60b7e58a644c4301196317725090c4e436.pickle b/test/testdata/migrationtests_v1/store/a9d010758f07750089e5e33bd34ece60b7e58a644c4301196317725090c4e436.pickle new file mode 100644 index 0000000000000000000000000000000000000000..7aa1ebfaeb7ac63925492e2a4d722423a4b812c8 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/a9d010758f07750089e5e33bd34ece60b7e58a644c4301196317725090c4e436.pickle differ diff --git a/test/testdata/migrationtests_v1/store/bdd92c06088130a3f0c97f59b70fa290ce0f2d483f07a5af752067ab93e28254.pickle b/test/testdata/migrationtests_v1/store/bdd92c06088130a3f0c97f59b70fa290ce0f2d483f07a5af752067ab93e28254.pickle new file mode 100644 index 0000000000000000000000000000000000000000..9eb822b030174b3dd3f0a526bfad1613b83a3db6 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/bdd92c06088130a3f0c97f59b70fa290ce0f2d483f07a5af752067ab93e28254.pickle differ diff --git a/test/testdata/migrationtests_v1/store/cadda373c547afec7792d24db35e484641d9637c8c0c18218eeadf1adee187f6.pickle b/test/testdata/migrationtests_v1/store/cadda373c547afec7792d24db35e484641d9637c8c0c18218eeadf1adee187f6.pickle new file mode 100644 index 0000000000000000000000000000000000000000..257d65d2887020909e16454dc0c8e2c112e71b6c Binary files /dev/null and b/test/testdata/migrationtests_v1/store/cadda373c547afec7792d24db35e484641d9637c8c0c18218eeadf1adee187f6.pickle differ diff --git a/test/testdata/migrationtests_v1/store/d64a75f5f99034ba83658d04a7a2534291cea143994623d5d997d5753940d7a6.pickle b/test/testdata/migrationtests_v1/store/d64a75f5f99034ba83658d04a7a2534291cea143994623d5d997d5753940d7a6.pickle new file mode 100644 index 0000000000000000000000000000000000000000..702c291ea942d2fd2453467054fe4d9c7db65e1d Binary files /dev/null and b/test/testdata/migrationtests_v1/store/d64a75f5f99034ba83658d04a7a2534291cea143994623d5d997d5753940d7a6.pickle differ diff --git a/test/testdata/migrationtests_v1/store/de23bac5c28824371ec499ef1d1c0d7611caf161540578bdebf8639251222796.pickle b/test/testdata/migrationtests_v1/store/de23bac5c28824371ec499ef1d1c0d7611caf161540578bdebf8639251222796.pickle new file mode 100644 index 0000000000000000000000000000000000000000..79b275d1d237e8b1263baafdbf5162c3d7246553 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/de23bac5c28824371ec499ef1d1c0d7611caf161540578bdebf8639251222796.pickle differ diff --git a/test/testdata/migrationtests_v1/store/df1eac9761b290c0dfefc6441b33b613ad9707f57e8cd03a7d915baa8949bed3.pickle b/test/testdata/migrationtests_v1/store/df1eac9761b290c0dfefc6441b33b613ad9707f57e8cd03a7d915baa8949bed3.pickle new file mode 100644 index 0000000000000000000000000000000000000000..88d19d9c117c32824630dd08e02e96e48067c6f2 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/df1eac9761b290c0dfefc6441b33b613ad9707f57e8cd03a7d915baa8949bed3.pickle differ diff --git a/test/testdata/migrationtests_v1/store/e5533b78cc2bbeaaf384b162f79c9ea1b4cd2a0b3f26b4407816a3db2ea41f92.pickle b/test/testdata/migrationtests_v1/store/e5533b78cc2bbeaaf384b162f79c9ea1b4cd2a0b3f26b4407816a3db2ea41f92.pickle new file mode 100644 index 0000000000000000000000000000000000000000..c4a439333e7edd19ff81c4d042c863185eff6e6f Binary files /dev/null and b/test/testdata/migrationtests_v1/store/e5533b78cc2bbeaaf384b162f79c9ea1b4cd2a0b3f26b4407816a3db2ea41f92.pickle differ diff --git a/test/testdata/migrationtests_v1/store/e6fcb2385c3f6faf1a3f85490d68571347b0de2dfa1104689a47655ade4ab305.pickle b/test/testdata/migrationtests_v1/store/e6fcb2385c3f6faf1a3f85490d68571347b0de2dfa1104689a47655ade4ab305.pickle new file mode 100644 index 0000000000000000000000000000000000000000..0ffd1268333b701912b73495f1b34423d4bb3903 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/e6fcb2385c3f6faf1a3f85490d68571347b0de2dfa1104689a47655ade4ab305.pickle differ diff --git a/test/testdata/migrationtests_v1/store/eb2f7aae7bba969258004bd9d690a40d2b7384b020a6ec1f042fc41356ed621d.pickle b/test/testdata/migrationtests_v1/store/eb2f7aae7bba969258004bd9d690a40d2b7384b020a6ec1f042fc41356ed621d.pickle new file mode 100644 index 0000000000000000000000000000000000000000..cd063959ec5a5f7f37b890b0f005286a009a6af4 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/eb2f7aae7bba969258004bd9d690a40d2b7384b020a6ec1f042fc41356ed621d.pickle differ diff --git a/test/testdata/migrationtests_v1/store/ec866bd1cdc14faf86c9f37b278f33641b34a5ce5e9946848bedd93f5d49bb42.pickle b/test/testdata/migrationtests_v1/store/ec866bd1cdc14faf86c9f37b278f33641b34a5ce5e9946848bedd93f5d49bb42.pickle new file mode 100644 index 0000000000000000000000000000000000000000..1fc013cfdf07693e9ec93b5d73a65a1955cc39f1 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/ec866bd1cdc14faf86c9f37b278f33641b34a5ce5e9946848bedd93f5d49bb42.pickle differ diff --git a/test/testdata/migrationtests_v1/store/fc1770e242c5ed7d8853286febc66214d52d44e6e929f2435ec0010b80f14578.pickle b/test/testdata/migrationtests_v1/store/fc1770e242c5ed7d8853286febc66214d52d44e6e929f2435ec0010b80f14578.pickle new file mode 100644 index 0000000000000000000000000000000000000000..929710593bc6cc24adc229cac628fdd051f4c538 Binary files /dev/null and b/test/testdata/migrationtests_v1/store/fc1770e242c5ed7d8853286febc66214d52d44e6e929f2435ec0010b80f14578.pickle differ diff --git a/test/testdata/twittv.xml b/test/testdata/twittv.xml new file mode 100644 index 0000000000000000000000000000000000000000..f0f7370083c6cc6ab2d4ae7c8ac74f8657948890 --- /dev/null +++ b/test/testdata/twittv.xml @@ -0,0 +1,964 @@ + + + + Security Now (Audio) + https://twit.tv/shows/security-now + TWiT Feed Generator v3.10.3 + http://blogs.law.harvard.edu/tech/rss + en-US + This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/ + http://creativecommons.org/licenses/by-nc-nd/4.0/ + distro@twit.tv (TWiT Editors) + distro@twit.tv (TWiT Engineering) + 720 + weekly + 1 + Tue, 29 Dec 2020 18:55:01 PST + Tue, 29 Dec 2020 18:55:01 PST + weekly + Petaluma, CA + episodic + TWiT + Steve Gibson discusses the hot topics in security today with Leo Laporte. + Steve Gibson, the man who coined the term spyware and created the first anti-spyware program, creator of Spinrite and ShieldsUP, discusses the hot topics in security today with Leo Laporte. + +Records live every Tuesday at 4:30pm Eastern / 1:30pm Pacific / 21:30 UTC. + Steve Gibson, the man who coined the term spyware and created the first anti-spyware program, creator of Spinrite and ShieldsUP, discusses the hot topics in security today with Leo Laporte. + +Records live every Tuesday at 4:30pm Eastern / 1:30pm Pacific / 21:30 UTC. + TWiT, Technology, Steve Gibson, Leo Laporte, security, spyware, malware, hacking, cyber crime, emcryption + tv-g + false + no + + Leo Laporte + distro@twit.tv + + + + + + + Security Now (Audio) + https://elroy.twit.tv/sites/default/files/styles/twit_album_art_144x144/public/images/shows/security_now/album_art/audio/sn_albumart_mask.jpg?itok=VEh3JGKV + https://twit.tv/shows/security-now + 144 + 144 + + + https://feeds.twit.tv/sn.xml + + + + SN 799: Sunburst & Supernova - Ransomware Task Force, Chrome 87, Firefox Caches, Preserving Flash Video + Sunburst & Supernova - Ransomware Task Force, Chrome 87, Firefox Caches, Preserving Flash Video + full + Tue, 29 Dec 2020 17:30:00 PST + 799 + https://twit.tv/shows/security-now/episodes/799 + https://twit.tv/shows/security-now/episodes/799 + TWiT + Technology + Security + clean + Ransomware Task Force, Chrome 87, Firefox Caches, Preserving Flash Video + Security Now, TWiT, steve gibson, Leo Laporte, chrome 87, insecure form warning, insecure form, chrome form, firefox cache, firefox partition, kazakhstan browser, rtf, ransomware task force, wordpress insecure, wordpress critical, wordpress plugin + Ransomware Task Force, Chrome 87, Firefox caches, preserving Flash video.

+
  • Chrome 87 backs away from Insecure Form Warnings.
  • +
  • Firefox to begin partitioning its caches.
  • +
  • Browsers say no to Kazakhstan again.
  • +
  • Announcing the RTF - The Ransomware Task Force.
  • +
  • 5 million WordPress sites in critical danger.
  • +
  • Treck's TCP/IO stack strikes again!
  • +
  • Preserving Flash content online.
  • +
  • SpinRite: ReadSpeed is ready!
  • +
  • InitDisk is at release 5.
  • +
  • Numerous updates on SolarWind, Sunburst, and Supernova.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-799-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+]]>
+ Ransomware Task Force, Chrome 87, Firefox caches, preserving Flash video.

+
  • Chrome 87 backs away from Insecure Form Warnings.
  • +
  • Firefox to begin partitioning its caches.
  • +
  • Browsers say no to Kazakhstan again.
  • +
  • Announcing the RTF - The Ransomware Task Force.
  • +
  • 5 million WordPress sites in critical danger.
  • +
  • Treck's TCP/IO stack strikes again!
  • +
  • Preserving Flash content online.
  • +
  • SpinRite: ReadSpeed is ready!
  • +
  • InitDisk is at release 5.
  • +
  • Numerous updates on SolarWind, Sunburst, and Supernova.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-799-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+]]>
+ Ransomware Task Force, Chrome 87, Firefox caches, preserving Flash video.

+
  • Chrome 87 backs away from Insecure Form Warnings.
  • +
  • Firefox to begin partitioning its caches.
  • +
  • Browsers say no to Kazakhstan again.
  • +
  • Announcing the RTF - The Ransomware Task Force.
  • +
  • 5 million WordPress sites in critical danger.
  • +
  • Treck's TCP/IO stack strikes again!
  • +
  • Preserving Flash content online.
  • +
  • SpinRite: ReadSpeed is ready!
  • +
  • InitDisk is at release 5.
  • +
  • Numerous updates on SolarWind, Sunburst, and Supernova.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-799-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0799/sn0799.mp3 + 1:36:11 + + + SN 799: Sunburst & Supernova - Ransomware Task Force, Chrome 87, Firefox Caches, Preserving Flash Video + Ransomware Task Force, Chrome 87, Firefox Caches, Preserving Flash Video + Security Now, TWiT, steve gibson, Leo Laporte, chrome 87, insecure form warning, insecure form, chrome form, firefox cache, firefox partition, kazakhstan browser, rtf, ransomware task force, wordpress insecure, wordpress critical, wordpress plugin + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 798: Best of 2020 - The Year's Best Stories on Security Now + Best of 2020 - The Year's Best Stories on Security Now + full + Tue, 22 Dec 2020 11:00:00 PST + 798 + https://twit.tv/shows/security-now/episodes/798 + https://twit.tv/shows/security-now/episodes/798 + TWiT + Technology + Security + clean + The Year's Best Stories on Security Now + Security Now, TWiT, steve gibson, Leo Laporte, bestof 2020, security 2020, Clearview AI, EARN IT Act, Contact Tracing, covid tech, covid security, twitter hack, zoom security, zoombomb + Leo Laporte walks through some of the highlights of the show and most impactful stories of 2020. Stories include:

+
  • Clearview AI face scanning.
  • +
  • The "EARN IT" act.
  • +
  • Zoom security issues.
  • +
  • Why contact tracing apps won't work.
  • +
  • How to prevent the next Twitter hack
  • +
  • Ring's autonomous flying home security webcam.
  • +
+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+]]>
+ Leo Laporte walks through some of the highlights of the show and most impactful stories of 2020. Stories include:

+
  • Clearview AI face scanning.
  • +
  • The "EARN IT" act.
  • +
  • Zoom security issues.
  • +
  • Why contact tracing apps won't work.
  • +
  • How to prevent the next Twitter hack
  • +
  • Ring's autonomous flying home security webcam.
  • +
+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+]]>
+ Leo Laporte walks through some of the highlights of the show and most impactful stories of 2020. Stories include:

+
  • Clearview AI face scanning.
  • +
  • The "EARN IT" act.
  • +
  • Zoom security issues.
  • +
  • Why contact tracing apps won't work.
  • +
  • How to prevent the next Twitter hack
  • +
  • Ring's autonomous flying home security webcam.
  • +
+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0798/sn0798.mp3 + 1:13:01 + + + SN 798: Best of 2020 - The Year's Best Stories on Security Now + The Year's Best Stories on Security Now + Security Now, TWiT, steve gibson, Leo Laporte, bestof 2020, security 2020, Clearview AI, EARN IT Act, Contact Tracing, covid tech, covid security, twitter hack, zoom security, zoombomb + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 797: SolarWinds - Chrome Throttling Ads, Google Outage, 2020 Pwnie Awards, JavaScript's 25th Birthday + SolarWinds - Chrome Throttling Ads, Google Outage, 2020 Pwnie Awards, JavaScript's 25th Birthday + full + Tue, 15 Dec 2020 19:00:00 PST + 797 + https://twit.tv/shows/security-now/episodes/797 + https://twit.tv/shows/security-now/episodes/797 + TWiT + Technology + Security + clean + Chrome Throttling Ads, Google Outage, 2020 Pwnie Awards, JavaScript's 25th Birthday + Security Now, TWiT, steve gibson, Leo Laporte, solarwinds, flash dead, end of flash, amnesia:33, d-link vulnerabiity, vpn vulnerability, patch tuesday, double extortion, chrome heavy ad, adrozek, pwnie award, pwnie 2020, initdisk 4, solarwinds hack + Chrome throttling ads, Google outage, 2020 Pwnie Awards, JavaScript's 25th birthday.

+
  • Chrome's heavy ad intervention.
  • +
  • Adrozek.
  • +
  • Ransomware: "Double Extortion."
  • +
  • A 0-click wormable vulnerability in D-Link VPN servers.
  • +
  • Google suffered an outage.
  • +
  • Amnesia:33.
  • +
  • Zero-day in WordPress SMTP plugin.
  • +
  • The 2020 Pwnie Awards.
  • +
  • The end of Flash.
  • +
  • JavaScript is celebrating its 25th birthday.
  • +
  • InitDisk release 4 published.
  • +
  • A deep look at the SolarWinds hack.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-797-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Chrome throttling ads, Google outage, 2020 Pwnie Awards, JavaScript's 25th birthday.

+
  • Chrome's heavy ad intervention.
  • +
  • Adrozek.
  • +
  • Ransomware: "Double Extortion."
  • +
  • A 0-click wormable vulnerability in D-Link VPN servers.
  • +
  • Google suffered an outage.
  • +
  • Amnesia:33.
  • +
  • Zero-day in WordPress SMTP plugin.
  • +
  • The 2020 Pwnie Awards.
  • +
  • The end of Flash.
  • +
  • JavaScript is celebrating its 25th birthday.
  • +
  • InitDisk release 4 published.
  • +
  • A deep look at the SolarWinds hack.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-797-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Chrome throttling ads, Google outage, 2020 Pwnie Awards, JavaScript's 25th birthday.

+
  • Chrome's heavy ad intervention.
  • +
  • Adrozek.
  • +
  • Ransomware: "Double Extortion."
  • +
  • A 0-click wormable vulnerability in D-Link VPN servers.
  • +
  • Google suffered an outage.
  • +
  • Amnesia:33.
  • +
  • Zero-day in WordPress SMTP plugin.
  • +
  • The 2020 Pwnie Awards.
  • +
  • The end of Flash.
  • +
  • JavaScript is celebrating its 25th birthday.
  • +
  • InitDisk release 4 published.
  • +
  • A deep look at the SolarWinds hack.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-797-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0797/sn0797.mp3 + 2:11:23 + + + SN 797: SolarWinds - Chrome Throttling Ads, Google Outage, 2020 Pwnie Awards, JavaScript's 25th Birthday + Chrome Throttling Ads, Google Outage, 2020 Pwnie Awards, JavaScript's 25th Birthday + Security Now, TWiT, steve gibson, Leo Laporte, solarwinds, flash dead, end of flash, amnesia:33, d-link vulnerabiity, vpn vulnerability, patch tuesday, double extortion, chrome heavy ad, adrozek, pwnie award, pwnie 2020, initdisk 4, solarwinds hack + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 796: Amazon Sidewalk - Google Play Core Library, iOS Zero-Click Radio Proximity Exploit, Apple M1 Chip + Amazon Sidewalk - Google Play Core Library, iOS Zero-Click Radio Proximity Exploit, Apple M1 Chip + full + Tue, 08 Dec 2020 18:00:00 PST + 796 + https://twit.tv/shows/security-now/episodes/796 + https://twit.tv/shows/security-now/episodes/796 + TWiT + Technology + Security + clean + Google Play Core Library, iOS Zero-Click Radio Proximity Exploit, Apple M1 Chip + Security Now, TWiT, steve gibson, Leo Laporte, Amazon Sidewalk, sidewalk security, sidewalk privacy, ransomware foxconn, ransomware egregor, ransomware k12, iphone wormable hack, iphone zero-click hack, ios wormable hack, ios radio hack, odoh + Google Play Core Library, iOS zero-click radio proximity exploit, Apple M1 chip.

+
  • Ransomware news regarding Foxconn, Egregor, and K12 Inc.
  • +
  • The Apple iPhone zero-click radio proximity vulnerability.
  • +
  • Oblivious DoH (ODoH).
  • +
  • Google Play Core Library problems.
  • +
  • The mysterious power of Apple's M1 Arm processor chip.
  • +
  • InitDisk release 2 published.
  • +
  • SpinRite update.
  • +
  • Amazon Sidewalk.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-796-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Google Play Core Library, iOS zero-click radio proximity exploit, Apple M1 chip.

+
  • Ransomware news regarding Foxconn, Egregor, and K12 Inc.
  • +
  • The Apple iPhone zero-click radio proximity vulnerability.
  • +
  • Oblivious DoH (ODoH).
  • +
  • Google Play Core Library problems.
  • +
  • The mysterious power of Apple's M1 Arm processor chip.
  • +
  • InitDisk release 2 published.
  • +
  • SpinRite update.
  • +
  • Amazon Sidewalk.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-796-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Google Play Core Library, iOS zero-click radio proximity exploit, Apple M1 chip.

+
  • Ransomware news regarding Foxconn, Egregor, and K12 Inc.
  • +
  • The Apple iPhone zero-click radio proximity vulnerability.
  • +
  • Oblivious DoH (ODoH).
  • +
  • Google Play Core Library problems.
  • +
  • The mysterious power of Apple's M1 Arm processor chip.
  • +
  • InitDisk release 2 published.
  • +
  • SpinRite update.
  • +
  • Amazon Sidewalk.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-796-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0796/sn0796.mp3 + 2:10:22 + + + SN 796: Amazon Sidewalk - Google Play Core Library, iOS Zero-Click Radio Proximity Exploit, Apple M1 Chip + Google Play Core Library, iOS Zero-Click Radio Proximity Exploit, Apple M1 Chip + Security Now, TWiT, steve gibson, Leo Laporte, Amazon Sidewalk, sidewalk security, sidewalk privacy, ransomware foxconn, ransomware egregor, ransomware k12, iphone wormable hack, iphone zero-click hack, ios wormable hack, ios radio hack, odoh + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 795: DNS Consolidation - Generic Smart Doorbells, Tesla Model X Key Fobs, Critical Drupal Flaw, Spotify + DNS Consolidation - Generic Smart Doorbells, Tesla Model X Key Fobs, Critical Drupal Flaw, Spotify + full + Tue, 01 Dec 2020 19:00:00 PST + 795 + https://twit.tv/shows/security-now/episodes/795 + https://twit.tv/shows/security-now/episodes/795 + TWiT + Technology + Security + clean + Generic Smart Doorbells, Tesla Model X Key Fobs, Critical Drupal Flaw, Spotify + Security Now, TWiT, steve gibson, Leo Laporte, ransomware, Canon, us fertility, banijay, drupal core, php code execution, tesla, tesla fob hack, tesla x hack, smart doorbells, hack doorbells, chrome open tab search, chrome omnibox, fortinet VPN leak + Generic smart doorbells, Tesla Model X key fobs, critical Drupal flaw, Spotify.

+
  • Chrome Omnibox becomes more Omni.
  • +
  • Chrome's open tabs search.
  • +
  • Ransomware news involving Delaware County, Canon, US Fertility, Ritzau, Baltimore County Public Schools, and Banijay group SAS.
  • +
  • Drupal's security advisory titled "Drupal core - Critical - Arbitrary PHP code execution."
  • +
  • The revenge of cheap smart doorbells.
  • +
  • Tesla Key Fob Hack #3.
  • +
  • CA's adapt to single-year certs.
  • +
  • Nearly 50,000 Fortinet VPN credentials posted online.
  • +
  • More than 300,000 Spotify accounts hacked.
  • +
  • MobileIron MDM CVSS 9.8 RCE.
  • +
  • The Salvation Trilogy.
  • +
  • Spinrite update.
  • +
  • DNS Consolidation.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-795-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Generic smart doorbells, Tesla Model X key fobs, critical Drupal flaw, Spotify.

+
  • Chrome Omnibox becomes more Omni.
  • +
  • Chrome's open tabs search.
  • +
  • Ransomware news involving Delaware County, Canon, US Fertility, Ritzau, Baltimore County Public Schools, and Banijay group SAS.
  • +
  • Drupal's security advisory titled "Drupal core - Critical - Arbitrary PHP code execution."
  • +
  • The revenge of cheap smart doorbells.
  • +
  • Tesla Key Fob Hack #3.
  • +
  • CA's adapt to single-year certs.
  • +
  • Nearly 50,000 Fortinet VPN credentials posted online.
  • +
  • More than 300,000 Spotify accounts hacked.
  • +
  • MobileIron MDM CVSS 9.8 RCE.
  • +
  • The Salvation Trilogy.
  • +
  • Spinrite update.
  • +
  • DNS Consolidation.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-795-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Generic smart doorbells, Tesla Model X key fobs, critical Drupal flaw, Spotify.

+
  • Chrome Omnibox becomes more Omni.
  • +
  • Chrome's open tabs search.
  • +
  • Ransomware news involving Delaware County, Canon, US Fertility, Ritzau, Baltimore County Public Schools, and Banijay group SAS.
  • +
  • Drupal's security advisory titled "Drupal core - Critical - Arbitrary PHP code execution."
  • +
  • The revenge of cheap smart doorbells.
  • +
  • Tesla Key Fob Hack #3.
  • +
  • CA's adapt to single-year certs.
  • +
  • Nearly 50,000 Fortinet VPN credentials posted online.
  • +
  • More than 300,000 Spotify accounts hacked.
  • +
  • MobileIron MDM CVSS 9.8 RCE.
  • +
  • The Salvation Trilogy.
  • +
  • Spinrite update.
  • +
  • DNS Consolidation.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-795-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0795/sn0795.mp3 + 2:03:46 + + + SN 795: DNS Consolidation - Generic Smart Doorbells, Tesla Model X Key Fobs, Critical Drupal Flaw, Spotify + Generic Smart Doorbells, Tesla Model X Key Fobs, Critical Drupal Flaw, Spotify + Security Now, TWiT, steve gibson, Leo Laporte, ransomware, Canon, us fertility, banijay, drupal core, php code execution, tesla, tesla fob hack, tesla x hack, smart doorbells, hack doorbells, chrome open tab search, chrome omnibox, fortinet VPN leak + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 794: Cicada - Ongoing WordPress Attack, RCS Gets End-to-End Encryption + Cicada - Ongoing WordPress Attack, RCS Gets End-to-End Encryption + full + Tue, 24 Nov 2020 17:30:00 PST + 794 + https://twit.tv/shows/security-now/episodes/794 + https://twit.tv/shows/security-now/episodes/794 + TWiT + Technology + Security + clean + Ongoing WordPress Attack, RCS Gets End-to-End Encryption + Security Now, TWiT, steve gibson, Leo Laporte, cicada china, cicada cyber espionage, rcs e2e, rcs encryption, rcs signal, bluekeep rdp, bluekeep bug, wordpress epsilon hack, wordpress hack, wordpress security, wordpress theme vulnerability + Ongoing WordPress attack, RCS gets End-to-end encryption.

+
  • Chrome moves to release 87.
  • +
  • Explicit Publication of Privacy Practices.
  • +
  • Firefox 83 gets HTTPS-only Mode.
  • +
  • Mozilla seeks consultation on implementing DNS-over-HTTPS.
  • +
  • The comical announcement strategy of the Egregor Ransomware.
  • +
  • Large-scale attacks targeting Epsilon Framework Themes in WordPress.
  • +
  • Cybercrime gang installs hidden e-commerce stores on WordPress sites.
  • +
  • 245,000 Windows systems still vulnerable to BlueKeep RDP bug.
  • +
  • Google's Rich Communication Services is getting E2EE via Signal.
  • +
  • Cicada, a Chinese state-sponsored advanced persistent threat group.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-794-Notes.pdf

+

Hosts: Steve Gibson and Jason Howell

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Ongoing WordPress attack, RCS gets End-to-end encryption.

+
  • Chrome moves to release 87.
  • +
  • Explicit Publication of Privacy Practices.
  • +
  • Firefox 83 gets HTTPS-only Mode.
  • +
  • Mozilla seeks consultation on implementing DNS-over-HTTPS.
  • +
  • The comical announcement strategy of the Egregor Ransomware.
  • +
  • Large-scale attacks targeting Epsilon Framework Themes in WordPress.
  • +
  • Cybercrime gang installs hidden e-commerce stores on WordPress sites.
  • +
  • 245,000 Windows systems still vulnerable to BlueKeep RDP bug.
  • +
  • Google's Rich Communication Services is getting E2EE via Signal.
  • +
  • Cicada, a Chinese state-sponsored advanced persistent threat group.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-794-Notes.pdf

+

Hosts: Steve Gibson and Jason Howell

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Ongoing WordPress attack, RCS gets End-to-end encryption.

+
  • Chrome moves to release 87.
  • +
  • Explicit Publication of Privacy Practices.
  • +
  • Firefox 83 gets HTTPS-only Mode.
  • +
  • Mozilla seeks consultation on implementing DNS-over-HTTPS.
  • +
  • The comical announcement strategy of the Egregor Ransomware.
  • +
  • Large-scale attacks targeting Epsilon Framework Themes in WordPress.
  • +
  • Cybercrime gang installs hidden e-commerce stores on WordPress sites.
  • +
  • 245,000 Windows systems still vulnerable to BlueKeep RDP bug.
  • +
  • Google's Rich Communication Services is getting E2EE via Signal.
  • +
  • Cicada, a Chinese state-sponsored advanced persistent threat group.
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-794-Notes.pdf

+

Hosts: Steve Gibson and Jason Howell

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0794/sn0794.mp3 + 1:44:03 + + + SN 794: Cicada - Ongoing WordPress Attack, RCS Gets End-to-End Encryption + Ongoing WordPress Attack, RCS Gets End-to-End Encryption + Security Now, TWiT, steve gibson, Leo Laporte, cicada china, cicada cyber espionage, rcs e2e, rcs encryption, rcs signal, bluekeep rdp, bluekeep bug, wordpress epsilon hack, wordpress hack, wordpress security, wordpress theme vulnerability + + nonadult + tv-g + IAB19 Steve Gibson + Jason Howell + +
+ + SN 793: SAD DNS - Malicious Android Apps, Ransomware-as-a-Service + SAD DNS - Malicious Android Apps, Ransomware-as-a-Service + full + Tue, 17 Nov 2020 19:29:25 PST + 793 + https://twit.tv/shows/security-now/episodes/793 + https://twit.tv/shows/security-now/episodes/793 + TWiT + Technology + Security + clean + Malicious Android Apps, Ransomware-as-a-Service + Security Now, TWiT, steve gibson, Leo Laporte, android malware, malicious apps, malicious android apps, ransomware as a service, ragnar locker, ryuk, chrome 0-days, patch tuesday, sad dns + Malicious Android apps, ransomware-as-a-service.

+
  • Where do most malicious Android apps come from?
  • +
  • SAD DNS is a revival of the classic DNS cache poisoning attack
  • +
  • How many Ransomware-as-a-Service (RaaS) operations are there?
  • +
  • Ragnar Locker ransomware gang takes out a Facebook ad
  • +
  • Two more new 0-days revealed in Chrome
  • +
  • Last Tuesday, Microsoft fixed 112 known vulnerabilities in Microsoft products
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-793-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Malicious Android apps, ransomware-as-a-service.

+
  • Where do most malicious Android apps come from?
  • +
  • SAD DNS is a revival of the classic DNS cache poisoning attack
  • +
  • How many Ransomware-as-a-Service (RaaS) operations are there?
  • +
  • Ragnar Locker ransomware gang takes out a Facebook ad
  • +
  • Two more new 0-days revealed in Chrome
  • +
  • Last Tuesday, Microsoft fixed 112 known vulnerabilities in Microsoft products
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-793-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Malicious Android apps, ransomware-as-a-service.

+
  • Where do most malicious Android apps come from?
  • +
  • SAD DNS is a revival of the classic DNS cache poisoning attack
  • +
  • How many Ransomware-as-a-Service (RaaS) operations are there?
  • +
  • Ragnar Locker ransomware gang takes out a Facebook ad
  • +
  • Two more new 0-days revealed in Chrome
  • +
  • Last Tuesday, Microsoft fixed 112 known vulnerabilities in Microsoft products
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-793-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0793/sn0793.mp3 + 1:59:10 + + + SN 793: SAD DNS - Malicious Android Apps, Ransomware-as-a-Service + Malicious Android Apps, Ransomware-as-a-Service + Security Now, TWiT, steve gibson, Leo Laporte, android malware, malicious apps, malicious android apps, ransomware as a service, ragnar locker, ryuk, chrome 0-days, patch tuesday, sad dns + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 792: NAT Firewall Bypass - SlipStream NAT Firewall Bypass, MS Police Use Ring Doorbell Cams + NAT Firewall Bypass - SlipStream NAT Firewall Bypass, MS Police Use Ring Doorbell Cams + full + Tue, 10 Nov 2020 18:30:00 PST + 792 + https://twit.tv/shows/security-now/episodes/792 + https://twit.tv/shows/security-now/episodes/792 + TWiT + Technology + Security + clean + SlipStream NAT Firewall Bypass, MS Police Use Ring Doorbell Cams + Security Now, TWiT, steve gibson, Leo Laporte, let's encrypt, Iden Trust, root certificate, chrome, chrome updated, Chrome 0-Day, 0-day, chrome android, chrome android update, Google threat analysis group, google TAG, ransomware, mattel. compal, capcom + SlipStream NAT firewall bypass, MS Police use Ring doorbell cams.

+
  • Let's Encrypt's cross-signed root expires next year
  • +
  • Chrome updates on Windows, macOS, Linux, and Android to remove 0-day vulnerability
  • +
  • Mattel, Compel, Capcom, and Campari fall to ransomware attacks
  • +
  • iOS 14.2 fixes three 0-day vulnerabilities
  • +
  • Introducing the Tianfu Cup: China's version of the Pwn2Own hacker competition
  • +
  • November's Patch Tuesday
  • +
  • The Great Encryption Dilemma hits Europe
  • +
  • Ring Doorbells to be tapped in a trial by local Police
  • +
  • WordPress plugins are a hot mess for security
  • +
  • SlipStream NAT Firewall Bypass
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-792-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ SlipStream NAT firewall bypass, MS Police use Ring doorbell cams.

+
  • Let's Encrypt's cross-signed root expires next year
  • +
  • Chrome updates on Windows, macOS, Linux, and Android to remove 0-day vulnerability
  • +
  • Mattel, Compel, Capcom, and Campari fall to ransomware attacks
  • +
  • iOS 14.2 fixes three 0-day vulnerabilities
  • +
  • Introducing the Tianfu Cup: China's version of the Pwn2Own hacker competition
  • +
  • November's Patch Tuesday
  • +
  • The Great Encryption Dilemma hits Europe
  • +
  • Ring Doorbells to be tapped in a trial by local Police
  • +
  • WordPress plugins are a hot mess for security
  • +
  • SlipStream NAT Firewall Bypass
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-792-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ SlipStream NAT firewall bypass, MS Police use Ring doorbell cams.

+
  • Let's Encrypt's cross-signed root expires next year
  • +
  • Chrome updates on Windows, macOS, Linux, and Android to remove 0-day vulnerability
  • +
  • Mattel, Compel, Capcom, and Campari fall to ransomware attacks
  • +
  • iOS 14.2 fixes three 0-day vulnerabilities
  • +
  • Introducing the Tianfu Cup: China's version of the Pwn2Own hacker competition
  • +
  • November's Patch Tuesday
  • +
  • The Great Encryption Dilemma hits Europe
  • +
  • Ring Doorbells to be tapped in a trial by local Police
  • +
  • WordPress plugins are a hot mess for security
  • +
  • SlipStream NAT Firewall Bypass
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-792-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0792/sn0792.mp3 + 1:54:16 + + + SN 792: NAT Firewall Bypass - SlipStream NAT Firewall Bypass, MS Police Use Ring Doorbell Cams + SlipStream NAT Firewall Bypass, MS Police Use Ring Doorbell Cams + Security Now, TWiT, steve gibson, Leo Laporte, let's encrypt, Iden Trust, root certificate, chrome, chrome updated, Chrome 0-Day, 0-day, chrome android, chrome android update, Google threat analysis group, google TAG, ransomware, mattel. compal, capcom + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 791: Google's Root Program - Google One VPN, WordPress Update Fail, Windows 7 0-Day + Google's Root Program - Google One VPN, WordPress Update Fail, Windows 7 0-Day + full + Tue, 03 Nov 2020 18:00:00 PST + 791 + https://twit.tv/shows/security-now/episodes/791 + https://twit.tv/shows/security-now/episodes/791 + TWiT + Technology + Security + clean + Google One VPN, WordPress Update Fail, Windows 7 0-Day + Security Now, TWiT, steve gibson, Leo Laporte, windows 7, windows 10, Windows Zero Day, Windows 0-day, FreeType flaw, Google, Google One, Google One VPN, Google VPN, Vulnonym, CERT, WordPress, wordpress security, WordPress update, chrome + Google One VPN, WordPress update fail, Windows 7 0-Day.

+
  • A new 0-day in Win7 through Win10
  • +
  • A public service reminder from Microsoft
  • +
  • Google One adding an Android VPN
  • +
  • Vulnonym: Stop the Naming Madness!
  • +
  • WordPress fumbles an important update
  • +
  • Chrome's Root Program
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-791-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Google One VPN, WordPress update fail, Windows 7 0-Day.

+
  • A new 0-day in Win7 through Win10
  • +
  • A public service reminder from Microsoft
  • +
  • Google One adding an Android VPN
  • +
  • Vulnonym: Stop the Naming Madness!
  • +
  • WordPress fumbles an important update
  • +
  • Chrome's Root Program
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-791-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Google One VPN, WordPress update fail, Windows 7 0-Day.

+
  • A new 0-day in Win7 through Win10
  • +
  • A public service reminder from Microsoft
  • +
  • Google One adding an Android VPN
  • +
  • Vulnonym: Stop the Naming Madness!
  • +
  • WordPress fumbles an important update
  • +
  • Chrome's Root Program
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-791-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://pdst.fm/e/chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0791/sn0791.mp3 + 1:45:36 + + + SN 791: Google's Root Program - Google One VPN, WordPress Update Fail, Windows 7 0-Day + Google One VPN, WordPress Update Fail, Windows 7 0-Day + Security Now, TWiT, steve gibson, Leo Laporte, windows 7, windows 10, Windows Zero Day, Windows 0-day, FreeType flaw, Google, Google One, Google One VPN, Google VPN, Vulnonym, CERT, WordPress, wordpress security, WordPress update, chrome + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+ + SN 790: Top 25 Vulnerabilities - Chrome 0-Day, Edge for Linux, WordPress Loginizer + Top 25 Vulnerabilities - Chrome 0-Day, Edge for Linux, WordPress Loginizer + full + Tue, 27 Oct 2020 17:00:00 PST + 790 + https://twit.tv/shows/security-now/episodes/790 + https://twit.tv/shows/security-now/episodes/790 + TWiT + Technology + Security + clean + Chrome 0-Day, Edge for Linux, WordPress Loginizer + Security Now, TWiT, steve gibson, Leo Laporte, Chrome 0-Day, chrome vulnerabilities, chrome exploits, Edge for Linux, Chredge for Linux, WordPress Loginizer, wordpress SQL injection bug, wordpress security, Site isolation Firefox + Chrome 0-Day, Edge for Linux, WordPress Loginizer.

+
  • Top 25 Vulnerabilities
  • +
  • Critical 0-day in Chrome
  • +
  • Chrome 86 is now blocking slippery notifications
  • +
  • Site Isolation coming soon to Firefox
  • +
  • Microsoft's Chredge for Linux
  • +
  • WordPress Loginizer vulnerability
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-790-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Chrome 0-Day, Edge for Linux, WordPress Loginizer.

+
  • Top 25 Vulnerabilities
  • +
  • Critical 0-day in Chrome
  • +
  • Chrome 86 is now blocking slippery notifications
  • +
  • Site Isolation coming soon to Firefox
  • +
  • Microsoft's Chredge for Linux
  • +
  • WordPress Loginizer vulnerability
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-790-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ Chrome 0-Day, Edge for Linux, WordPress Loginizer.

+
  • Top 25 Vulnerabilities
  • +
  • Critical 0-day in Chrome
  • +
  • Chrome 86 is now blocking slippery notifications
  • +
  • Site Isolation coming soon to Firefox
  • +
  • Microsoft's Chredge for Linux
  • +
  • WordPress Loginizer vulnerability
  • +

We invite you to read our show notes at https://www.grc.com/sn/SN-790-Notes.pdf

+

Hosts: Steve Gibson and Leo Laporte

+

Download or subscribe to this show at https://twit.tv/shows/security-now.

+

You can submit a question to Security Now! at the GRC Feedback Page.

+

For 16kbps versions, transcripts, and notes (including fixes), visit Steve's site: grc.com, also the home of the best disk maintenance and recovery utility ever written Spinrite 6.

+

Sponsors:

+]]>
+ + https://chtbl.com/track/E91833/cdn.twit.tv/audio/sn/sn0790/sn0790.mp3 + 1:43:40 + + + SN 790: Top 25 Vulnerabilities - Chrome 0-Day, Edge for Linux, WordPress Loginizer + Chrome 0-Day, Edge for Linux, WordPress Loginizer + Security Now, TWiT, steve gibson, Leo Laporte, Chrome 0-Day, chrome vulnerabilities, chrome exploits, Edge for Linux, Chredge for Linux, WordPress Loginizer, wordpress SQL injection bug, wordpress security, Site isolation Firefox + + nonadult + tv-g + IAB19 Steve Gibson + Leo Laporte + +
+
+
diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index 52ad5c523e189b60c3b551ea828a31127ced3aac..b3179df110347e72faa8aa2fd3e89e93deebd302 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -60,12 +60,12 @@ Fremde Audiodateien - + Rendering Zeichne - + Collecting Podcasts Sammle Podcasts @@ -78,6 +78,51 @@ Kapitel wählen + + BackupButtons + + + Backup done + Backup durchgeführt + + + + + Backup done to + Backup durchgeführt nach + + + + OPML file saved + OPML-File gespeichert + + + + + OPML file saved to + OPML-File gesichert nach + + + + Backup + Backup + + + + Start Backup + Starte Backup + + + + Save to OPML + Speichere als OPML + + + + Start OPML export + Starte OPML-Export + + Chapters @@ -104,6 +149,45 @@ Kapitel + + DataMigration + + + Update done + + + + + + Successfully migrated data to the current version + + + + + Data Migration + + + + + Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong. + + + + + Start Migration + + + + + An error has happened. + + + + + Copy to clipboard + + + Discover @@ -146,51 +230,9 @@ DiscoverExport - Backup done - Backup durchgeführt - - - - - Backup done to - Backup durchgeführt nach - - - - OPML file saved - OPML-File gespeichert - - - - - OPML file saved to - OPML-File gesichert nach - - - Export Exportieren - - - Backup - Backup - - - - Start Backup - Starte Backup - - - - Save to OPML - Speichere als OPML - - - - Start OPML export - Starte OPML-Export - DiscoverFyyd @@ -332,7 +374,7 @@ FeedParserPython - + Error Fehler @@ -412,30 +454,25 @@ Podcast - + Subscribe Subscribe - + Configure Einstellungen - + Feed alternatives Alternative Feeds - + Latest Post Letzte Sendung - - - First Post - Erste Sendung - PodcastEntryItem @@ -831,50 +868,50 @@ harbour-podqast - + New posts available Neue Beiträge verfügbar - + Click to view updates Klicken um Aktualisierungen anzusehen - + New Posts are available. Click to view. Neue Beiträge verfügbar. Klicke zum Betrachten - - + + PodQast message PodQast-Nachricht - + New PodQast message Neue PodQast-Nachricht - + Podcasts imported Podcasts importiert - - + + Podcasts imported from OPML Podcasts vom OPML importiert - + Error Fehler - - + + Audio File not existing Audiodatei existiert nicht diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 07e9007e227644cb3c0d64ab3e282269205210c4..421ae92d401265941dd7a3edc39224cdc9665368 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -60,12 +60,12 @@ Audio externo - + Rendering Generando - + Collecting Podcasts Obteniendo podcasts @@ -78,6 +78,51 @@ + + BackupButtons + + + Backup done + Copia realizada + + + + + Backup done to + Copia realizada en + + + + OPML file saved + Archivo OPML guardado + + + + + OPML file saved to + Archivo OPML guardado en + + + + Backup + Copia de seguridad + + + + Start Backup + Iniciar copia + + + + Save to OPML + Guardar OPML + + + + Start OPML export + Iniciar exportación OPML + + Chapters @@ -104,6 +149,45 @@ capítulos + + DataMigration + + + Update done + + + + + + Successfully migrated data to the current version + + + + + Data Migration + + + + + Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong. + + + + + Start Migration + + + + + An error has happened. + + + + + Copy to clipboard + + + Discover @@ -146,51 +230,9 @@ DiscoverExport - Backup done - Copia realizada - - - - - Backup done to - Copia realizada en - - - - OPML file saved - Archivo OPML guardado - - - - - OPML file saved to - Archivo OPML guardado en - - - Export Exportar - - - Backup - Copia de seguridad - - - - Start Backup - Iniciar copia - - - - Save to OPML - Guardar OPML - - - - Start OPML export - Iniciar exportación OPML - DiscoverFyyd @@ -332,7 +374,7 @@ FeedParserPython - + Error Error @@ -412,30 +454,25 @@ Podcast - + Subscribe Suscribir - + Configure Configurar - + Feed alternatives Alternativas de canal - + Latest Post Última publicación - - - First Post - Primera publicación - PodcastEntryItem @@ -831,50 +868,50 @@ harbour-podqast - + New posts available Nuevas publicaciones disponibles - + Click to view updates Haz clic para ver actualizaciones - + New Posts are available. Click to view. Hay nuevas publicaciones. Haz clic para verlas. - - + + PodQast message Mensaje de PodQast - + New PodQast message Nuevo mensaje de PodQast - + Podcasts imported podcasts importados - - + + Podcasts imported from OPML podcasts importados desde OPML - + Error Error - - + + Audio File not existing El archivo de audio no existe diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 8ba47103bb52d777cf5c38e02702e12e8a89c1aa..2c373b2d13b85f228463e8d01daecf185501255b 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -60,12 +60,12 @@ Fichiers Audios Externes - + Rendering Chargement - + Collecting Podcasts Récupération des Podcasts @@ -78,6 +78,51 @@ + + BackupButtons + + + Backup done + Sauvegarde terminée + + + + + Backup done to + Sauvegarde réalisée dans + + + + OPML file saved + Fichier OPML sauvegardé + + + + + OPML file saved to + Fichier OPML sauvegardé dans + + + + Backup + Faire une sauvegarde + + + + Start Backup + Démarrage de la sauvegarde dans + + + + Save to OPML + Sauvegarder au format OPML + + + + Start OPML export + Démarrage de l'export OPML dans + + Chapters @@ -104,6 +149,45 @@ chapitres + + DataMigration + + + Update done + + + + + + Successfully migrated data to the current version + + + + + Data Migration + + + + + Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong. + + + + + Start Migration + + + + + An error has happened. + + + + + Copy to clipboard + + + Discover @@ -146,51 +230,9 @@ DiscoverExport - Backup done - Sauvegarde terminée - - - - - Backup done to - Sauvegarde réalisée dans - - - - OPML file saved - Fichier OPML sauvegardé - - - - - OPML file saved to - Fichier OPML sauvegardé dans - - - Export Exporter - - - Backup - Faire une sauvegarde - - - - Start Backup - Démarrage de la sauvegarde dans - - - - Save to OPML - Sauvegarder au format OPML - - - - Start OPML export - Démarrage de l'export OPML dans - DiscoverFyyd @@ -333,7 +375,7 @@ FeedParserPython - + Error Erreur @@ -413,30 +455,25 @@ Podcast - + Subscribe S'abonner - + Configure Configurer - + Feed alternatives Flux Alternatifs - + Latest Post Derniers Articles - - - First Post - Premier Article - PodcastEntryItem @@ -834,50 +871,50 @@ harbour-podqast - + New posts available Nouveaux articles disponibles - + Click to view updates Cliquez pour voir les mises à jour - + New Posts are available. Click to view. De nouveaux articles sont diponibles. Cliquez pour voir. - - + + PodQast message Message PodQast - + New PodQast message Nouveau message PodQast - + Podcasts imported Podcasts importés - - + + Podcasts imported from OPML Podcasts importés depuis OPML - + Error Erreur - - + + Audio File not existing Le fichier audio n'existe pas diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index dac6c92730e19ac71b920a0d8aa9010bce3c51f0..85773cb65dac61ee4d9694b0420db2b09f396468 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -60,12 +60,12 @@ Externt ljud - + Rendering Rendering - + Collecting Podcasts Hämtar poddar @@ -78,6 +78,51 @@ Välj avsnitt + + BackupButtons + + + Backup done + Säkerhetskopiering klar + + + + + Backup done to + Säkerhetskopiering klar till + + + + OPML file saved + OPML-fil sparad + + + + + OPML file saved to + OPML-fil sparad i + + + + Backup + Säkerhetskopiera + + + + Start Backup + Starta säkerhetskopiering + + + + Save to OPML + Spara till OPML + + + + Start OPML export + Starta OPML-export + + Chapters @@ -104,6 +149,45 @@ avsnitt + + DataMigration + + + Update done + + + + + + Successfully migrated data to the current version + + + + + Data Migration + + + + + Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong. + + + + + Start Migration + + + + + An error has happened. + + + + + Copy to clipboard + + + Discover @@ -146,51 +230,9 @@ DiscoverExport - Backup done - Säkerhetskopiering klar - - - - - Backup done to - Säkerhetskopiering klar till - - - - OPML file saved - OPML-fil sparad - - - - - OPML file saved to - OPML-fil sparad i - - - Export Exportera - - - Backup - Säkerhetskopiera - - - - Start Backup - Starta säkerhetskopiering - - - - Save to OPML - Spara till OPML - - - - Start OPML export - Starta OPML-export - DiscoverFyyd @@ -332,7 +374,7 @@ FeedParserPython - + Error Fel @@ -412,30 +454,25 @@ Podcast - + Subscribe Prenumerera - + Configure Konfigurera - + Feed alternatives Flödesalternativ - + Latest Post Senaste posten - - - First Post - Första posten - PodcastEntryItem @@ -833,50 +870,50 @@ harbour-podqast - + New posts available Nya poster tillgängliga - + Click to view updates Tryck för att visa uppdateringar - + New Posts are available. Click to view. Det finns nya poster. Tryck för att visa. - - + + PodQast message PodQast-meddelande - + New PodQast message Nytt PodQast-meddelande - + Podcasts imported poddar importerade - - + + Podcasts imported from OPML poddar importerade från OPML - + Error Fel - - + + Audio File not existing Ljudfilen finns inte diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index a595dee221a28441279befeda26e0d20d9a1fd87..d8964403e532fd0c262327c783e41236653345a2 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -60,12 +60,12 @@ 外部音频 - + Rendering 更新 - + Collecting Podcasts 收藏的播客 @@ -78,6 +78,51 @@ + + BackupButtons + + + Backup done + 备份完成 + + + + + Backup done to + 备份完成 + + + + OPML file saved + 已保存 OPML 文件 + + + + + OPML file saved to + OPML 文件保存到 + + + + Backup + 备份 + + + + Start Backup + 开始备份 + + + + Save to OPML + 保存到 OPML + + + + Start OPML export + 开始导出 OPML + + Chapters @@ -104,6 +149,45 @@ 章节 + + DataMigration + + + Update done + + + + + + Successfully migrated data to the current version + + + + + Data Migration + + + + + Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong. + + + + + Start Migration + + + + + An error has happened. + + + + + Copy to clipboard + + + Discover @@ -146,51 +230,9 @@ DiscoverExport - Backup done - 备份完成 - - - - - Backup done to - 备份完成 - - - - OPML file saved - 已保存 OPML 文件 - - - - - OPML file saved to - OPML 文件保存到 - - - Export 导出 - - - Backup - 备份 - - - - Start Backup - 开始备份 - - - - Save to OPML - 保存到 OPML - - - - Start OPML export - 开始导出 OPML - DiscoverFyyd @@ -332,7 +374,7 @@ FeedParserPython - + Error 错误 @@ -412,30 +454,25 @@ Podcast - + Subscribe 订阅 - + Configure 配置 - + Feed alternatives 可选订阅溜 - + Latest Post 最新的内容 - - - First Post - 最早的内容 - PodcastEntryItem @@ -831,50 +868,50 @@ harbour-podqast - + New posts available 有可获取的新内容 - + Click to view updates 点击以查看更新 - + New Posts are available. Click to view. 有可用新内容,点击以查看。 - - + + PodQast message PodQast 消息 - + New PodQast message 新 PodQast 消息 - + Podcasts imported 播客已导入 - - + + Podcasts imported from OPML 已从 OPML 导入播客 - + Error 错误 - - + + Audio File not existing 音频文件不存在 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index e39368a1bcfb557dae3fa863082327f0be3d3d2c..efc4d8af1a09f569ea72c85449e4b28f455e92a9 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -60,12 +60,12 @@ - + Rendering - + Collecting Podcasts @@ -78,6 +78,51 @@ + + BackupButtons + + + Backup done + + + + + + Backup done to + + + + + OPML file saved + + + + + + OPML file saved to + + + + + Backup + + + + + Start Backup + + + + + Save to OPML + + + + + Start OPML export + + + Chapters @@ -105,90 +150,87 @@ - Discover + DataMigration - - Discover + + Update done - - Search on gpodder.net + + + Successfully migrated data to the current version - - Search on fyyd.de + + Data Migration - - Tags... + + Because the data format changed, podqast will need to migrate your data to the new format. You can use one of the buttons below to create a backup of your data for recovery, if anything goes wrong. - - Url... + + Start Migration - - Import... + + An error has happened. - - Export... + + Copy to clipboard - DiscoverExport - - - Backup done - - + Discover - - - Backup done to + + Discover - - OPML file saved + + Search on gpodder.net - - - OPML file saved to + + Search on fyyd.de - - Export + + Tags... - - Backup + + Url... - - Start Backup + + Import... - - Save to OPML + + Export... + + + DiscoverExport - - Start OPML export + + Export @@ -332,7 +374,7 @@ FeedParserPython - + Error @@ -412,30 +454,25 @@ Podcast - + Subscribe - + Configure - + Feed alternatives - + Latest Post - - - First Post - - PodcastEntryItem @@ -831,50 +868,50 @@ harbour-podqast - + New posts available - + Click to view updates - + New Posts are available. Click to view. - - + + PodQast message - + New PodQast message - + Podcasts imported - - + + Podcasts imported from OPML - + Error - - + + Audio File not existing