diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000000000000000000000000000000000000..a8618295f083056d8d36e09f74baa3afae6b8dc0
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,5 @@
+[pytest]
+log_cli = 1
+log_cli_level = Info
+testpaths =
+ test
\ No newline at end of file
diff --git a/python/podcast/archive.py b/python/podcast/archive.py
index 0e2d649378430b9b9486c06abf7fc1626baa8079..e432a4168c79fd58c04ac1f8c65760390f10e321 100644
--- a/python/podcast/archive.py
+++ b/python/podcast/archive.py
@@ -1,29 +1,45 @@
"""
Archive of listened poposts
"""
-
import sys
import time
+from typing import List, Iterator
sys.path.append("../")
from podcast.singleton import Singleton
from podcast.factory import Factory
+from podcast.podpost import Podpost, PodpostFactory
archivename = "the_archive"
+class ArchiveEntry:
+ insert_date: float # unused yet
+ podpost: str
+
+ @classmethod
+ def of_podpost(cls, postid: str):
+ if not type(postid) == str:
+ raise ValueError("did not get the id of a post")
+ archive_entry = ArchiveEntry()
+ archive_entry.podpost = postid
+ archive_entry.insert_date = time.time()
+ return archive_entry
+
+
class Archive:
"""
The podcast Archive. It has a list of podposts
"""
+ _archive_entries: List[ArchiveEntry]
def __init__(self):
"""
Initialization
"""
- self.podposts = [] # Id list of posposts
+ self._archive_entries = [] # Id list of posposts
def save(self):
"""
@@ -33,33 +49,42 @@ class Archive:
store = Factory().get_store()
store.store(archivename, self)
- def insert(self, podpost):
+ def insert(self, podpost: str):
"""
Insert an podost
"""
-
- if podpost not in self.podposts:
- self.podposts.insert(0, podpost)
- post = Factory().get_podpost(podpost)
- if post.insert_date == None:
- post.insert_date = time.time()
- post.save()
+ if type(podpost) != str:
+ raise ValueError("please supply the id of the post")
+ if podpost not in [e.podpost for e in self._archive_entries]:
+ self._archive_entries.insert(0, ArchiveEntry.of_podpost(podpost))
self.save()
- def get_podposts(self):
+ def get_podposts(self, sorted_by_date=False):
"""
get the list of podposts
"""
-
- for podpost in self.podposts:
- yield podpost
+ if not sorted_by_date:
+ for archive_entry in self._archive_entries:
+ yield archive_entry.podpost
+ else:
+ for archive_entry in sorted(self._archive_entries, key=lambda e: e.insert_date, reverse=True):
+ yield archive_entry.podpost
+
+ def get_podpost_objects(self, url_filter=None, filter_favorite=False) -> Iterator[Podpost]:
+ for id in self.get_podposts():
+ entry: Podpost = PodpostFactory().get_podpost(id)
+ if entry:
+ if ((not url_filter or (url_filter and entry.podurl == url_filter)) and
+ (not filter_favorite or entry.favorite)
+ ):
+ yield entry
def remove_podpost(self, podpost):
"""
Remove a podpost from archive
"""
- self.podposts.remove(podpost)
+ self._archive_entries = [entry for entry in self._archive_entries if entry.podpost != podpost]
class ArchiveFactory(metaclass=Singleton):
diff --git a/python/podcast/constants.py b/python/podcast/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a2c90bb0f3ddc1c2aea65feea6565d8de19136c
--- /dev/null
+++ b/python/podcast/constants.py
@@ -0,0 +1,61 @@
+import os
+import pyotherside
+from typing import Optional
+
+from podcast import util
+from podcast.singleton import Singleton
+
+
+class Constants(metaclass=Singleton):
+ """
+ manages constants supplied by qml like süecific paths etc
+ """
+ favorites_home: Optional[str]
+ media_home: Optional[str]
+ external_home: Optional[str]
+
+ object: Optional[object]
+
+ def __init__(self):
+ self.object = None
+ self.media_home = None
+ self.favorites_home = None
+ self.external_home = None
+
+
+ def init_from_qml(self, object):
+ """
+ Get QML object
+ """
+ if self.object:
+ return
+ 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")
+ )
+ if os.path.exists(xdg_media_home):
+ os.rename(xdg_media_home, self.media_home)
+
+ if self.get_val("allowExtVal") or self.get_val("allowFavVal"):
+ util.make_directory(self.media_home)
+ util.make_directory(self.favorites_home)
+ util.make_directory(self.external_home)
+ epath = os.path.join(self.external_home, ".nomedia")
+ open(epath, "a").close()
+
+ pyotherside.send("objectLoaded")
+
+ def get_val(self, valname):
+ """
+ Return property value by valname
+ """
+
+ try:
+ return getattr(self.object, valname)
+ except:
+ return None
\ No newline at end of file
diff --git a/python/podcast/data_migration.py b/python/podcast/data_migration.py
index a7bcdbc55e720a2e2ef1180783b916256d1cc490..b103050dd73d5fa7e644f8e7f65d0eede759a442 100644
--- a/python/podcast/data_migration.py
+++ b/python/podcast/data_migration.py
@@ -1,11 +1,14 @@
+import datetime
import logging
import os
+from typing import List
+
import pyotherside
from podcast.factory import Factory
from podcast.podcast import PodcastFactory, Podcast
from podcast.podcastlist import PodcastListFactory
-from podcast.podpost import Podpost
+from podcast.podpost import Podpost, PodpostFactory
logger = logging.getLogger(__name__)
@@ -38,9 +41,10 @@ def run_migrations():
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))
+ migrate_podpost_v0_v1(PodpostFactory().get_podpost(entry_id))
i += 1
pyotherside.send("migrationProgress", i)
+ migrate_archive_v0_v1()
set_versionnumber(1)
pyotherside.send("migrationDone")
@@ -64,21 +68,45 @@ def get_versionfile_path():
return os.path.join(Factory().data_home, "dataversion")
+def migrate_archive_v0_v1():
+ """
+ migrates the id list to ArchiveEntry
+ """
+ from podcast.archive import ArchiveFactory, ArchiveEntry, Archive
+ import datetime
+ archive: Archive = ArchiveFactory().get_archive()
+ if hasattr(archive, "podposts") and len(archive.podposts) > 0 and type(archive.podposts) is not ArchiveEntry:
+ old_archive: List[str] = archive.podposts
+ archive._archive_entries = []
+ for postid in old_archive:
+ archive_entry = ArchiveEntry()
+ post = PodpostFactory().get_podpost(postid)
+ if not post:
+ continue
+ if hasattr(post, "insert_date") and post.insert_date:
+ archive_entry.insert_date = post.insert_date
+ else:
+ archive_entry.insert_date = datetime.datetime.now()
+ archive_entry.podpost = postid
+ archive._archive_entries.append(post)
+ del archive.podposts
+
+
def migrate_podpost_v0_v1(self: Podpost):
- if hasattr(self, "entry"):
- if not hasattr(self, "isaudio"):
- self.isaudio = False
- if not hasattr(self, "length"):
- self.length = 0
- if not self.isaudio:
- self.init(self.entry, self.logo_url, self.podurl)
- self.save()
+ if not hasattr(self, "insert_date") or self.insert_date is None:
+ self.insert_date = datetime.datetime.now().timestamp()
+ if not hasattr(self, "isaudio"):
+ self.isaudio = False
+ if not hasattr(self, "length"):
+ self.length = 0
+ if hasattr(self, "entry") and not self.isaudio:
+ self.init(self.entry, self.logo_url, self.podurl)
+ PodpostFactory().persist(self)
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:
@@ -90,10 +118,10 @@ def migrate_podcast_v0_v1(self: Podcast):
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)
+ p2 = PodpostFactory().get_podpost(post.id)
if p2:
post = p2
- post.save()
+ PodpostFactory().persist(post)
entry_ids_new_to_old.append(post.id)
self._set_alt_feeds(self.feed)
else:
@@ -105,7 +133,7 @@ def migrate_podcast_v0_v1(self: Podcast):
self.feed = None
if old_entry_count == len(entry_ids_new_to_old):
logger.info("migration done")
- self.save()
+ PodcastFactory().persist(self)
else:
logger.error("Could not complete migration. old count: %d, new: %d", old_entry_count,
len(entry_ids_new_to_old))
diff --git a/python/podcast/external.py b/python/podcast/external.py
index d2f3ccdf3f16ecc894820e2c10ce9e16e3cfdf59..1557c753977fd0d726708d692441568ef2c9f45a 100644
--- a/python/podcast/external.py
+++ b/python/podcast/external.py
@@ -6,6 +6,8 @@ import sys
import time
import pyotherside
+from podcast.podpost import PodpostFactory
+
sys.path.append("../")
from podcast.singleton import Singleton
@@ -44,7 +46,7 @@ class External:
post = Factory().get_podpost(podpost)
if post.insert_date == None:
post.insert_date = time.time()
- post.save()
+ PodpostFactory().persist(post)
self.save()
pyotherside.send("got %d entries" % len(self.podposts))
@@ -58,6 +60,10 @@ class External:
for podpost in self.podposts:
yield podpost
+ def get_podposts_objects(self):
+ for post_id in self.get_podposts():
+ yield PodpostFactory().get_podpost(post_id)
+
def remove_podpost(self, podpost):
"""
Remove a podpost from archive
diff --git a/python/podcast/factory.py b/python/podcast/factory.py
index 15d7dca4e3f04cff5b5e2083035fb8cafa562878..0f0053ffddf0b8828e24e8a961acc87097717723 100644
--- a/python/podcast/factory.py
+++ b/python/podcast/factory.py
@@ -11,30 +11,22 @@ import os
sys.path.append("../")
from podcast.singleton import Singleton
-from podcast.cache import Cache
from podcast.store import Store
from podcast import util
-import feedparser
-import pyotherside
-
-class Factory(metaclass=Singleton):
- """
- Factory for
- """
+class BaseFactory(metaclass=Singleton):
data_home: str
+ store: Store
def __init__(self, progname="harbour-podqast"):
"""
Initialization
- feedcache: cache for feeds
store: store for feeds
"""
self.queue = None
- self.feedcache = Cache()
self.object = None
self._set_paths(progname)
@@ -53,7 +45,6 @@ class Factory(metaclass=Singleton):
util.make_directory(self.afilepath)
self.store = Store(storepath)
- self.podpostcache = Cache(limit=500)
def _set_paths(self, progname):
home = os.path.expanduser("~")
@@ -71,100 +62,13 @@ class Factory(metaclass=Singleton):
self.cache_home = os.path.join(xdg_cache_home, progname)
- def init_from_qml(self, object):
- """
- Get QML object
- """
-
- try:
- if self.object:
- return
- except:
- 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")
- )
- if os.path.exists(xdg_media_home):
- os.rename(xdg_media_home, self.media_home)
-
- if self.get_val("allowExtVal") or self.get_val("allowFavVal"):
- util.make_directory(self.media_home)
- util.make_directory(self.favorites_home)
- util.make_directory(self.external_home)
- epath = os.path.join(self.external_home, ".nomedia")
- open(epath, "a").close()
-
- pyotherside.send("objectLoaded")
-
- def get_val(self, valname):
- """
- Return property value by valname
- """
-
- try:
- return getattr(self.object, valname)
- except:
- return None
-
- def get_feed(self, index):
- """
- get a feed object by index
- index: index of feed
- """
-
- feed = self.feedcache.get(index)
- if not feed:
- feed = self.store.get(index)
- if not feed:
- feed = feedparser.parse(index)
- if feed.bozo != 0:
- pyotherside.send("get_feed: error in feed parsing")
- return None
- self.feedcache.store(index, feed)
-
- return feed
-
- def safe_feed(self, index):
- """
- Safe feed to disk
- """
-
- feed = self.get_feed(index)
- if feed:
- self.store.store(index, feed)
-
- def delete_feed(self, index):
- """
- Delete file from disk
- """
-
- self.store.delete(index)
-
- def get_podpost(self, index):
- """
- Get a podpost from cache or store
- """
-
- podpost = self.podpostcache.get(index)
- if not podpost:
- podpost = self.store.get(index)
- if podpost:
- self.podpostcache.store(index, podpost)
- else:
- return None
-
- return podpost
+class Factory(BaseFactory):
+ """
+ Factory for
+ """
- def delete_podpost(self, index):
- self.podpostcache.delete(index)
- self.store.delete(index)
+ def __init__(self):
+ super().__init__()
def get_store(self):
"""
diff --git a/python/podcast/favorite.py b/python/podcast/favorite.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b66df281eb8e0c2eeba0845118ad1da09b7fe4c
--- /dev/null
+++ b/python/podcast/favorite.py
@@ -0,0 +1,51 @@
+def get_queue_favorites(podurl=None):
+ """
+ Return a list of all favorites posts from queue
+ """
+
+ from podcast.queue import QueueFactory
+
+ return list(QueueFactory().get_favorites(podurl))
+
+
+
+def append_archive_favorites(posts, podurl=None):
+ """
+ append archive favorites to posts list
+ """
+
+ from podcast.archive import ArchiveFactory
+
+ archive = ArchiveFactory().get_archive()
+ return list(archive.get_podpost_objects(url_filter=podurl,filter_favorite=True))
+
+
+
+def get_favorites(podurl=None):
+ """
+ Test
+ """
+
+ posts = get_queue_favorites(podurl)
+ posts = append_archive_favorites(posts, podurl=podurl)
+ yield from sorted(posts, key=lambda p: p.insert_date, reverse=True)
+
+
+def get_favorite_podcast_stats():
+ """
+ """
+
+ entries = {}
+
+ posts = get_queue_favorites()
+ posts = append_archive_favorites(posts)
+ for p in posts:
+ if p.podurl in entries:
+ entries[p.podurl]["count"] += 1
+ else:
+ if p.logo_url:
+ logo_url = p.logo_url
+ else:
+ logo_url = "../../images/podcast.png"
+ entries[p.podurl] = {"count": 1, "logo_url": logo_url}
+ return entries
diff --git a/python/podcast/inbox.py b/python/podcast/inbox.py
index 41cf20f9fddc37a8209ce085ecdaaa93de4ef267..30f8cf03d4699273e35d19cd5b26898b4d477033 100644
--- a/python/podcast/inbox.py
+++ b/python/podcast/inbox.py
@@ -5,6 +5,8 @@ The inbox queue. This also uses a Factory for instancing
import sys
import time
+from podcast.podpost import PodpostFactory, Podpost
+
sys.path.append("../")
from podcast.singleton import Singleton
@@ -44,10 +46,10 @@ class Inbox:
if podpost not in self.podposts:
self.podposts.insert(0, podpost)
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
if post.insert_date == None:
post.insert_date = time.time()
- post.save()
+ PodpostFactory().persist(post)
pyotherside.send("Inbox element saved %d" % len(self.podposts))
self.save()
@@ -59,6 +61,13 @@ class Inbox:
for podpost in self.podposts:
yield podpost
+ def get_podposts_objects(self, podurl = None):
+ for post_id in self.get_podposts():
+ post:Podpost = PodpostFactory().get_podpost(post_id)
+ if not podurl or (podurl and post.podurl == podurl):
+ yield post
+
+
def remove(self, podpost):
"""
Remove a podpost from archive
@@ -140,3 +149,4 @@ class InboxFactory(metaclass=Singleton):
return self.inbox
return self.inbox
+
diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py
index 424d634dbe7583eae992c26ffa21d8246ded099c..1889da8533865eec6aa551e22b289eb8fd953c01 100644
--- a/python/podcast/podcast.py
+++ b/python/podcast/podcast.py
@@ -3,18 +3,17 @@ Podcast
"""
import sys
-from typing import List, Dict, Optional, Generator
+from typing import List, Dict, Optional, Iterator
from feedparser import FeedParserDict
-from podcast.factory import Factory
+from podcast.factory import BaseFactory
sys.path.append("/usr/share/podqast/python")
-from podcast.singleton import Singleton
from podcast.cache import Cache
from podcast.store import Store
-from podcast.podpost import Podpost
+from podcast.podpost import Podpost, PodpostFactory
from podcast import util
from email.utils import mktime_tz, parsedate_tz
from calendar import timegm
@@ -112,10 +111,7 @@ class Podcast:
logger.debug("fetching episodes for %s", self.title)
- if self.logo_path:
- image = self.logo_path
- else:
- image = self.logo_url
+ image = self.logo()
old_first_id = self.entry_ids_old_to_new[-1] if len(self.entry_ids_old_to_new) > 0 else ""
@@ -138,19 +134,26 @@ class Podcast:
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
+ PodpostFactory().persist_bulk(new_posts)
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()
+ PodcastFactory().persist(self)
return new_posts
+ def logo(self):
+ if self.logo_path:
+ image = self.logo_path
+ else:
+ image = self.logo_url
+ return image
+
def __set_data_from_feed(self, feed):
logger.debug("setting instance values from feed")
self.__set_times_from_feeds(feed)
@@ -197,17 +200,13 @@ class Podcast:
description = description[:160] + "..."
self.download_icon()
- if self.logo_path:
- image = self.logo_path
- else:
- image = self.logo_url
return {
"url": self.url,
"title": title,
"description": description,
"descriptionfull": descriptionfull,
- "logo_url": image,
+ "logo_url": self.logo(),
}
def _set_alt_feeds(self, feed):
@@ -230,19 +229,17 @@ class Podcast:
"""
Get an entry from podcast by id
"""
- from podcast.factory import Factory
- entry = Factory().get_podpost(id)
- return entry
+ return PodpostFactory().get_podpost(id)
- def get_entries(self) -> Generator[dict, None, None]:
+
+ def get_entries(self) -> Iterator[Dict]:
"""
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()
+ for post in PodpostFactory().get_podcast_posts(self):
+ yield post.get_data()
def get_latest_entry(self) -> Optional[Podpost]:
"""
@@ -254,22 +251,7 @@ class Podcast:
else:
return None
- def save(self):
- """
- Save this element
- """
- self.download_icon()
- PodcastFactory().store.store(self.url, self)
- PodcastFactory().podcastcache.store(self.url, self)
-
- def delete(self):
- """
- Remove Item from store
- """
-
- PodcastFactory().store.delete(self.url)
- PodcastFactory().podcastcache.delete(self.url)
-
+ # todo handle download icon during init
def download_icon(self):
"""
Download icon
@@ -318,7 +300,7 @@ class Podcast:
pyotherside.send("refreshPost", None)
return []
- self.save()
+ PodcastFactory().persist(self)
move = self.move
if move < 0:
@@ -355,7 +337,7 @@ class Podcast:
self.move = params["move"]
self.autolimit = params["autolimit"]
self.playrate = params["playrate"]
- self.save()
+ PodcastFactory().persist(self)
def get_params(self):
"""
@@ -386,45 +368,16 @@ class Podcast:
}
-class PodcastFactory(metaclass=Singleton):
+class PodcastFactory(BaseFactory):
"""
Helping Factory
"""
store: Store
podcastcache: Cache
- def __init__(self, progname="harbour-podqast"):
- """
- Initialization
- """
-
- self.podcastcache = Cache(limit=50)
- 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)
-
- if "PODQAST_HOME" in os.environ:
- home = os.environ["PODQAST_HOME"]
- self.data_home = self.config_home = self.cache_home = home
-
- self.iconpath = os.path.join(self.data_home, "icons")
- storepath = os.path.join(self.data_home, "store")
- util.make_directory(self.data_home)
- util.make_directory(storepath)
- util.make_directory(self.iconpath)
-
- self.store = Store(storepath)
+ def __init__(self):
+ super().__init__()
+ self.podcastcache = Cache(50)
def get_podcast(self, url, preview: bool = False) -> Optional[Podcast]:
"""
@@ -449,11 +402,18 @@ class PodcastFactory(metaclass=Singleton):
logger.debug("PodcastFactory: from cache %s", url)
return podcast
- def remove_podcast(self, url):
+ def remove_podcast(self, url: str):
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)
+ PodpostFactory().delete_all_from_podcast(to_delete)
self.podcastcache.delete(url)
self.store.delete(url)
+
+ def persist(self, podcast:Podcast):
+ self.store.store(podcast.url,podcast)
+ self.podcastcache.store(podcast.url,podcast)
+
+ def delete(self, podcast:Podcast):
+ self.store.delete(podcast.url)
+ self.podcastcache.delete(podcast.url)
diff --git a/python/podcast/podcastlist.py b/python/podcast/podcastlist.py
index 5fe15d623e6d0fac922cfc4454a9c730a95b195a..308375244729fbdd7221b0493d0868be6431410b 100644
--- a/python/podcast/podcastlist.py
+++ b/python/podcast/podcastlist.py
@@ -3,13 +3,13 @@ List of subscribed podcasts
"""
import sys
-from typing import List
+from typing import List, Iterator
sys.path.append("../")
from podcast.singleton import Singleton
from podcast.factory import Factory
-from podcast.podcast import PodcastFactory
+from podcast.podcast import PodcastFactory, Podcast
from podcast import util
from podcast import gpodder_import
import pyotherside
@@ -45,7 +45,7 @@ class PodcastList:
if podcast:
if podcast.title:
podcast.subscribed = True
- podcast.save()
+ PodcastFactory().persist(podcast)
if url not in self.podcasts:
self.podcasts.append(url)
self.save()
@@ -63,15 +63,19 @@ class PodcastList:
pc.delete()
self.save()
- def get_podcasts(self) -> str:
+ def get_podcasts(self) -> Iterator[str]:
"""
return the list of subscribed podcasts
"""
- for podcast in self.podcasts:
- pc = PodcastFactory().get_podcast(podcast)
+ for podcast in self.get_podcasts_objects():
+ yield podcast.url
+
+ def get_podcasts_objects(self, subscribed_only=True) -> Iterator[Podcast]:
+ for podcast_url in self.podcasts:
+ pc = PodcastFactory().get_podcast(podcast_url)
if pc:
- if hasattr(pc, "subscribed") and pc.subscribed:
- yield podcast
+ if not subscribed_only or hasattr(pc, "subscribed") and pc.subscribed:
+ yield pc
def refresh(self, moveto, limit=0):
"""
@@ -80,16 +84,15 @@ class PodcastList:
pllen = len(self.podcasts)
count = 0
- for podcast in self.podcasts:
- pc = PodcastFactory().get_podcast(podcast)
- if pc:
+ for podcast in self.get_podcasts_objects(subscribed_only=False):
+ if podcast:
try:
- if pc.subscribed:
- for post in pc.refresh(moveto, limit):
+ if podcast.subscribed:
+ for post in podcast.refresh(moveto, limit):
yield post
except:
logger.exception(
- "podcast refresh failed for %s." % pc.title
+ "podcast refresh failed for %s." % podcast.title
)
count += 1
diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py
index d59b89d4545c27ae2fc8b8d18d5a46c3e1296734..d01ad603fe6508f1ccf866f498081e3e168ffd18 100644
--- a/python/podcast/podpost.py
+++ b/python/podcast/podpost.py
@@ -1,9 +1,12 @@
"""
Podpost: a single podcast post
"""
-
+import datetime
import sys
+from podcast.cache import Cache
+from podcast.constants import Constants
+
sys.path.append("../")
import hashlib
@@ -12,13 +15,12 @@ from urllib.parse import urlparse
import html2text
from email.utils import mktime_tz, parsedate_tz
from calendar import timegm
-from typing import Optional, List
+from typing import Optional, List, Iterator
-from podcast.factory import Factory
+from podcast.factory import Factory, BaseFactory
from podcast.util import s_to_hr, tx_to_s, dl_from_url, dl_from_url_progress
from podcast.util import delete_file, format_full_date, s_to_year, make_symlink
from podcast.util import get_audio_meta, get_audio_tech, down_audio_icon
-from podcast.queue import QueueFactory
import pyotherside
import time
import re
@@ -41,7 +43,7 @@ class Podpost:
title: str
percentage: float
position: int
- isaudio: bool
+ isaudio: bool # if the post comes from the external folder
id: str
state: int
favorite: bool
@@ -51,13 +53,12 @@ class Podpost:
href: str
duration: Optional[int]
length: int
- published: Optional[float]
- insert_date: object
+ published: Optional[float] # when the post was published according to feed
+ insert_date: float # when we entered this post into our db
link: Optional[str]
type: str
- podurl: str
+ podurl: str # the feed url
chapters: List
- version: int
def __init__(self, entry, podurl, logo_url, data=None, isaudio=False):
"""
@@ -75,6 +76,7 @@ class Podpost:
self.length = 0
self.type = ""
self.href = ""
+ self.insert_date = datetime.datetime.now().timestamp()
if isaudio:
self.init_file_entry(data, logo_url, podurl)
@@ -93,7 +95,6 @@ class Podpost:
self.id = data["id"]
self.length = int(data["length"])
self.published = 0
- self.insert_date = None
self.link = None
self.type = data["mime"]
self.podurl = podurl
@@ -107,7 +108,6 @@ class Podpost:
if len(logo_url) > 0:
if logo_url[0] == "/":
self.logo_path = logo_url
- self.insert_date = None
try:
self.published = timegm(entry.published_parsed)
except:
@@ -225,11 +225,11 @@ class Podpost:
queue = QueueFactory().get_queue()
if position:
self.position = position
- self.save()
+ PodpostFactory().persist(self)
if self.duration:
if self.position > 0:
add = ""
- if self.state == PLAY and self.id == queue.podposts[0]:
+ if self.state == PLAY and self.id == queue.get_top_id():
add = " playing"
else:
add = " remaining"
@@ -250,7 +250,7 @@ class Podpost:
from podcast.queue import QueueFactory
queue = QueueFactory().get_queue()
- inqueue = self.id in queue.podposts
+ inqueue = self.id in queue.get_podposts()
timestring = self.get_hr_time()
@@ -262,14 +262,7 @@ class Podpost:
section = format_full_date(self.published)
date = self.published
fdate = s_to_year(date)
- if self.insert_date:
- asection = format_full_date(self.insert_date)
- adate = self.insert_date
- else:
- asection = ""
- adate = 0
- logger.debug("adate, asection %s, %s", adate, asection)
- logger.debug("date, section %s, %s", date, section)
+ asection = format_full_date(self.insert_date)
if self.duration:
duration = s_to_hr(self.duration)
else:
@@ -300,7 +293,6 @@ class Podpost:
"date": date,
"fdate": fdate,
"section": section,
- "adate": adate,
"asection": asection,
"length": self.length,
"duration": duration,
@@ -312,18 +304,6 @@ class Podpost:
"haschapters": haschapters,
}
- def save(self):
- """
- Save this element. If self.insert_date = None: set date
- """
-
- logger.info("storing podpost %s", self.id)
- if not self.logo_path:
- self.download_icon()
- store = Factory().get_store()
- store.store(self.id, self)
- logger.debug("...done")
-
def download_audio(self):
"""
Download the audio stuff
@@ -369,7 +349,7 @@ class Podpost:
self.percentage = 0
self.file_path = file_path
- self.save()
+ PodpostFactory().persist(self)
def download_icon(self):
"""
@@ -427,7 +407,7 @@ class Podpost:
"""
self.position = position
- self.save()
+ PodpostFactory().persist(self)
@property
def get_position(self):
@@ -444,7 +424,7 @@ class Podpost:
if not self.duration:
self.duration = duration
- self.save()
+ PodpostFactory().persist(self)
def pause(self, position):
"""
@@ -454,7 +434,7 @@ class Podpost:
if position >= 0:
self.position = position
self.state = PAUSE
- self.save()
+ PodpostFactory().persist(self)
def stop(self, position):
"""
@@ -464,7 +444,7 @@ class Podpost:
if position >= 0:
self.position = position
self.state = STOP
- self.save()
+ PodpostFactory().persist(self)
@property
def get_play_state(self):
@@ -500,13 +480,14 @@ class Podpost:
delete_file(self.file_path)
self.percentage = 0
self.file_path = None
- self.save()
+ PodpostFactory().persist(self)
def toggle_fav(self, do_download=False):
"""
toggle favorite flag (and create one if it not exists)
return: bool if favorite
"""
+ from podcast.queue import QueueFactory
try:
self.favorite = not self.favorite
@@ -523,7 +504,7 @@ class Podpost:
)
tnb = re.sub(r"(?u)[^-\w.]", "", tn) + "." + ext
tnb = "".join(x for x in tnb if x.isalnum() or x in "._-()")
- topath = os.path.join(Factory().favorites_home, tnb)
+ topath = os.path.join(Constants().favorites_home, tnb)
pyotherside.send("topath: " + topath)
return topath
@@ -539,10 +520,10 @@ class Podpost:
if self.file_path:
queue = QueueFactory().get_queue()
delete_file(get_topath())
- if self.id not in queue.podposts:
+ if self.id not in queue.get_podposts_ids():
self.delete_file()
- self.save()
+ PodpostFactory().persist(self)
return self.favorite
def get_chapters(self):
@@ -555,7 +536,7 @@ class Podpost:
logger.info("setting chapter selection flags")
for chapter in self.chapters:
chapter["selected"] = True
- self.save()
+ PodpostFactory().persist(self)
return self.chapters
@@ -566,7 +547,7 @@ class Podpost:
"""
self.chapters[chapterid]["selected"] = not self.chapters[chapterid]["selected"]
- self.save()
+ PodpostFactory().persist(self)
@property
def get_fav(self):
@@ -582,3 +563,57 @@ class Podpost:
return favorite
+class PodpostFactory(BaseFactory):
+
+ def __init__(self):
+ super().__init__()
+ self.podpostcache = Cache(limit=500)
+
+ def get_podpost(self, index):
+ """
+ Get a podpost from cache or store
+ """
+
+ podpost = self.podpostcache.get(index)
+ if not podpost:
+ podpost = self.store.get(index)
+ if podpost:
+ self.podpostcache.store(index, podpost)
+ else:
+ return None
+
+ return podpost
+
+ def delete_podpost(self, index):
+ self.podpostcache.delete(index)
+ self.store.delete(index)
+
+ def persist(self, post: Podpost):
+ if type(post) != Podpost:
+ raise ValueError("Can only persist podposts")
+ self.store.store(post.id, post)
+ self.podpostcache.store(post.id, post)
+
+ def get_podcast_posts(self, podcast) -> Iterator[Podpost]:
+ """
+ @type podcast: Podcast
+ """
+ from podcast.podcast import Podcast
+ if (type(podcast) != Podcast):
+ raise ValueError("supplied argument must be a podcast")
+ for id in podcast.entry_ids_old_to_new:
+ yield self.get_podpost(id)
+
+ def persist_bulk(self, new_posts:List[Podpost]):
+ for post in new_posts:
+ self.persist(post)
+
+ def delete_all_from_podcast(self, podcast):
+ """
+ @type podcast: Podcast
+ """
+ from podcast.podcast import Podcast
+ if (type(podcast) != Podcast):
+ raise ValueError("supplied argument must be a podcast")
+ for post_id in podcast.entry_ids_old_to_new:
+ self.delete_podpost(post_id)
\ No newline at end of file
diff --git a/python/podcast/queue.py b/python/podcast/queue.py
index 51272ac4d5660e09a366742ba374e5d5b52b8302..3a35beb1adfd17ebc43193e648e8f4d5d959f56f 100644
--- a/python/podcast/queue.py
+++ b/python/podcast/queue.py
@@ -5,9 +5,13 @@ It instanciates Podposts via Factory if needed.
import sys
import os
+from typing import Iterator
+
import pyotherside
import time
+from podcast.podpost import PodpostFactory, Podpost
+
sys.path.append("../")
from podcast.singleton import Singleton
@@ -57,7 +61,7 @@ class Queue:
self.top = None
else:
podpost = self.podposts[0]
- self.top = Factory().get_podpost(podpost)
+ self.top = PodpostFactory().get_podpost(podpost)
return self.top
@@ -81,7 +85,7 @@ class Queue:
self.podposts.remove(podpost)
self.podposts.insert(0, podpost)
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
position = post.get_position / 1000
duration = post.duration
if duration == 0 or duration - position < 60:
@@ -91,7 +95,7 @@ class Queue:
if post.insert_date == None:
post.insert_date = time.time()
- post.save()
+ PodpostFactory().persist(post)
self.save()
pyotherside.send("needDownload", podpost)
pyotherside.send("posplay", post.get_position)
@@ -110,7 +114,7 @@ class Queue:
self.podposts.remove(podpost)
self.podposts.insert(1, podpost)
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
position = post.get_position / 1000
duration = post.duration
if duration == 0 or duration - position < 60:
@@ -120,7 +124,7 @@ class Queue:
if post.insert_date == None:
post.insert_date = time.time()
- post.save()
+ PodpostFactory().persist(post)
self.save()
pyotherside.send("needDownload", podpost)
@@ -136,7 +140,7 @@ class Queue:
self.podposts.remove(podpost)
self.podposts.append(podpost)
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
position = post.get_position / 1000
duration = post.duration
if duration == 0 or duration - position < 60:
@@ -146,7 +150,7 @@ class Queue:
if post.insert_date == None:
post.insert_date = time.time()
- post.save()
+ PodpostFactory().persist(post)
self.save()
pyotherside.send("needDownload", podpost)
@@ -156,16 +160,16 @@ class Queue:
"""
if podpost in self.podposts:
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
post.delete_file()
post.state = 0
- post.save()
+ PodpostFactory().persist(post)
if podpost == self.podposts[0] and len(self.podposts) > 1:
- post1 = Factory().get_podpost(self.podposts[1])
+ post1 = PodpostFactory().get_podpost(self.podposts[1])
pyotherside.send("setpos", post1.position)
self.podposts.remove(podpost)
else:
- Factory().get_podpost(podpost).save()
+ PodpostFactory().persist(PodpostFactory().get_podpost(podpost))
self.save()
@@ -175,7 +179,7 @@ class Queue:
"""
if podpost in self.podposts:
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
for perc in post.download_audio():
yield perc
@@ -188,7 +192,7 @@ class Queue:
"""
for podpost in self.podposts:
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
try:
if post.file_path:
if os.path.isfile(post.file_path):
@@ -204,7 +208,7 @@ class Queue:
"""
return (
- Factory()
+ PodpostFactory()
.get_podpost(self.podposts[0])
.get_hr_time(position=position)
)
@@ -254,7 +258,17 @@ class Queue:
"""
for podpost in self.podposts:
- yield (podpost)
+ yield podpost
+
+ def get_top_id(self):
+ return self._get_top().id
+
+ def get_podposts_ids(self):
+ for p in self.get_podposts():
+ yield p.id
+
+ def count(self):
+ return len(self.podposts)
def play(self):
"""
@@ -321,3 +335,21 @@ class QueueFactory(metaclass=Singleton):
self.queue = Queue()
return self.queue
+
+ def get_podposts_objects(self)->Iterator[Podpost]:
+ for postid in self.get_queue().podposts:
+ yield PodpostFactory().get_podpost(postid)
+
+ def get_first_podpost_object(self):
+ return next(self.get_podposts_objects(), None)
+
+ def get_favorites(self, podurl)->Iterator[Podpost]:
+ for post in self.get_podposts_objects():
+ if post.favorite:
+ if podurl:
+ if post.podurl == podurl:
+ yield post
+ else:
+ yield post
+
+
diff --git a/python/podcast/util.py b/python/podcast/util.py
index 2bcaee1899baa0b06c45ca36713879984dfbe2ea..8513b41473b393c4baa94d0ae5e227afca6a8a4d 100644
--- a/python/podcast/util.py
+++ b/python/podcast/util.py
@@ -70,7 +70,6 @@ def s_to_hr(secs):
hours = int(secs / 3600)
hr = str(hours) + "h"
secs = secs - hours * 3600
- print(secs)
if secs >= 59:
hr = hr + str(int(secs / 60)) + "m"
diff --git a/qml/components/ArchiveHandler.py b/qml/components/ArchiveHandler.py
index f4883945ebde7cabbf1de2ae2efbf94c2d6f2dfb..7d0673d25876048aefd47110baf7927673c85cfc 100644
--- a/qml/components/ArchiveHandler.py
+++ b/qml/components/ArchiveHandler.py
@@ -8,30 +8,18 @@ import sys
sys.path.append("/usr/share/harbour-podqast/python")
from podcast.archive import ArchiveFactory
-from podcast.factory import Factory
+import logging
+logger = logging.getLogger(__name__)
def get_archive_posts(podurl=None):
"""
Return a list of all archive posts
"""
- entries = []
- archive = ArchiveFactory().get_archive()
-
- for post in archive.get_podposts():
- entry = Factory().get_podpost(post)
- if podurl:
- if entry.podurl == podurl:
- entries.append(entry.get_data())
- else:
- if entry:
- entries.append(entry.get_data())
- else:
- pyotherside.send("ArchiveHandler: We have a none object")
-
- entries.sort(key=lambda r: r["adate"], reverse=True)
- pyotherside.send("createArchiveList", entries)
+ posts = ArchiveFactory().get_archive().get_podpost_objects(url_filter=podurl)
+ sorted_posts = sorted(posts, key=lambda p: p.insert_date, reverse=True)
+ pyotherside.send("createArchiveList", [post.get_data() for post in sorted_posts])
def get_archive_pod_data():
@@ -42,8 +30,7 @@ def get_archive_pod_data():
archive = ArchiveFactory().get_archive()
- for post in archive.get_podposts():
- entry = Factory().get_podpost(post)
+ for entry in archive.get_podpost_objects():
if entry.podurl in entries:
entries[entry.podurl]["count"] += 1
else:
diff --git a/qml/components/ExternalHandler.py b/qml/components/ExternalHandler.py
index b4be120fc533eaf200a7c80bf20379ec2fc37a38..72e00755b9c59f279d90e077f0c0dbce16906cd0 100644
--- a/qml/components/ExternalHandler.py
+++ b/qml/components/ExternalHandler.py
@@ -7,15 +7,16 @@ import sys
import os
import hashlib
+
sys.path.append("/usr/share/harbour-podqast/python")
+from podcast.constants import Constants
from podcast.external import ExternalFactory
from podcast.inbox import InboxFactory
from podcast.archive import ArchiveFactory
from podcast.queue import QueueFactory
-from podcast.factory import Factory
-from podcast.podpost import Podpost
+from podcast.podpost import Podpost, PodpostFactory
import inotify.adapters
import mutagen
@@ -42,7 +43,7 @@ def check_new():
"""
pyotherside.send("Check new/lost audio files")
- external = Factory().external_home
+ external = Constants().external_home
if not os.path.isdir(external):
pyotherside.send("Warning: external directory does not exist!")
return
@@ -60,13 +61,13 @@ def check_new():
os.path.join(external, f).encode()
).hexdigest()
mfiles.append(fnh)
- if fnh not in exlib.podposts:
+ if fnh not in exlib.get_podposts():
pyotherside.send("external: " + fn + " is music")
post = Podpost.from_audio_file(fn)
- post.save()
+ PodpostFactory().persist(post)
exlib.insert(post.id)
exlib.save()
- wheremove = Factory().get_val("extMoveToVal")
+ wheremove = Constants().get_val("extMoveToVal")
movePost(post.id, wheremove)
pyotherside.send(
"updatesNotification",
@@ -78,7 +79,7 @@ def check_new():
except:
pyotherside.send("File does not exist")
- for f in exlib.podposts:
+ for f in exlib.get_podposts():
pyotherside.send("we are on file %s" % f)
if f not in mfiles:
exlib.remove_podpost(f)
@@ -90,7 +91,7 @@ def wait_new():
check via inotify for new files in ~/podqast/external
"""
- external = Factory().external_home
+ external = Constants().external_home
if not os.path.isdir(external):
return
@@ -105,7 +106,7 @@ def wait_new():
pyotherside.send(post.title)
pyotherside.send(post.logo_url)
pyotherside.send(post.id)
- post.save()
+ PodpostFactory().persist(post)
exlib = ExternalFactory().get_external()
exlib.insert(post.id)
exlib.save()
@@ -123,7 +124,7 @@ def wait_new():
for event in i.event_gen(timeout_s=10, yield_nones=False):
(_, type_names, path, filename) = event
- wheremove = Factory().get_val("extMoveToVal")
+ wheremove = Constants().get_val("extMoveToVal")
if "IN_CREATE" in type_names:
filepath = os.path.join(external, filename)
if os.path.islink(filepath):
@@ -168,9 +169,8 @@ def get_external_posts():
entries = []
external = ExternalFactory().get_external()
- for post in external.get_podposts():
+ for entry in external.get_podposts_objects():
pyotherside.send("podposts loop")
- entry = Factory().get_podpost(post)
entries.append(entry.get_data())
pyotherside.send("after py external")
# entries.sort(key = lambda r: r["edate"], reverse=True)
@@ -185,8 +185,7 @@ def get_external_pod_data():
external = ExternalFactory().get_external()
- for post in external.get_podposts():
- entry = Factory().get_podpost(post)
+ for entry in external.get_podposts_objects():
if entry.podurl in entries:
entries[entry.podurl]["count"] += 1
else:
diff --git a/qml/components/FavoriteHandler.py b/qml/components/FavoriteHandler.py
index e19a9a92e4b74309525b7a4632b08510bbec8d8d..84eb2daf9bf70ecdbe5b4b8aba535ca1a1e2aa24 100644
--- a/qml/components/FavoriteHandler.py
+++ b/qml/components/FavoriteHandler.py
@@ -5,106 +5,28 @@ import pyotherside
import threading
import sys
-sys.path.append("/usr/share/harbour-podqast/python")
-
-from podcast.factory import Factory
+sys.path.append("/usr/share/harbour-podqast/python")
+from podcast.podpost import PodpostFactory
+import podcast.favorite as favorite
def toggle_favorite(podpost, do_download=False):
"""
Return a list of all archive posts
"""
- post = Factory().get_podpost(podpost)
+ post = PodpostFactory().get_podpost(podpost)
if not post:
return
pyotherside.send("favoriteToggled", podpost, post.toggle_fav(do_download))
-def get_queue_favorites(podurl=None):
- """
- Return a list of all favorites posts from queue
- """
-
- from podcast.queue import QueueFactory
-
- posts = []
-
- queue = QueueFactory().get_queue()
- for post in queue.get_podposts():
- p = Factory().get_podpost(post)
- try:
- if p.favorite:
- if podurl:
- if p.podurl == podurl:
- posts.append(p)
- else:
- posts.append(p)
- except:
- pass
- return posts
-
-
-def append_archive_favorites(posts, podurl=None):
- """
- append archive favorites to posts list
- """
-
- from podcast.archive import ArchiveFactory
-
- archive = ArchiveFactory().get_archive()
-
- for post in archive.get_podposts():
- p = Factory().get_podpost(post)
- try:
- if p.favorite:
- if p not in posts:
- if podurl:
- if p.podurl == podurl:
- posts.append(p)
- else:
- posts.append(p)
- except:
- pass
-
- return posts
-
-
-def get_favorites(podurl=None):
- """
- Test
- """
-
- posts = get_queue_favorites(podurl)
- posts = append_archive_favorites(posts, podurl=podurl)
- postsdata = []
- for post in posts:
- postsdata.append(post.get_data())
-
- postsdata.sort(key=lambda r: r["adate"], reverse=True)
- pyotherside.send("favoriteList", postsdata)
-
+def send_favorites(podurl=None):
+ pyotherside.send("favoriteList", [p.get_data() for p in favorite.get_favorites(podurl)])
def get_fav_pod_data():
- """
- """
-
- entries = {}
-
- posts = get_queue_favorites()
- posts = append_archive_favorites(posts)
- for p in posts:
- if p.podurl in entries:
- entries[p.podurl]["count"] += 1
- else:
- if p.logo_url:
- logo_url = p.logo_url
- else:
- logo_url = "../../images/podcast.png"
- entries[p.podurl] = {"count": 1, "logo_url": logo_url}
-
- pyotherside.send("archivePodList", entries)
+ pyotherside.send("archivePodList", favorite.get_favorite_podcast_stats())
class FavoriteHandler:
@@ -127,10 +49,10 @@ class FavoriteHandler:
return
if podurl:
self.bgthread2 = threading.Thread(
- target=get_favorites, args=[podurl]
+ target=send_favorites, args=[podurl]
)
else:
- self.bgthread2 = threading.Thread(target=get_favorites)
+ self.bgthread2 = threading.Thread(target=send_favorites)
self.bgthread2.start()
def getfavpoddata(self):
diff --git a/qml/components/FeedParser.py b/qml/components/FeedParser.py
index 39984ae32d21403487a7eb548e86c2bc0535b420..b732145842fa00bd8154d31bb818a66866f587fd 100644
--- a/qml/components/FeedParser.py
+++ b/qml/components/FeedParser.py
@@ -9,8 +9,10 @@ import os
import time
import tarfile
+
sys.path.append("/usr/share/harbour-podqast/python")
+from podcast.constants import Constants
from podcast.podcast import PodcastFactory
from podcast.podcastlist import PodcastListFactory
from podcast.queue import QueueFactory
@@ -49,7 +51,7 @@ def get_latest_entry(url):
def init_from_qml(object):
- Factory().init_from_qml(object)
+ Constants().init_from_qml(object)
def get_entries(url):
@@ -85,8 +87,7 @@ def get_podcasts():
podcasts = []
pclist = PodcastListFactory().get_podcast_list()
- for podcast in pclist.get_podcasts():
- pc = PodcastFactory().get_podcast(podcast)
+ for pc in pclist.get_podcasts_objects():
podcasts.append(pc.feedinfo())
pyotherside.send("podcastslist", podcasts)
@@ -99,8 +100,7 @@ def get_podcastscache():
pclist = PodcastListFactory().get_podcast_list()
- for podcast in pclist.get_podcasts():
- PodcastFactory().get_podcast(podcast)
+ list(pclist.get_podcasts_objects())
pyotherside.send("podcast cache ready")
@@ -282,8 +282,7 @@ def write_opml():
podcasts = []
pclist = PodcastListFactory().get_podcast_list()
- for podcast in pclist.get_podcasts():
- pc = PodcastFactory().get_podcast(podcast)
+ for pc in pclist.get_podcasts_objects():
podcasts.append(
{"name": pc.title, "xml_url": pc.url, "html_url": pc.link}
)
diff --git a/qml/components/InboxHandler.py b/qml/components/InboxHandler.py
index f0038cae6e4e9e4e1e8828ba2ab85f14f072ee58..b5abb2a2c8132ae980383c59bebcba988b59d108 100644
--- a/qml/components/InboxHandler.py
+++ b/qml/components/InboxHandler.py
@@ -9,7 +9,6 @@ sys.path.append("/usr/share/harbour-podqast/python")
from podcast.inbox import InboxFactory
from podcast.queue import QueueFactory
-from podcast.factory import Factory
def get_inbox_posts(podurl=None):
@@ -20,15 +19,10 @@ def get_inbox_posts(podurl=None):
entries = []
inbox = InboxFactory().get_inbox()
- pyotherside.send("len Inbox %d" % len(inbox.podposts))
+ pyotherside.send("len Inbox %d" % len(inbox.get_podposts()))
- for post in inbox.get_podposts():
- entry = Factory().get_podpost(post)
- if podurl:
- if entry.podurl == podurl:
- entries.append(entry.get_data())
- else:
- entries.append(entry.get_data())
+ for entry in inbox.get_podposts_objects(podurl=podurl):
+ entries.append(entry.get_data())
entries.sort(key=lambda r: r["date"], reverse=True)
pyotherside.send("createInboxList", entries)
@@ -42,8 +36,7 @@ def get_inbox_pod_data():
inbox = InboxFactory().get_inbox()
- for post in inbox.get_podposts():
- entry = Factory().get_podpost(post)
+ for entry in inbox.get_podposts_objects():
if entry.podurl in entries:
entries[entry.podurl]["count"] += 1
else:
diff --git a/qml/components/QueueHandler.py b/qml/components/QueueHandler.py
index 2c8845504451a5b8e4dbc652fe3d8e5387b42754..07832490cccfcfe4f364bf155695bbe2c21cfae1 100644
--- a/qml/components/QueueHandler.py
+++ b/qml/components/QueueHandler.py
@@ -5,10 +5,11 @@ import pyotherside
import threading
import sys
+
sys.path.append("/usr/share/harbour-podqast/python")
+from podcast.podpost import PodpostFactory
from podcast.queue import QueueFactory
-from podcast.factory import Factory
from podcast.archive import ArchiveFactory
@@ -18,10 +19,8 @@ def get_queue_posts(moved=""):
"""
entries = []
- queue = QueueFactory().get_queue()
- for post in queue.get_podposts():
- entry = Factory().get_podpost(post)
+ for entry in QueueFactory().get_podposts_objects():
edata = entry.get_data()
edata["moved"] = edata["id"] == moved
entries.append(edata)
@@ -39,12 +38,11 @@ def get_first_entry():
Return the data of the first entry
"""
- queue = QueueFactory().get_queue()
- if len(queue.podposts) < 1:
- return None
- pyotherside.send(
- "firstentry", Factory().get_podpost(queue.podposts[0]).get_data()
- )
+ post = QueueFactory().get_first_podpost_object()
+ if post is not None:
+ pyotherside.send(
+ "firstentry", post.get_data()
+ )
def queue_insert_top(id, doplay=False):
@@ -150,7 +148,7 @@ def queue_to_archive(id):
queue = QueueFactory().get_queue()
archive = ArchiveFactory().get_archive()
- if queue.podposts[0] == id:
+ if queue.get_top_id() == id:
pyotherside.send("stopping")
queue.remove(id)
archive.insert(id)
@@ -164,7 +162,7 @@ def queue_top_to_archive():
"""
queue = QueueFactory().get_queue()
- queue_to_archive(queue.podposts[0])
+ queue_to_archive(queue.get_top_id())
queue_play()
@@ -193,9 +191,9 @@ def queue_hr_time(position):
"""
queue = QueueFactory().get_queue()
- if len(queue.podposts) > 0:
+ if queue.count() > 0:
pyotherside.send(
- "hrtime", queue.podposts[0], queue.get_hr_time(position)
+ "hrtime", queue.get_top_id(), queue.get_hr_time(position)
)
@@ -214,7 +212,7 @@ def get_episode_chapters(id):
id: id of the episode
"""
- entry = Factory().get_podpost(id)
+ entry = PodpostFactory().get_podpost(id)
chapters = entry.get_chapters()
pyotherside.send("episodeChapters", chapters)
@@ -225,7 +223,7 @@ def send_first_episode_chapters(id):
(necessary when chapters of the currently
playing episode are selected/deselected)
"""
- entry = Factory().get_podpost(id)
+ entry = PodpostFactory().get_podpost(id)
edata = entry.get_data()
chapters = entry.get_chapters()
pyotherside.send("setFirst", edata, chapters)
@@ -237,7 +235,7 @@ def toggle_chapter(episodeid, chapterid):
episodeid: id of the episode
chapterid: chapter number
"""
- entry = Factory().get_podpost(episodeid)
+ entry = PodpostFactory().get_podpost(episodeid)
entry.toggle_chapter(chapterid)
diff --git a/test/__init__.py b/test/__init__.py
index 30af4cafc6ca7ca941effcc08f30f4458fda74ee..24706eabdcf1e12ca0ce0b9fb06805f2aed0c5bc 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -1,11 +1,33 @@
+import os
+import tempfile
import types
import sys
from unittest.mock import Mock
+
def mock_pyotherside():
module_name = "pyotherside"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
- module.send = Mock(name=module_name+'.send')
+ module.send = Mock(name=module_name + '.send')
+
mock_pyotherside()
+
+os.environ["PODQAST_HOME"] = tempfile.gettempdir()
+print("tempdir: %s" % os.environ["PODQAST_HOME"])
+
+
+def cleanup_archive():
+ from podcast.archive import ArchiveFactory
+ archive = ArchiveFactory().get_archive()
+ for post in archive.get_podposts():
+ archive.remove_podpost(post)
+ archive.save()
+
+def cleanup_queue():
+ from podcast.queue import QueueFactory
+ queue = QueueFactory().get_queue()
+ for post in queue.get_podposts():
+ queue.remove(post)
+ queue.save()
diff --git a/test/test_archive.py b/test/test_archive.py
index e3d187d7f2efd82cadb3c001a724fb73f20384c0..0586057902b75f7e30d5319c1d94d705d168d6f9 100644
--- a/test/test_archive.py
+++ b/test/test_archive.py
@@ -4,18 +4,17 @@ test the archive
import sys
-import httpretty
from httpretty import HTTPretty, httprettified
+from . import cleanup_archive
from test.test_podcast import read_testdata, xml_headers
sys.path.append("../python")
from podcast.archive import ArchiveFactory, Archive
-from podcast.factory import Factory
from podcast.podcast import Podcast
-from podcast.podpost import Podpost
-import podcast
+from podcast.podpost import Podpost, PodpostFactory
+
def test_create_archive():
"""
@@ -28,6 +27,7 @@ def test_create_archive():
a2 = ArchiveFactory().get_archive()
assert a1 == a2
+
def test_archive_save():
"""
does the archive save itself?
@@ -36,36 +36,63 @@ def test_archive_save():
a = ArchiveFactory().get_archive()
a.save()
-@httprettified
+
def test_insert():
"""
"""
+
+ archive = ArchiveFactory().get_archive()
+ entry1, entry2 = setup_archive_with_2_posts(archive)
+
+ result_posts = list(archive.get_podposts())
+ assert entry1.id in result_posts
+ assert entry2.id in result_posts
+ object_ids = [a.id for a in archive.get_podpost_objects('https://freakshow.fm/feed/opus/')]
+ assert entry1.id in object_ids
+ assert entry2.id in object_ids
+ assert 0 == len(list(archive.get_podpost_objects(url_filter='https://i.am.not.corrects/speex')))
+
+
+@httprettified
+def setup_archive_with_2_posts(archive):
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_entry(p.entry_ids_old_to_new[0])
- e.save()
- a.insert(e.id)
-
- e2 = p.get_entry(p.entry_ids_old_to_new[1])
- e2.save()
- a.insert(e2.id)
-
- assert e.id in a.podposts
- assert e2.id in a.podposts
+ podcast = Podcast('https://freakshow.fm/feed/opus/')
+ entry1 = podcast.get_entry(podcast.entry_ids_old_to_new[0])
+ PodpostFactory().persist(entry1)
+ archive.insert(entry1.id)
+ entry2 = podcast.get_entry(podcast.entry_ids_old_to_new[1])
+ PodpostFactory().persist(entry2)
+ archive.insert(entry2.id)
+ return entry1, entry2
+
def test_get_archives():
"""
Test listing of podposts
"""
-
+ cleanup_archive()
a = ArchiveFactory().get_archive()
+ setup_archive_with_2_posts(a)
count = 0
for post in a.get_podposts():
assert type(post) == str
- assert type(Factory().get_podpost(post)) == Podpost
+ assert type(PodpostFactory().get_podpost(post)) == Podpost
count += 1
- # make sure to clean ~/.local/share/harbour-podqast if this fails
+ posts = list(a.get_podpost_objects())
+ assert len(posts) == 2
+ for post in posts:
+ assert type(post) == Podpost
+ assert len(list(a.get_podpost_objects(filter_favorite=True))) == 0
+ assert len(list(a.get_podpost_objects(url_filter="http://i.am.wrong"))) == 0
+ assert len(list(a.get_podpost_objects(url_filter="http://i.am.wrong",filter_favorite=True))) == 0
+ assert len(list(a.get_podpost_objects(url_filter="https://freakshow.fm/feed/opus/"))) == 2
+ assert len(list(a.get_podpost_objects(url_filter="https://freakshow.fm/feed/opus/",filter_favorite=True))) == 0
+ for post in a.get_podpost_objects():
+ post.favorite = True
+ PodpostFactory().persist(post)
+ assert len(list(a.get_podpost_objects(url_filter="https://freakshow.fm/feed/opus/",filter_favorite=True))) == 2
+ assert len(list(a.get_podpost_objects(filter_favorite=True))) == 2
+ assert len(list(a.get_podpost_objects(url_filter="http://i.am.wrong",filter_favorite=True))) == 0
+
assert count == 2
diff --git a/test/test_factory.py b/test/test_factory.py
index 6d8530ab678d645707ca15ed32381f6fa9d38c37..49238a564ce352a1ba9fc5fb88070b6de2e5d8ae 100644
--- a/test/test_factory.py
+++ b/test/test_factory.py
@@ -3,73 +3,18 @@ Factory test
"""
import sys
+
sys.path.append("../python")
from podcast.factory import Factory
-from podcast.queue import Queue
-import feedparser
def test_singleton():
"""
Factory is a Singleton. Let's test, if it really is...
"""
- f1 = Factory() # first instance: set storedir
+ f1 = Factory() # first instance: set storedir
f2 = Factory()
assert f1 == f2
-
-def test_getfeed():
- """
- get a feed
- """
-
- f = Factory()
- feed1 = f.get_feed("https://freakshow.fm/feed/mp3")
- feed2 = f.get_feed("https://freakshow.fm/feed/mp3")
- feed3 = f.get_feed("https://cre.fm/feed/opus")
-
- assert feed1 == feed2
- assert type(feed1) == feedparser.FeedParserDict
-
-def test_getfeed2():
- """
- get a feed (should work offline)
- """
-
- f = Factory()
- feed1 = f.get_feed("https://freakshow.fm/feed/mp3")
- feed2 = f.get_feed("https://freakshow.fm/feed/mp3")
- feed3 = f.get_feed("https://cre.fm/feed/opus")
-
- assert feed1 == feed2
- assert feed1 != feed3
-
- assert type(feed1) == feedparser.FeedParserDict
- assert type(feed3) == feedparser.FeedParserDict
-
-def test_feed_persist():
- """
- Test feed persisting
- """
-
- f = Factory()
- f.safe_feed("https://freakshow.fm/feed/mp3")
- f.safe_feed("https://cre.fm/feed/opus")
-
-def test_feed_unpersist():
- """
- test feed removing
- """
-
- f = Factory()
- f.delete_feed("https://cre.fm/feed/opus")
-
-def test_feed_unpersist2():
- """
- delete other feed too
- """
-
- f = Factory()
- f.delete_feed("https://freakshow.fm/feed/mp3")
diff --git a/test/test_favorites.py b/test/test_favorites.py
new file mode 100644
index 0000000000000000000000000000000000000000..c56492355cf449c44f058e7c41b92e8db080230f
--- /dev/null
+++ b/test/test_favorites.py
@@ -0,0 +1,37 @@
+from httpretty import HTTPretty, httprettified
+
+from podcast.archive import ArchiveFactory
+from podcast.factory import Factory
+from podcast.podpost import PodpostFactory
+from . import cleanup_archive, cleanup_queue
+from test.test_podcast import read_testdata, xml_headers
+
+
+@httprettified
+def test_queue_favorites():
+ """
+ Test listing of podposts
+ """
+ HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/',
+ body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers)
+
+ from podcast import favorite
+ from podcast.podcast import PodcastFactory
+ cleanup_archive()
+ cleanup_queue()
+
+ url = 'https://freakshow.fm/feed/opus/'
+ podcast = PodcastFactory().get_podcast(url)
+ fav_posts = [PodpostFactory().get_podpost(podcast.entry_ids_old_to_new[i]) for i in range(0, 10, 2)]
+ assert len(fav_posts) == 5
+ archive = ArchiveFactory().get_archive()
+ assert len(list(archive.get_podposts())) == 0
+ for post in fav_posts:
+ post.favorite = True
+ PodpostFactory().persist(post)
+ archive.insert(post.id)
+ archive.save()
+ assert len(favorite.get_queue_favorites()) == 0
+ assert len(list(favorite.get_favorites())) == 5
+ assert len(favorite.get_favorite_podcast_stats()) == 1
+ assert favorite.get_favorite_podcast_stats()[url]['count'] == 5
diff --git a/test/test_migration_to_v1.py b/test/test_migration_to_v1.py
index 4f9e587c1fe064fb3c8b1cb746b5c2a6ce2f9509..2c15b6c65655ee699384bdff3e2d75e47aee29f9 100644
--- a/test/test_migration_to_v1.py
+++ b/test/test_migration_to_v1.py
@@ -10,10 +10,12 @@ from podcast.factory import Factory
from podcast.inbox import InboxFactory
from podcast.podcast import Podcast, PodcastFactory
from podcast.podcastlist import PodcastListFactory
+from podcast.podpost import PodpostFactory
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)
@@ -23,9 +25,12 @@ def test_migration():
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])
+ podcast: Podcast = PodcastFactory().get_podcast(podcasts[0])
assert podcast != None
assert len(podcast.entry_ids_old_to_new) == 22
+ for post in (PodpostFactory().get_podpost(post) for post in podcast.entry_ids_old_to_new for podcast in
+ map(PodcastFactory().get_podcast, podcasts)):
+ assert post.insert_date is not None
assert len(list(podcast.get_entries())) == 22
del os.environ["PODQAST_HOME"]
resetSingletons()
diff --git a/test/test_podcast.py b/test/test_podcast.py
index 3d71891ac723736d9b045cb46ce8786876e4df4d..f2f1afa701f5e6e1d173a672017115940be3ffc6 100644
--- a/test/test_podcast.py
+++ b/test/test_podcast.py
@@ -7,9 +7,11 @@ import os
import httpretty
from httpretty import HTTPretty
+from podcast.podpost import PodpostFactory
+
sys.path.append("../python")
-from podcast.podcast import Podcast
+from podcast.podcast import Podcast, PodcastFactory
xml_headers = {"content-type": "application/rss+xml; charset=UTF-8"}
def read_testdata(filename):
@@ -28,10 +30,10 @@ def test_feed_entry():
HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/',
body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers)
- for data in [ 'https://freakshow.fm/feed/opus/',
+ for url in [ 'https://freakshow.fm/feed/opus/',
'http://feeds.twit.tv/sn.xml',
]:
- podcast = Podcast(data)
+ podcast = Podcast(url)
assert podcast.alt_feeds != None
for feed in podcast.alt_feeds:
assert type(feed['url']) == str
@@ -58,6 +60,9 @@ def test_feed_entry():
assert type(data['logo_url']) == str
assert podcast.get_entry(data['id']) != None
assert data == podcast.get_entry(data['id']).get_data()
+ PodcastFactory().remove_podcast(url)
+ for entry in entries:
+ assert PodpostFactory().get_podpost(entry['id']) is None
invoked = 0
@httpretty.activate
diff --git a/test/test_podpost.py b/test/test_podpost.py
index 2d40f3ce8734e07515f4c66f3eb931eba3f2cc4f..97ff0434db30acf7ef1fed6a331407e36b64d6fc 100644
--- a/test/test_podpost.py
+++ b/test/test_podpost.py
@@ -3,24 +3,33 @@ Podpost tests
"""
import sys
+
+import httpretty
+from httpretty import HTTPretty
+
+from test.test_podcast import read_testdata, xml_headers
+
sys.path.append("../python")
from podcast.factory import Factory
from podcast.podcast import Podcast
-from podcast.podpost import Podpost
+from podcast.podpost import Podpost, PodpostFactory
+
+@httpretty.activate
def test_podpost_save():
"""
Test podpost saving
"""
-
+ HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/',
+ body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers)
f1 = Factory()
p = Podcast('https://freakshow.fm/feed/opus/')
e = p.get_latest_entry()
id1 = e.id
- e.save()
- n = Factory().get_store().get(e.id)
+ PodpostFactory().persist(e)
+ n = PodpostFactory().get_podpost(e.id)
id2 = n.id
assert type(n) == Podpost
assert id1 == id2
diff --git a/test/test_queue.py b/test/test_queue.py
index 76343369e664089900482ce9b5d256e6e1511b38..8cc3a87015a45ba077fd2fe443577221d58cf5a8 100644
--- a/test/test_queue.py
+++ b/test/test_queue.py
@@ -4,14 +4,19 @@ test the queue
import sys
+import httpretty
+from httpretty import HTTPretty
+
+from test import cleanup_queue
+from test.test_podcast import read_testdata, xml_headers
+
sys.path.append("../python")
from podcast.queue import QueueFactory
from podcast.factory import Factory
from podcast.queue import Queue
-from podcast.podcast import Podcast
-from podcast.podpost import Podpost
-import podcast
+from podcast.podcast import Podcast, PodcastFactory
+from podcast.podpost import Podpost, PodpostFactory
def test_create_queue():
@@ -35,21 +40,23 @@ def test_queue_save():
q.save()
+@httpretty.activate
def test_insert_top():
"""
"""
-
+ HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/',
+ body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers)
q = QueueFactory().get_queue()
l1 = len(q.podposts)
p = Podcast('https://freakshow.fm/feed/opus/')
e = p.get_entry(p.entry_ids_old_to_new[1])
e2 = p.get_entry(p.entry_ids_old_to_new[0])
- e.save()
+ PodpostFactory().persist(e)
q.insert_top(e.id)
assert q.podposts[0] == e.id
- e2.save()
+ PodpostFactory().persist(e2)
q.insert_top(e2.id)
assert q.podposts[0] == e2.id
@@ -62,12 +69,42 @@ def test_get_podposts():
"""
Test listing of podposts
"""
+ cleanup_queue()
queue = QueueFactory().get_queue()
- queue.podposts = ['85140fc8e16ba62517877ef4857a85f5bbf83e6a236c659a73489f093e51f154',
- 'ce656a3888a282fe9df727e0729fa7d375704e4bd6c0d50e9c0fe7ddafa232ec']
+ podcast = PodcastFactory().get_podcast('https://freakshow.fm/feed/opus/')
+ entry = podcast.entry_ids_old_to_new[1]
+ entry2 = podcast.entry_ids_old_to_new[0]
+ queue.insert_top(entry)
+ queue.insert_top(entry2)
postids = list(queue.get_podposts())
assert len(postids) == 2
for post in postids:
assert type(post) == str
- assert type(Factory().get_podpost(post)) == Podpost
+ assert type(PodpostFactory().get_podpost(post)) == Podpost
+ assert post in [entry, entry2]
+
+
+def test_queue_favorites():
+ """
+ Test listing of podposts
+ """
+ from podcast import favorite
+
+ queue = QueueFactory().get_queue()
+ url = 'https://freakshow.fm/feed/opus/'
+ podcast = PodcastFactory().get_podcast(url)
+ for post in (PodpostFactory().get_podpost(podcast.entry_ids_old_to_new[i]) for i in range(0, 10, 2)):
+ post.favorite = True
+ PodpostFactory().persist(post)
+ for id in podcast.entry_ids_old_to_new:
+ queue.insert_bottom(id)
+ assert len(queue.podposts) == 20
+ assert len(favorite.get_queue_favorites('https://wrong.url')) == 0
+ assert [fav.id for fav in favorite.get_queue_favorites()] == [podcast.entry_ids_old_to_new[i] for i in
+ range(0, 10, 2)]
+ assert len(favorite.get_queue_favorites(url)) == 5
+ for id in podcast.entry_ids_old_to_new:
+ queue.remove(id)
+ assert len(queue.podposts) == 0
+ assert len(favorite.get_queue_favorites()) == 0
diff --git a/test/testdata/freakshow.rss b/test/testdata/freakshow.rss
index c81a8fccf91a314a6c96eb557bb53cdfbf2f2e2f..e2b9ac7bac51f25bae7114b4d0983155b1b3e006 100644
--- a/test/testdata/freakshow.rss
+++ b/test/testdata/freakshow.rss
@@ -56,5397 +56,7 @@
-Schreibe uns einen Kommentar
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Podcast Clients — 2020 Technik-Rückblick — LiDAR — iPhone 12 — M1 — OpenZFS — NAS — Mac Apps — Bildschirme
- -Das Jahr geht (endlich) zu Ende und wir füllen die Bits eines Bytes bis auf letzte Stelle. Ausgabe Nummer Zweihundertfünfundfünzig versucht Euch zu Weihnachten noch mal das Herz zu fluten.
- -Dauer: 3:56:56
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Denis Ahrens - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--roddi - | -
-
-
- |
-
-
-
- |
-
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Talking Heads — Konten und Banken — M1 und Wintel — Internet Tips — pretix — venueless — pretalx — Virtuelle Events — Videotechnik — Mikrofone und Headsets — Licht — Ethernet
- -Heute dreht sich alles um Events. Wir begrüßen rami als Gast in der Sendung, der das Projekt pretix losgetreten hat, dass ein Ticketing-System für Veranstaltungen aller Art ist. Wir sprechen über Online-Events und in was von der Pandemie-Zeit übrig bleiben wird, wenn wir uns wieder in der Realität treffen können. Dazu gibt es noch eine Reihe von Tips für Technik zur Durchführung von Audio- und Videosessions.
- -Dauer: 3:30:42
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Letty - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--hukl - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--rami - | -
-
-
- |
-- | -
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Ultraschall — M1 Macs — Big Sur
- -Nach zwei Wochen und vor allem nachdem manche von uns bereits ein paar der neuen M1 Macs erhalten habe dreht sich hier ein weiteres Mal alles um die neuen Computer. Wir teilen erste Erfahrungen und Einschätzungen und sind überhaupt sehr aufgeregt. Außerdem ist Ralf zu Gast und berichtet von den Fortschritten beim Ultraschall-Projekt.
- -Dauer: 3:41:53
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Dominik Wagner - | -
-
-
- |
-- | -
|
-
- |
--Denis Ahrens - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Ralf Stockmann - | -
-
-
- |
-- | -
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Kontowechsel — Pausenende — Elektrourlaub — Tesla Battery Day — iPhone 12 — Apple Watch — iOS 14 und AirPods Pro — Augmented Reality — Big Sur — ARM-Macs — 5G — Audio auf dem Mac — Audiointerfaces — Küchenkram
- -We are back. Nach einer längeren, im Kern ungeplanten aber trotzdem notwenidgen Verschnaufpause ist die Freak Show wieder am Start, denn wir mussten dringend mal wieder mehr über Macs reden. Wir gehen die neue Produkten von Apple der letzten Woche durch und natürlich legen wir einen Schwerpunkt auf die neuen ARM-Macs, die nun endlich vorgestellt worden. Dazu ein wenig Corona-Küchenkram rund um Waffeleisen und scharfe Messer.
- -Dauer: 3:50:53
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--hukl - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Denis Ahrens - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--roddi - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Dominik Wagner - | -
-
-
- |
-- | -
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Eine Spezialausgabe der Freak Show mit dem Schwerpunkt Elektromobilität
- -Wir haben schon oft darüber gesprochen, aber so richtig Ahnung haben wir ja eh nicht. Deswegen haben wir (Roddi, Denis, Tim) uns den Philipp vom cleaneletric Podcast eingeladen um mal ganz ausführlich über den Stand der Dinge in Sachen Elektromobilität in Deutschland und dem Rest der Welt zu sprechen. Dabei versuchen wir zu ergründen, woran es noch hapert und wo vielleicht der Umstieg für viele schon näher liegt, als diese selbst noch glauben.
- -Dauer: 3:19:11
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Denis Ahrens - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--roddi - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Philipp Hellwig - | -
-
-
- |
-- | -
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Die 250. Sendung der Freak Show über die WWDC 2020 und andere Dinge
- -Nun sind schon über 12 Jahre vergangen seitdem die Freak Show (damals noch unter anderem Namen) ans Netz gegangen ist. Wir bedanken uns für die jahrelange Treue unserer Hörerinnen und Hörer, die mit ihrem Feedback immer dazu beigetragen haben, dass uns die Lust am Projekt nie vergangen ist.
-
-Heute sind wir dann auch standesgemäß vollzählig angetreten und dazu haben wir uns auch noch den Dominik dazugehört, um die Akustik für Euch komplett unübersichtlich zu gestalten.
Dauer: 3:37:40
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Dominik Wagner - | -
-
-
- |
-- | -
|
-
- |
--hukl - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Clemens Schrimpe - | -
-
-
- |
-- | -
|
-
- |
--Letty - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Tala - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--roddi - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Denis Ahrens - | -
-
-
- |
-
-
-
- |
-
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Die Corona-Situation — Masken und Zauberwürfel — Gesundheitstechnologie — Differential Privacy — macOS auf ARM — Programmiersprachen
- -Heute begrüßen wir in unserer Runde den Menschen, der jetzt schon seit Jahren auf die eine oder andere Art und Weise zu hören war, nur noch nicht selbst: Rainer, der seinerzeit die Produktion der Previouslys von David übernommen hatte nimmt Platz in unserer Runde. Inhaltlich dreht sich natürlich viel um Das Unvermeidliche Thema ™, aber wir versuchen noch das eine oder andere sinnvolle zur Debatte hinzuzufügen. Und wir mussten mal wieder über Macs reden. Viel Spaß dabei.
- -Dauer: 4:05:36
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Letty - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Denis Ahrens - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Clemens Schrimpe - | -
-
-
- |
-- | -
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
| -Support - | -|||
|
-
- |
-
-Rainer
- Previously - |
-
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Die erste Freak Show, die komplett remote stattfindet
- -Nun ist es es also soweit: der Coronavirus zwingt auch die Freak Show in den Zwangsabstand und so wird das erste mal nicht live und lebendig in der Metaebene aufgenommen, sondern wir finden uns alle digital zusammen. Klappt trotzdem irgendwie und wir bieten seit langem mal wieder ein volles Team. Die Themen liegen natürlich auf der Hand, aber wir reden auch mal wieder über Macs. Viel Spaß.
- -Dauer: 3:23:01
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Letty - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--hukl - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Tala - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--roddi - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Clemens Schrimpe - | -
-
-
- |
-- | -
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Umziehen — Podlove Web Player — Podlove Publisher — WordPress — Podlove Radiator — Küchengeräte
- -Heute finden wir uns mit einer ganzen Reihe an Wiedergängern aus den letzten Jahren zusammen. Wir begrüßen Dennis Morhardt, Alexander Heimbuch und Eric Teubert in unserer Runde und reden viel über Podlove und Podcasting, aber auch darüber, wie man sich richtig die Hände wäscht und ohne welchen Küchengeräte ein Leben im 21. Jahrhundert unvorstellbar ist.
- -Dauer: 4:02:47
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--roddi - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Eric Teubert - | -
-
-
- |
-- | -
|
-
- |
--Dennis Morhardt - | -
-
-
- |
-- | -
|
-
- |
--Alexander Heimbuch - | -
-
-
- |
-
-
-
- |
-
| -Support - | -|||
|
-
- |
-
-Rainer
- Previously on Freak Show - |
-
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -
Diese Sendung soll nie aufhören?
-Unterstütze die Metaebene mit einer Spende
-
Monty Python und Humor — Coronavirus — Living the Future — Raumzeit — Space — Tesla und die Automobilindustrie — Sozialraum Auto — Promotion — Billie Eilish
- -Eine kleine Runde mit Gast. Tala ist und Gregor ist mal wieder dazugestossen. Wenig überraschend gehen wir diverse Dinge stark auf der Metaebene an (im doppelten Wortsinne). Wir diskutieren Humor, Viren, Texteditoren, das Leben in der Zukunft und warum das mit der Abkehr vom Auto noch eine Weile dauern wird.
- -Dauer: 4:02:58
- - - -| -On Air - | -|||
|---|---|---|---|
|
-
- |
--Tim Pritlove - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Tala - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Gregor Sedlag - | -
-
-
- |
-- | -
| -Support - | -|||
|
-
- |
--Rainer - | -
-
-
- |
-
-
-
- |
-
|
-
- |
--Studio Link On Air - | -
-
-
- |
-- | -