diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py
index ae573fdfc08d660f9084dcbf9c2de491c78a54e5..f208d0a2342563c8c46f753140c8c9097c933c99 100644
--- a/python/podcast/podcast.py
+++ b/python/podcast/podcast.py
@@ -9,7 +9,7 @@ from feedparser import FeedParserDict
from peewee import CharField, TextField, BooleanField, IntegerField, ModelSelect, DoesNotExist, FloatField, \
IntegrityError
from podcast.persistent_log import persist_log, LogType
-from podcast.util import movePost
+from podcast.util import movePost, download_image_into_storage
sys.path.append("/usr/share/podqast/python")
@@ -28,7 +28,6 @@ from podcast import util, feedutils
from email.utils import mktime_tz, parsedate_tz
from calendar import timegm
-import hashlib
import os
import logging
import pyotherside
@@ -132,9 +131,7 @@ class Podcast(BaseModel):
self.logo_url = feed.feed.image.href
logger.debug("podcast new logo_url: %s", self.logo_url)
old_path = self.logo_path
- if self.logo_path:
- self.logo_path = None
- self.__download_icon()
+ self.logo_path = download_image_into_storage(self.logo_url, Constants().iconpath) or None
if old_path:
util.delete_file(old_path)
except:
@@ -291,35 +288,6 @@ class Podcast(BaseModel):
else:
return None
- def __download_icon(self):
- """
- Download icon
- """
-
- iconpath = Constants().iconpath
- rdot = self.logo_url.rfind(".")
- extexcl = self.logo_url.rfind("?")
- if extexcl > 0:
- ext = self.logo_url[rdot + 1: extexcl]
- else:
- 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
- )
-
- logo_path = os.path.join(iconpath, name)
- try:
- util.dl_from_url(self.logo_url, logo_path)
- except:
- logo_path = ""
- logger.info(
- "could not load icon: %s", str(sys.exc_info()[0])
- )
- self.logo_path = logo_path
- else:
- self.logo_path = ""
-
def refresh(self, moveto, limit=0, full_refresh=False) -> Iterator[Tuple[int, 'Podpost']]:
"""
Refresh podcast and return list of new entries
diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py
index cc47b9fd33ec94b225f32c49fcab6d2de5d8e658..a684e6174be112f1101ae8634d841234c5e58686 100644
--- a/python/podcast/podpost.py
+++ b/python/podcast/podpost.py
@@ -324,28 +324,15 @@ class Podpost(BaseModel):
"""
Download icon
"""
-
+ if not self.logo_url:
+ return
if self.logo_path:
if os.path.exists(self.logo_path):
return
-
- iconpath = Constants().iconpath
- rdot = self.logo_url.rfind(".")
- 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
- )
-
- logo_path = os.path.join(iconpath, name)
- try:
- util.dl_from_url(self.logo_url, logo_path)
- except:
- logo_path = ""
-
- self.logo_path = logo_path
- else:
- self.logo_path = ""
+ try:
+ self.logo_path = util.download_image_into_storage(self.logo_url, Constants().iconpath) or None
+ except:
+ self.logo_path = None
def play(self) -> Dict[str,object]:
"""
diff --git a/python/podcast/util.py b/python/podcast/util.py
index da747262a1d9404834817e57991f5c41ca5b0390..c850a594d3fffde3b6fbd50fcc579edace816f26 100644
--- a/python/podcast/util.py
+++ b/python/podcast/util.py
@@ -1,23 +1,28 @@
"""
some utilities
"""
+import base64
+import datetime
+import hashlib
import logging
import os
-import urllib.request
-import urllib.error
+import shutil
+import subprocess
+import tempfile
import time
-import datetime
+import urllib.error
+import urllib.request
from collections import deque
from enum import unique, IntEnum
from itertools import count
-from typing import List, Iterator, Tuple
+from typing import List, Iterator, Tuple, Optional
+from xml.dom import minidom
from xml.etree import ElementTree as ET
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
-from xml.dom import minidom
+
import pyotherside
+
import mutagen
-import hashlib
-import base64
from mutagen.flac import Picture, error as FLACError
logger = logging.getLogger(__name__)
@@ -114,20 +119,53 @@ def s_to_year(secs):
return time.strftime("%Y-%m-%d", time.gmtime(secs))
-def dl_from_url(url, path):
+def download_image_into_storage(url: str, iconpath: str) -> Optional[str]:
+ name = hashlib.sha256(url.encode()).hexdigest()
+ save_path = os.path.join(iconpath, name)
+ try:
+ download_image(url, save_path)
+ return save_path
+ except Exception:
+ logger.exception("could not load icon")
+ return None
+
+
+def download_image(url, path, max_file_size_in_mb=20):
"""
- Download a file from url and save to path
+ downloads the image from url to path. checks if it is actually an image
+ :param url: url of the image
+ :param path: path to save to
+ :param max_file_size_in_mb: raises an exception if the file is larger than that
+ :return: nothing
"""
+ req = urllib.request.Request(url, data=None, headers={"User-Agent": __get_http_agent(url)})
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
+ with urllib.request.urlopen(req) as response:
+ shutil.copyfileobj(response, tmp_file)
+ tmp_file.flush()
+
+ filesize = get_file_length(tmp_file.name)
+ if filesize > 1024 * 1024 * max_file_size_in_mb: # panic if file is larger than 20 mb
+ raise ValueError("the downloaded file was had size %d bytes which is larger than %d bytes" % (
+ filesize, max_file_size_in_mb * 1024 * 1024))
+ __assert_is_image_type(tmp_file.name, url)
+ shutil.move(tmp_file.name, path)
+
+
+def __assert_is_image_type(path: str, url: str):
+ return True # file util is not allowed with sailjail unfortunatley...
+ result = subprocess.check_output(['file', '--mime-type', path], text=True)
+ mime_type = result.split(maxsplit=1)[1]
+ if not mime_type.startswith("image/"):
+ raise ValueError("expected an image from '%s' on path %s but detected mimetype '%s'" % (url, path, mime_type))
+
+def __get_http_agent(url):
if url.find("tumblr.com") >= 0:
agent = user_agent2
else:
agent = user_agent
-
- rsp = urllib.request.Request(url, data=None, headers={"User-Agent": agent})
- response = urllib.request.urlopen(rsp)
- with open(path, "wb") as fhandle:
- fhandle.write(response.read())
+ return agent
def dl_from_url_progress(url, path):
@@ -139,10 +177,7 @@ def dl_from_url_progress(url, path):
count = 0
p1 = 0
- if url.find("tumblr.com") >= 0:
- agent = user_agent2
- else:
- agent = user_agent
+ agent = __get_http_agent(url)
req = urllib.request.Request(url, data=None, headers={"User-Agent": agent})
try:
@@ -415,6 +450,7 @@ def down_audio_icon(afile, downpath):
return filepath
+
@unique
class MovePostToPage(IntEnum):
INBOX = 0
@@ -423,7 +459,7 @@ class MovePostToPage(IntEnum):
HISTORY = 3
def get_humanized_name(self):
- return ["Inbox","Queue","Queue","History"][self]
+ return ["Inbox", "Queue", "Queue", "History"][self]
def movePost(move: int, postid):
diff --git a/qml/components/EpisodeImage.qml b/qml/components/EpisodeImage.qml
index f24ce90e279dfac44f6f4bc046e580cb803412c4..591de93b6f8b9aed3ebc5343bacb9914c68d7b8d 100644
--- a/qml/components/EpisodeImage.qml
+++ b/qml/components/EpisodeImage.qml
@@ -52,7 +52,8 @@ Thumbnail {
height: Theme.iconSizeLarge
sourceSize.width: Theme.iconSizeLarge
sourceSize.height: Theme.iconSizeLarge
- source: model.podcast_logo === "" ? "../../images/podcast.png" : model.podcast_logo
+ source: (model.podcast_logo === "" || model === undefined
+ || model.podcast_logo === undefined) ? "../../images/podcast.png" : model.podcast_logo
Rectangle {
id: dlstatus
visible: showDownladingState
diff --git a/qml/components/hints/HintEpisodeImage.qml b/qml/components/hints/HintEpisodeImage.qml
index 496c7829a461a64224995382c1180b6908bd5eb1..9dfd64317f806b75ad4a796f5e4344d7c2b0d7a2 100644
--- a/qml/components/hints/HintEpisodeImage.qml
+++ b/qml/components/hints/HintEpisodeImage.qml
@@ -11,7 +11,6 @@ EpisodeImage {
showIsListened: relativeIndex === 2
percentage: relativeIndex === 0 ? 100 : 0
anchors.fill: centeredComponent
- property string logo_url: "../../images/podcast.png"
- width: Theme.iconSizeExtraLarge
- height: Theme.iconSizeExtraLarge
+ width: Theme.itemSizeHuge
+ height: Theme.itemSizeHuge
}
diff --git a/qml/components/hints/PlayerHint.qml b/qml/components/hints/PlayerHint.qml
index d500d4f2e18681f352f8556b3b09363c23aa612b..78e85398f74368f6e0e6a0586e4fe7e200063f11 100644
--- a/qml/components/hints/PlayerHint.qml
+++ b/qml/components/hints/PlayerHint.qml
@@ -8,12 +8,12 @@ SlideShowHint {
"text": qsTr("Select if the next item of the playlist should be played automatically"),
"direction": TouchInteraction.Down,
"interactionMode": TouchInteraction.EdgeSwipe,
- "loops": 3
+ "loops": 0
}, {
"text": qsTr("Click the image to adjust the playrate"),
"direction": 0,
"interactionMode": 0,
"interactionHidden": true,
- "loops": 3
+ "loops": 0
}]
}
diff --git a/qml/lib/hints/SlideShowHint.qml b/qml/lib/hints/SlideShowHint.qml
index c121957c0c8d586c838c87acce7bfc47abe6e381..ce48eff3c0fd4b5dbe3cefbcca278d93f80fab39 100644
--- a/qml/lib/hints/SlideShowHint.qml
+++ b/qml/lib/hints/SlideShowHint.qml
@@ -27,7 +27,8 @@ Item {
width: Theme.iconSizeExtraLarge
height: Theme.iconSizeExtraLarge
property int index: slideNr
- source: !slideloaded ? "" : slideconfig.centeredComponent
+ source: (!slideloaded || slideconfig.centeredComponent
+ === undefined) ? "" : slideconfig.centeredComponent
visible: !slideloaded ? false : slideconfig.centeredComponent !== undefined
active: !slideloaded ? false : slideconfig.centeredComponent !== undefined
onActiveChanged: console.info("loading component " + source)
diff --git a/test/test_podcast.py b/test/test_podcast.py
index 9531750172ca112727e140252a030a86dd41e011..b581dfb0f825876f5e606d141ab3b3c27e462b75 100644
--- a/test/test_podcast.py
+++ b/test/test_podcast.py
@@ -2,6 +2,8 @@
Test Podcast functions
"""
import logging
+import os
+from io import StringIO
from typing import Tuple, List
import httpretty
@@ -21,6 +23,11 @@ from test import xml_headers, read_testdata
logger = logging.getLogger(__name__)
+def request_callback(request, uri, response_headers):
+ with open(os.path.join(os.path.dirname(__file__), "testdata/empty_image.png"), "rb") as file:
+ return [200, response_headers, file.read()]
+
+
@httpretty.activate(allow_net_connect=False)
def test_feed_entry():
"""
@@ -35,15 +42,14 @@ def test_feed_entry():
url_fs = 'https://freakshow.fm/feed/opus/'
HTTPretty.register_uri(HTTPretty.GET, url_fs,
body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers)
- image = b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00}\x00}\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x00\xf7\xfa(\xa2\x80\n(\xa2\x80\n(\xa2\x80\n(\xa2\x80\n(\xa2\x80\n(\xa2\x80\n(\xa2\x80\n(\xa2\x80\n(\xa2\x80?\xff\xd9'
HTTPretty.register_uri(HTTPretty.GET, 'https://meta.metaebene.me/media/mm/freakshow-logo-1.0.jpg',
- body=image)
+ body=request_callback)
HTTPretty.register_uri(HTTPretty.GET,
'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',
- body=image)
+ body=request_callback)
HTTPretty.register_uri(HTTPretty.GET,
'https://elroy.twit.tv/sites/default/files/styles/twit_album_art_2048x2048/public/images/shows/security_now/album_art/audio/sn_albumart_mask.jpg?itok=scC8c-TL',
- body=image)
+ body=request_callback)
seen_postids = {}
@@ -207,6 +213,7 @@ def test_podcastlist_refresh(refreshable_podcast_fixture):
assert plist.get_podcast_count() == 1
assert list(plist.refresh(0)) == [(2, p.title, 'Hello, I am a new fake episode for testing!!öäü', 0)]
+
@pytest.mark.parametrize("refreshable_podcast_fixture", ["testdata/fakefeed"], indirect=True)
def test_move(refreshable_podcast_fixture):
p, episodes = refreshable_podcast_fixture
@@ -222,7 +229,6 @@ def test_move(refreshable_podcast_fixture):
assert len(QueueData.get_queue()) == 1
-
@pytest.fixture
def refreshable_podcast_fixture(request) -> Tuple[Podcast, List[Podpost]]:
"""
@@ -247,8 +253,6 @@ def refreshable_podcast_fixture(request) -> Tuple[Podcast, List[Podpost]]:
assert invoked > 0
-
-
@httpretty.activate(allow_net_connect=False)
def test_pagination_stops():
url_f1 = "https://fakefeed.com/"
diff --git a/test/testdata/empty_image.png b/test/testdata/empty_image.png
new file mode 100644
index 0000000000000000000000000000000000000000..f11f13440cc18625138ff5ddb4a5d9937ac56f64
Binary files /dev/null and b/test/testdata/empty_image.png differ
diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts
index c0e4996ee1edd893afbb559e9d8aaf538183cefa..dfc3338510c89287db890e5609a8ffa6c19fd253 100644
--- a/translations/harbour-podqast-de.ts
+++ b/translations/harbour-podqast-de.ts
@@ -128,7 +128,7 @@
- <b>Whats New?</b><ul><li>Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.</li><li>If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).</li><li>Podcasts are now sorted alphabetically, we hope you like that!</li><li>Gpodder should work again.</li><li>Check the new Log Page next to the settings.</li><li>If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.</li><li>Also more speed, bugfixes and code cleanups.</li></ul><br>If you want to contribute to podQast, you can help translating the app or report issues on GitLab.
+ whatsnew textwhatsnew section of the migration
diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts
index c80c63371246b2841c182391cf9e4f9de12a87bc..176a293f61c3612526eafa647ed7d43b24668cd5 100644
--- a/translations/harbour-podqast-es.ts
+++ b/translations/harbour-podqast-es.ts
@@ -128,7 +128,7 @@
- <b>Whats New?</b><ul><li>Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.</li><li>If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).</li><li>Podcasts are now sorted alphabetically, we hope you like that!</li><li>Gpodder should work again.</li><li>Check the new Log Page next to the settings.</li><li>If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.</li><li>Also more speed, bugfixes and code cleanups.</li></ul><br>If you want to contribute to podQast, you can help translating the app or report issues on GitLab.
+ whatsnew textwhatsnew section of the migration
diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts
index 0f66804e061f2d9d11cde4366a085144f73d40a7..d19d84510089a31b383a1e911369abd80e61f46b 100644
--- a/translations/harbour-podqast-fr.ts
+++ b/translations/harbour-podqast-fr.ts
@@ -128,7 +128,7 @@
- <b>Whats New?</b><ul><li>Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.</li><li>If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).</li><li>Podcasts are now sorted alphabetically, we hope you like that!</li><li>Gpodder should work again.</li><li>Check the new Log Page next to the settings.</li><li>If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.</li><li>Also more speed, bugfixes and code cleanups.</li></ul><br>If you want to contribute to podQast, you can help translating the app or report issues on GitLab.
+ whatsnew textwhatsnew section of the migration
diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts
index 7b652c33e05038fa4aa19f1e11b3862e88482813..65693375b044c1facced95edd396a33ceeff50cf 100644
--- a/translations/harbour-podqast-sv.ts
+++ b/translations/harbour-podqast-sv.ts
@@ -128,7 +128,7 @@
- <b>Whats New?</b><ul><li>Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.</li><li>If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).</li><li>Podcasts are now sorted alphabetically, we hope you like that!</li><li>Gpodder should work again.</li><li>Check the new Log Page next to the settings.</li><li>If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.</li><li>Also more speed, bugfixes and code cleanups.</li></ul><br>If you want to contribute to podQast, you can help translating the app or report issues on GitLab.
+ whatsnew textwhatsnew section of the migration
diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts
index 5b494c2195f5dc1c0f4c7ca8ef88655d9c294d8b..64e140442450ed3c0ca6b078cb711734b629573c 100644
--- a/translations/harbour-podqast-zh_CN.ts
+++ b/translations/harbour-podqast-zh_CN.ts
@@ -128,7 +128,7 @@
- <b>Whats New?</b><ul><li>Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.</li><li>If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).</li><li>Podcasts are now sorted alphabetically, we hope you like that!</li><li>Gpodder should work again.</li><li>Check the new Log Page next to the settings.</li><li>If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.</li><li>Also more speed, bugfixes and code cleanups.</li></ul><br>If you want to contribute to podQast, you can help translating the app or report issues on GitLab.
+ whatsnew textwhatsnew section of the migration
diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts
index 90be4447db064bc0084b71a483bd61bfdaee53ab..125c00f57e5c76b5c204dc5c5cb7123a35079806 100644
--- a/translations/harbour-podqast.ts
+++ b/translations/harbour-podqast.ts
@@ -128,7 +128,7 @@
- <b>Whats New?</b><ul><li>Some of you lost episodes in the last migration. Because we found this pretty sad, we're trying to recover them now.</li><li>If you are missing old episodes of feeds in general and this didn't help, try the force refresh button in the settings (We added support for Pagination).</li><li>Podcasts are now sorted alphabetically, we hope you like that!</li><li>Gpodder should work again.</li><li>Check the new Log Page next to the settings.</li><li>If you're streaming an episode and the download finishes, we're now switching to the downloaded file automatically.</li><li>Also more speed, bugfixes and code cleanups.</li></ul><br>If you want to contribute to podQast, you can help translating the app or report issues on GitLab.
+ whatsnew textwhatsnew section of the migration