Merge branch 'master' into dev-adaptive
This commit is contained in:
commit
46dbc80d8b
|
@ -6,7 +6,7 @@ jobs:
|
||||||
xcode: "13.2.1"
|
xcode: "13.2.1"
|
||||||
shell: /bin/bash --login -o pipefail
|
shell: /bin/bash --login -o pipefail
|
||||||
environment:
|
environment:
|
||||||
- BUNDLE_TAG: 22.7.28
|
- BUNDLE_TAG: 22.8.27
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: >
|
- run: >
|
||||||
|
@ -15,7 +15,7 @@ jobs:
|
||||||
saved_hash=$(awk '{print $1;}' < "pythonbase-$BUNDLE_TAG.zip.sha256");
|
saved_hash=$(awk '{print $1;}' < "pythonbase-$BUNDLE_TAG.zip.sha256");
|
||||||
comp_hash=$(openssl sha256 "pythonbase-$BUNDLE_TAG.zip" | awk '{print $2;}');
|
comp_hash=$(openssl sha256 "pythonbase-$BUNDLE_TAG.zip" | awk '{print $2;}');
|
||||||
if [ "$saved_hash" != "$comp_hash" ]; then echo "E: $saved_hash != $comp_hash"; exit 1; else echo "valid hash"; fi;
|
if [ "$saved_hash" != "$comp_hash" ]; then echo "E: $saved_hash != $comp_hash"; exit 1; else echo "valid hash"; fi;
|
||||||
LC_CTYPE=C.UTF-8 LANG=C.UTF-8 tools/mac-osx/release_on_mac.sh "$(pwd)/pythonbase-$BUNDLE_TAG.zip";
|
LC_CTYPE=C.UTF-8 LANG=C.UTF-8 tools/mac-osx/release_on_mac.sh "$(pwd)/pythonbase-$BUNDLE_TAG.zip" || exit 1;
|
||||||
rm -Rf tools/mac-osx/_build/{gPodder.app,*.deps.zip*,gPodder.contents,run-*,gpo,gpodder-migrate2tres}
|
rm -Rf tools/mac-osx/_build/{gPodder.app,*.deps.zip*,gPodder.contents,run-*,gpo,gpodder-migrate2tres}
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: tools/mac-osx/_build/
|
path: tools/mac-osx/_build/
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ['3.10']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
20
bin/gpo
20
bin/gpo
|
@ -88,6 +88,7 @@ import pydoc
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -438,10 +439,20 @@ class gPodderCli(object):
|
||||||
if podcast is None:
|
if podcast is None:
|
||||||
self._error(_('You are not subscribed to %s.') % url)
|
self._error(_('You are not subscribed to %s.') % url)
|
||||||
else:
|
else:
|
||||||
|
# Clean up downloads and download directories
|
||||||
|
common.clean_up_downloads()
|
||||||
|
|
||||||
podcast.delete()
|
podcast.delete()
|
||||||
self._db.commit()
|
self._db.commit()
|
||||||
self._error(_('Unsubscribed from %s.') % url)
|
self._error(_('Unsubscribed from %s.') % url)
|
||||||
|
|
||||||
|
# Delete downloaded episodes
|
||||||
|
podcast.remove_downloaded()
|
||||||
|
|
||||||
|
# TODO: subscribe and unsubscribe need to sync with mygpo
|
||||||
|
# Upload subscription list changes to the web service
|
||||||
|
# self.mygpo_client.on_unsubscribe([podcast.url])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_episode_new(self, episode):
|
def is_episode_new(self, episode):
|
||||||
|
@ -481,13 +492,18 @@ class gPodderCli(object):
|
||||||
return "disabled"
|
return "disabled"
|
||||||
return "enabled"
|
return "enabled"
|
||||||
|
|
||||||
title, url, status = podcast.title, podcast.url, \
|
title, url, description, link, status = (
|
||||||
feed_update_status_msg(podcast)
|
podcast.title, podcast.url, podcast.description, podcast.link,
|
||||||
|
feed_update_status_msg(podcast))
|
||||||
|
description = '\n'.join(textwrap.wrap(description, subsequent_indent=' ' * 8))
|
||||||
episodes = self._episodesList(podcast)
|
episodes = self._episodesList(podcast)
|
||||||
episodes = '\n '.join(episodes)
|
episodes = '\n '.join(episodes)
|
||||||
self._pager("""
|
self._pager("""
|
||||||
Title: %(title)s
|
Title: %(title)s
|
||||||
URL: %(url)s
|
URL: %(url)s
|
||||||
|
Description:
|
||||||
|
%(description)s
|
||||||
|
Link: %(link)s
|
||||||
Feed update is %(status)s
|
Feed update is %(status)s
|
||||||
|
|
||||||
Episodes:
|
Episodes:
|
||||||
|
|
12
makefile
12
makefile
|
@ -38,8 +38,8 @@ UIFILES=$(wildcard share/gpodder/ui/gtk/*.ui \
|
||||||
share/gpodder/ui/adaptive/*.ui)
|
share/gpodder/ui/adaptive/*.ui)
|
||||||
UIFILES_H=$(subst .ui,.ui.h,$(UIFILES))
|
UIFILES_H=$(subst .ui,.ui.h,$(UIFILES))
|
||||||
GETTEXT_SOURCE=$(wildcard src/gpodder/*.py \
|
GETTEXT_SOURCE=$(wildcard src/gpodder/*.py \
|
||||||
src/gpodder/gtkui/*.py \
|
src/gpodder/gtkui/*.py \
|
||||||
src/gpodder/gtkui/interface/*.py \
|
src/gpodder/gtkui/interface/*.py \
|
||||||
src/gpodder/gtkui/desktop/*.py \
|
src/gpodder/gtkui/desktop/*.py \
|
||||||
src/gpodder/plugins/*.py \
|
src/gpodder/plugins/*.py \
|
||||||
share/gpodder/extensions/*.py)
|
share/gpodder/extensions/*.py)
|
||||||
|
@ -72,13 +72,12 @@ lint:
|
||||||
pycodestyle share src/gpodder tools bin/* *.py
|
pycodestyle share src/gpodder tools bin/* *.py
|
||||||
isort -q $(ISORTOPTS) || isort --df $(ISORTOPTS)
|
isort -q $(ISORTOPTS) || isort --df $(ISORTOPTS)
|
||||||
|
|
||||||
|
|
||||||
release: distclean
|
release: distclean
|
||||||
$(PYTHON) setup.py sdist
|
$(PYTHON) setup.py sdist
|
||||||
|
|
||||||
releasetest: unittest $(DESKTOP_FILES) $(POFILES)
|
releasetest: unittest $(DESKTOP_FILES) $(POFILES)
|
||||||
for f in $(DESKTOP_FILES); do desktop-file-validate $$f; done
|
for f in $(DESKTOP_FILES); do desktop-file-validate $$f || exit 1; done
|
||||||
for f in $(POFILES); do msgfmt --check $$f; done
|
for f in $(POFILES); do msgfmt --check $$f || exit 1; done
|
||||||
|
|
||||||
$(GPODDER_SERVICE_FILE): $(GPODDER_SERVICE_FILE_IN)
|
$(GPODDER_SERVICE_FILE): $(GPODDER_SERVICE_FILE_IN)
|
||||||
sed -e 's#__PREFIX__#$(PREFIX)#' $< >$@
|
sed -e 's#__PREFIX__#$(PREFIX)#' $< >$@
|
||||||
|
@ -140,7 +139,6 @@ $(MESSAGES): $(GETTEXT_SOURCE)
|
||||||
messages-force:
|
messages-force:
|
||||||
xgettext --from-code=utf-8 -LPython -k_:1 -kN_:1 -kN_:1,2 -kn_:1,2 -o $(MESSAGES) $(GETTEXT_SOURCE)
|
xgettext --from-code=utf-8 -LPython -k_:1 -kN_:1 -kN_:1,2 -kn_:1,2 -o $(MESSAGES) $(GETTEXT_SOURCE)
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
# This only works in a Git working commit, and assumes that the local Git
|
# This only works in a Git working commit, and assumes that the local Git
|
||||||
|
@ -171,5 +169,3 @@ distclean: clean
|
||||||
.PHONY: help unittest release releasetest install manpages clean distclean messages headlink lint revbump
|
.PHONY: help unittest release releasetest install manpages clean distclean messages headlink lint revbump
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
682
po/cs_CZ.po
682
po/cs_CZ.po
File diff suppressed because it is too large
Load Diff
682
po/es_ES.po
682
po/es_ES.po
File diff suppressed because it is too large
Load Diff
682
po/es_MX.po
682
po/es_MX.po
File diff suppressed because it is too large
Load Diff
694
po/fa_IR.po
694
po/fa_IR.po
File diff suppressed because it is too large
Load Diff
690
po/id_ID.po
690
po/id_ID.po
File diff suppressed because it is too large
Load Diff
682
po/ko_KR.po
682
po/ko_KR.po
File diff suppressed because it is too large
Load Diff
690
po/messages.pot
690
po/messages.pot
File diff suppressed because it is too large
Load Diff
682
po/pt_BR.po
682
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
682
po/zh_CN.po
682
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
# Example script that can be used as post-play extension in media players
|
# Example script that can be used as post-play extension in media players
|
||||||
#
|
#
|
||||||
# Set the configuration options "audio_played_dbus" and "video_played_dbus"
|
# Set the configuration options "audio_played_dbus" and "video_played_dbus"
|
||||||
|
|
|
@ -75,7 +75,9 @@ class gPodderExtension:
|
||||||
close_fds=True)
|
close_fds=True)
|
||||||
result = ffmpeg.wait()
|
result = ffmpeg.wait()
|
||||||
util.delete_file(list_filename)
|
util.delete_file(list_filename)
|
||||||
util.idle_add(lambda: indicator.on_finished())
|
|
||||||
|
indicator.on_finished()
|
||||||
|
|
||||||
util.idle_add(lambda: self.gpodder.show_message(
|
util.idle_add(lambda: self.gpodder.show_message(
|
||||||
_('Videos successfully converted') if result == 0 else
|
_('Videos successfully converted') if result == 0 else
|
||||||
_('Error converting videos'),
|
_('Error converting videos'),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
####
|
####
|
||||||
# 01/2011 Bernd Schlapsi <brot@gmx.info>
|
# 01/2011 Bernd Schlapsi <brot@gmx.info>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Requirements: apt-get install python-kaa-metadata ffmpeg python-dbus
|
# Requirements: apt-get install python-kaa-metadata ffmpeg python-dbus
|
||||||
# To use, copy it as a Python script into ~/.config/gpodder/extensions/rockbox_mp4_convert.py
|
# To use, copy it as a Python script into ~/.config/gpodder/extensions/rockbox_mp4_convert.py
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
####
|
####
|
||||||
# 01/2011 Bernd Schlapsi <brot@gmx.info>
|
# 01/2011 Bernd Schlapsi <brot@gmx.info>
|
||||||
|
|
|
@ -13,7 +13,7 @@ from gpodder import util
|
||||||
|
|
||||||
import gi # isort:skip
|
import gi # isort:skip
|
||||||
gi.require_version('Unity', '7.0') # isort:skip
|
gi.require_version('Unity', '7.0') # isort:skip
|
||||||
from gi.repository import GObject, Unity # isort:skip
|
from gi.repository import GLib, Unity # isort:skip
|
||||||
|
|
||||||
|
|
||||||
_ = gpodder.gettext
|
_ = gpodder.gettext
|
||||||
|
@ -59,4 +59,4 @@ class gPodderExtension:
|
||||||
self.launcher_entry = None
|
self.launcher_entry = None
|
||||||
|
|
||||||
def on_download_progress(self, progress):
|
def on_download_progress(self, progress):
|
||||||
GObject.idle_add(self.launcher_entry.set_progress, float(progress))
|
GLib.idle_add(self.launcher_entry.set_progress, float(progress))
|
||||||
|
|
|
@ -12,8 +12,10 @@ import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yt_dlp as youtube_dl
|
import yt_dlp as youtube_dl
|
||||||
|
program_name = 'yt-dlp'
|
||||||
except:
|
except:
|
||||||
import youtube_dl
|
import youtube_dl
|
||||||
|
program_name = 'youtube-dl'
|
||||||
|
|
||||||
import gpodder
|
import gpodder
|
||||||
from gpodder import download, feedcore, model, registry, util, youtube
|
from gpodder import download, feedcore, model, registry, util, youtube
|
||||||
|
@ -426,6 +428,14 @@ class gPodderYoutubeDL(download.CustomDownloader):
|
||||||
"""
|
"""
|
||||||
if not self.force and not self.my_config.manage_downloads:
|
if not self.force and not self.my_config.manage_downloads:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
try: # Reject URLs linking to known media files
|
||||||
|
(_, ext) = util.filename_from_url(episode.url)
|
||||||
|
if util.file_type_by_extension(ext) is not None:
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if self.is_supported_url(episode.url):
|
if self.is_supported_url(episode.url):
|
||||||
return YoutubeCustomDownload(self, episode.url, episode)
|
return YoutubeCustomDownload(self, episode.url, episode)
|
||||||
|
|
||||||
|
@ -439,12 +449,10 @@ class gPodderExtension:
|
||||||
|
|
||||||
def on_load(self):
|
def on_load(self):
|
||||||
self.ytdl = gPodderYoutubeDL(self.container.manager.core.config, self.container.config)
|
self.ytdl = gPodderYoutubeDL(self.container.manager.core.config, self.container.config)
|
||||||
logger.info('Registering youtube-dl.')
|
logger.info('Registering youtube-dl. (using %s %s)' % (program_name, youtube_dl.version.__version__))
|
||||||
registry.feed_handler.register(self.ytdl.fetch_channel)
|
registry.feed_handler.register(self.ytdl.fetch_channel)
|
||||||
registry.custom_downloader.register(self.ytdl.custom_downloader)
|
registry.custom_downloader.register(self.ytdl.custom_downloader)
|
||||||
|
|
||||||
logger.debug('youtube-dl %s' % youtube_dl.version.__version__)
|
|
||||||
|
|
||||||
if youtube_dl.utils.version_tuple(youtube_dl.version.__version__) < youtube_dl.utils.version_tuple(want_ytdl_version):
|
if youtube_dl.utils.version_tuple(youtube_dl.version.__version__) < youtube_dl.utils.version_tuple(want_ytdl_version):
|
||||||
logger.error(want_ytdl_version_msg
|
logger.error(want_ytdl_version_msg
|
||||||
% {'have_version': youtube_dl.version.__version__, 'want_version': want_ytdl_version})
|
% {'have_version': youtube_dl.version.__version__, 'want_version': want_ytdl_version})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.38.2 -->
|
<!-- Generated with glade 3.40.0 -->
|
||||||
<!--*- mode: xml -*-->
|
<!--*- mode: xml -*-->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.16"/>
|
<requires lib="gtk+" version="3.16"/>
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="is-important">True</property>
|
<property name="is-important">True</property>
|
||||||
<property name="label" translatable="yes">Play</property>
|
<property name="label" translatable="yes">Play</property>
|
||||||
<property name="icon-name">media-playback-start</property>
|
<property name="icon-name">media-playback-start-symbolic</property>
|
||||||
<signal name="clicked" handler="on_playback_selected_episodes" swapped="no"/>
|
<signal name="clicked" handler="on_playback_selected_episodes" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="is-important">True</property>
|
<property name="is-important">True</property>
|
||||||
<property name="label" translatable="yes">Download</property>
|
<property name="label" translatable="yes">Download</property>
|
||||||
<property name="icon-name">go-down</property>
|
<property name="icon-name">document-save-symbolic</property>
|
||||||
<signal name="clicked" handler="on_download_selected_episodes" swapped="no"/>
|
<signal name="clicked" handler="on_download_selected_episodes" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="is-important">True</property>
|
<property name="is-important">True</property>
|
||||||
<property name="label" translatable="yes">Pause</property>
|
<property name="label" translatable="yes">Pause</property>
|
||||||
<property name="icon-name">media-playback-pause</property>
|
<property name="icon-name">media-playback-pause-symbolic</property>
|
||||||
<signal name="clicked" handler="on_pause_selected_episodes" swapped="no"/>
|
<signal name="clicked" handler="on_pause_selected_episodes" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="is-important">True</property>
|
<property name="is-important">True</property>
|
||||||
<property name="label" translatable="yes">Cancel</property>
|
<property name="label" translatable="yes">Cancel</property>
|
||||||
<property name="icon-name">process-stop</property>
|
<property name="icon-name">process-stop-symbolic</property>
|
||||||
<signal name="clicked" handler="on_item_cancel_download_activate" swapped="no"/>
|
<signal name="clicked" handler="on_item_cancel_download_activate" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="action-name">app.preferences</property>
|
<property name="action-name">app.preferences</property>
|
||||||
<property name="label" translatable="yes">Preferences</property>
|
<property name="label" translatable="yes">Preferences</property>
|
||||||
<property name="icon-name">preferences-desktop</property>
|
<property name="icon-name">preferences-other-symbolic</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="label" translatable="yes">Quit</property>
|
<property name="label" translatable="yes">Quit</property>
|
||||||
<property name="icon-name">application-exit</property>
|
<property name="icon-name">application-exit-symbolic</property>
|
||||||
<signal name="clicked" handler="on_gPodder_delete_event" swapped="no"/>
|
<signal name="clicked" handler="on_gPodder_delete_event" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
@ -384,17 +384,85 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<!-- n-columns=1 n-rows=2 -->
|
<object class="GtkBox">
|
||||||
<object class="GtkGrid" id="vboxDownloadStatusWidgets">
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="border-width">5</property>
|
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="row-spacing">5</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow" id="scrolledwindow1">
|
<object class="GtkInfoBar" id="resume_all_infobar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="message-type">question</property>
|
||||||
|
<property name="show-close-button">True</property>
|
||||||
|
<property name="revealed">False</property>
|
||||||
|
<signal name="response" handler="on_resume_all_infobar_response" swapped="no"/>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<object class="GtkButtonBox">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<property name="layout-style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="resume_all_button">
|
||||||
|
<property name="label" translatable="yes">Resume all</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="content_area">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="spacing">16</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Incomplete downloads from a previous session were found.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="-5">resume_all_button</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
|
<property name="margin-start">5</property>
|
||||||
|
<property name="margin-end">5</property>
|
||||||
|
<property name="margin-top">5</property>
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<property name="shadow-type">in</property>
|
<property name="shadow-type">in</property>
|
||||||
<child>
|
<child>
|
||||||
|
@ -415,23 +483,26 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">0</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<!-- n-columns=3 n-rows=1 -->
|
<object class="GtkBox">
|
||||||
<object class="GtkGrid" id="hboxDownloadSettings">
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="border-width">5</property>
|
<property name="margin-start">5</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="margin-end">5</property>
|
||||||
|
<property name="margin-top">5</property>
|
||||||
|
<property name="margin-bottom">10</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
<child>
|
<child>
|
||||||
<!-- n-columns=3 n-rows=1 -->
|
<object class="GtkBox">
|
||||||
<object class="GtkGrid" id="hboxDownloadLimit">
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="column-spacing">5</property>
|
<property name="margin-start">5</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCheckButton" id="cbLimitDownloads">
|
<object class="GtkCheckButton" id="cbLimitDownloads">
|
||||||
<property name="label" translatable="yes">Limit rate to</property>
|
<property name="label" translatable="yes">Limit rate to</property>
|
||||||
|
@ -442,22 +513,27 @@
|
||||||
<signal name="toggled" handler="on_cbLimitDownloads_toggled" swapped="no"/>
|
<signal name="toggled" handler="on_cbLimitDownloads_toggled" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">0</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSpinButton" id="spinLimitDownloads">
|
<object class="GtkSpinButton" id="spinLimitDownloads">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="invisible-char">●</property>
|
<property name="caps-lock-warning">False</property>
|
||||||
|
<property name="input-purpose">number</property>
|
||||||
<property name="adjustment">adjustment1</property>
|
<property name="adjustment">adjustment1</property>
|
||||||
<property name="climb-rate">1</property>
|
<property name="climb-rate">1</property>
|
||||||
<property name="digits">1</property>
|
<property name="digits">1</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
<property name="update-policy">if-valid</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">1</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -468,33 +544,23 @@
|
||||||
<property name="xalign">0</property>
|
<property name="xalign">0</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">2</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">0</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="DownloadSettingsSpacer">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="spacing">5</property>
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">1</property>
|
|
||||||
<property name="top-attach">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<!-- n-columns=2 n-rows=1 -->
|
|
||||||
<object class="GtkGrid" id="hboxDownloadRate">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="column-spacing">5</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCheckButton" id="cbMaxDownloads">
|
<object class="GtkCheckButton" id="cbMaxDownloads">
|
||||||
<property name="label" translatable="yes">Limit downloads to</property>
|
<property name="label" translatable="yes">Limit downloads to</property>
|
||||||
|
@ -505,33 +571,42 @@
|
||||||
<signal name="toggled" handler="on_cbMaxDownloads_toggled" swapped="no"/>
|
<signal name="toggled" handler="on_cbMaxDownloads_toggled" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">0</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSpinButton" id="spinMaxDownloads">
|
<object class="GtkSpinButton" id="spinMaxDownloads">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="invisible-char">●</property>
|
<property name="caps-lock-warning">False</property>
|
||||||
|
<property name="input-purpose">number</property>
|
||||||
<property name="adjustment">adjustment2</property>
|
<property name="adjustment">adjustment2</property>
|
||||||
<property name="climb-rate">1</property>
|
<property name="climb-rate">1</property>
|
||||||
|
<property name="snap-to-ticks">True</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
<property name="update-policy">if-valid</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">1</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">2</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">0</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="pack-type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left-attach">0</property>
|
<property name="expand">False</property>
|
||||||
<property name="top-attach">1</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -80,6 +80,7 @@
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="has-focus">True</property>
|
<property name="has-focus">True</property>
|
||||||
<property name="activates-default">True</property>
|
<property name="activates-default">True</property>
|
||||||
|
<property name="secondary-icon-name">edit-clear</property>
|
||||||
<signal name="changed" handler="on_entry_url_changed" swapped="no"/>
|
<signal name="changed" handler="on_entry_url_changed" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
|
|
@ -19,6 +19,10 @@
|
||||||
</item>
|
</item>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Open Logs</attribute>
|
||||||
|
<attribute name="action">app.logs</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">Help</attribute>
|
<attribute name="label" translatable="yes">Help</attribute>
|
||||||
<attribute name="action">app.help</attribute>
|
<attribute name="action">app.help</attribute>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- :indentSize=4:noTabs=true:tabSize=4: -->
|
|
||||||
<component type="desktop-application">
|
<component type="desktop-application">
|
||||||
<id>org.gpodder.gpodder</id>
|
<id>org.gpodder.gpodder</id>
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
@ -10,24 +9,81 @@
|
||||||
<p>gPodder lets you manage your Podcast subscriptions, discover new content and download episodes to your devices.</p>
|
<p>gPodder lets you manage your Podcast subscriptions, discover new content and download episodes to your devices.</p>
|
||||||
<p>You can also take advantage of the service gpodder.net, which lets you sync subscriptions, playback progress and starred episodes.</p>
|
<p>You can also take advantage of the service gpodder.net, which lets you sync subscriptions, playback progress and starred episodes.</p>
|
||||||
</description>
|
</description>
|
||||||
<launchable type="desktop-id">gpodder.desktop</launchable>
|
<launchable type="desktop-id">org.gpodder.gpodder.desktop</launchable>
|
||||||
<url type="homepage">https://www.gpodder.org</url>
|
<url type="homepage">https://www.gpodder.org</url>
|
||||||
<provides>
|
<provides>
|
||||||
<binary>gpodder</binary>
|
<binary>gpodder</binary>
|
||||||
<id>gpodder.desktop</id>
|
<id>gpodder.desktop</id>
|
||||||
</provides>
|
</provides>
|
||||||
<content_rating type="oars-1.0"/>
|
<content_rating type="oars-1.1">
|
||||||
<screenshots>
|
<content_attribute id="violence-cartoon">none</content_attribute>
|
||||||
|
<content_attribute id="violence-fantasy">none</content_attribute>
|
||||||
|
<content_attribute id="violence-realistic">none</content_attribute>
|
||||||
|
<content_attribute id="violence-bloodshed">none</content_attribute>
|
||||||
|
<content_attribute id="violence-sexual">none</content_attribute>
|
||||||
|
<content_attribute id="violence-desecration">none</content_attribute>
|
||||||
|
<content_attribute id="violence-slavery">none</content_attribute>
|
||||||
|
<content_attribute id="violence-worship">none</content_attribute>
|
||||||
|
<content_attribute id="drugs-alcohol">none</content_attribute>
|
||||||
|
<content_attribute id="drugs-narcotics">none</content_attribute>
|
||||||
|
<content_attribute id="drugs-tobacco">none</content_attribute>
|
||||||
|
<content_attribute id="sex-nudity">none</content_attribute>
|
||||||
|
<content_attribute id="sex-themes">none</content_attribute>
|
||||||
|
<content_attribute id="sex-homosexuality">none</content_attribute>
|
||||||
|
<content_attribute id="sex-prostitution">none</content_attribute>
|
||||||
|
<content_attribute id="sex-adultery">none</content_attribute>
|
||||||
|
<content_attribute id="sex-appearance">none</content_attribute>
|
||||||
|
<content_attribute id="language-profanity">none</content_attribute>
|
||||||
|
<content_attribute id="language-humor">none</content_attribute>
|
||||||
|
<content_attribute id="language-discrimination">none</content_attribute>
|
||||||
|
<content_attribute id="social-chat">none</content_attribute>
|
||||||
|
<content_attribute id="social-info">none</content_attribute>
|
||||||
|
<content_attribute id="social-audio">none</content_attribute>
|
||||||
|
<content_attribute id="social-location">none</content_attribute>
|
||||||
|
<content_attribute id="social-contacts">none</content_attribute>
|
||||||
|
<content_attribute id="money-purchasing">none</content_attribute>
|
||||||
|
<content_attribute id="money-gambling">none</content_attribute>
|
||||||
|
</content_rating>
|
||||||
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<caption>The main window</caption>
|
<caption>The main window</caption>
|
||||||
<image>https://raw.githubusercontent.com/flathub/org.gpodder.gpodder/master/screenshot.png</image>
|
<image>https://raw.githubusercontent.com/flathub/org.gpodder.gpodder/master/screenshot.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="3.11.0" date="2022-07-30">
|
||||||
|
<description>
|
||||||
|
<p>This release contains a year's worth of improvements. Major changes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Warning: There is a database schema update (See "Moving to an older gPodder release" in the user manual at gpodder.github.io for how to rollback)</li>
|
||||||
|
<li>Numerous bug fixes</li>
|
||||||
|
<li>Performance improvements</li>
|
||||||
|
<li>A new preferences dialog</li>
|
||||||
|
<li>Support again syncing to mtp:// and iPod devices on Linux</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="3.10.21" date="2021-07-20"/>
|
||||||
|
<release version="3.10.20" date="2021-06-06"/>
|
||||||
|
<release version="3.10.19" date="2021-04-15"/>
|
||||||
|
<release version="3.10.17" date="2020-11-23"/>
|
||||||
|
<release version="3.10.13" date="2020-01-29"/>
|
||||||
|
<release version="3.10.12" date="2020-01-25"/>
|
||||||
|
<release version="3.10.11" date="2019-09-29"/>
|
||||||
|
<release version="3.10.10" date="2019-09-27"/>
|
||||||
|
<release version="3.10.9" date="2019-06-09"/>
|
||||||
|
<release version="3.10.8" date="2019-04-08"/>
|
||||||
|
<release version="3.10.7" date="2019-02-02"/>
|
||||||
|
<release version="3.10.6" date="2018-12-29">
|
||||||
<release version="3.10.5" date="2018-09-15">
|
<release version="3.10.5" date="2018-09-15">
|
||||||
<description>This is a bugfix release, shortly after 3.10.4, for the Rename after Download extension.</description>
|
<description>
|
||||||
|
<p>This is a bugfix release, shortly after 3.10.4, for the Rename after Download extension.</p>
|
||||||
|
</description>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.10.4" date="2018-09-09"/>
|
<release version="3.10.4" date="2018-09-09"/>
|
||||||
<release version="3.10.3" date="2018-06-14"/>
|
<release version="3.10.3" date="2018-06-14"/>
|
||||||
|
<release version="3.10.2" date="2018-06-10"/>
|
||||||
|
<release version="3.10.1" date="2018-02-19"/>
|
||||||
|
<release version="3.10.0" date="2017-12-29"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -430,3 +430,13 @@ class Config(object):
|
||||||
logger.debug("setting config.device_sync.max_filename_length=120"
|
logger.debug("setting config.device_sync.max_filename_length=120"
|
||||||
" (999 is bad for NTFS and ext{2-4})")
|
" (999 is bad for NTFS and ext{2-4})")
|
||||||
self.device_sync.max_filename_length = 120
|
self.device_sync.max_filename_length = 120
|
||||||
|
|
||||||
|
def clamp_range(self, name, min, max):
|
||||||
|
value = getattr(self, name)
|
||||||
|
if value < min:
|
||||||
|
setattr(self, name, min)
|
||||||
|
return True
|
||||||
|
if value > max:
|
||||||
|
setattr(self, name, max)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# gPodder - A media aggregator and podcast client
|
# gPodder - A media aggregator and podcast client
|
||||||
|
|
|
@ -95,6 +95,10 @@ class gPodderApplication(Gtk.Application):
|
||||||
action.connect('activate', self.on_help_activate)
|
action.connect('activate', self.on_help_activate)
|
||||||
self.add_action(action)
|
self.add_action(action)
|
||||||
|
|
||||||
|
action = Gio.SimpleAction.new('logs', None)
|
||||||
|
action.connect('activate', self.on_logs_activate)
|
||||||
|
self.add_action(action)
|
||||||
|
|
||||||
action = Gio.SimpleAction.new('preferences', None)
|
action = Gio.SimpleAction.new('preferences', None)
|
||||||
action.connect('activate', self.on_itemPreferences_activate)
|
action.connect('activate', self.on_itemPreferences_activate)
|
||||||
self.add_action(action)
|
self.add_action(action)
|
||||||
|
@ -259,6 +263,9 @@ class gPodderApplication(Gtk.Application):
|
||||||
def on_help_activate(self, action, param):
|
def on_help_activate(self, action, param):
|
||||||
util.open_website('https://gpodder.github.io/docs/')
|
util.open_website('https://gpodder.github.io/docs/')
|
||||||
|
|
||||||
|
def on_logs_activate(self, action, param):
|
||||||
|
util.gui_open(os.path.join(gpodder.home, 'Logs'), gui=self.window)
|
||||||
|
|
||||||
def on_itemPreferences_activate(self, action, param=None):
|
def on_itemPreferences_activate(self, action, param=None):
|
||||||
gPodderPreferences(self.window.gPodder,
|
gPodderPreferences(self.window.gPodder,
|
||||||
_config=self.window.config,
|
_config=self.window.config,
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
# gpodder.gtkui.config -- Config object with GTK+ support (2009-08-24)
|
# gpodder.gtkui.config -- Config object with GTK+ support (2009-08-24)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import gi # isort:skip
|
import gi # isort:skip
|
||||||
gi.require_version('Gdk', '3.0') # isort:skip
|
gi.require_version('Gdk', '3.0') # isort:skip
|
||||||
gi.require_version('Gtk', '3.0') # isort:skip
|
gi.require_version('Gtk', '3.0') # isort:skip
|
||||||
|
@ -30,6 +32,8 @@ from gi.repository import Gdk, Gtk, Pango
|
||||||
import gpodder
|
import gpodder
|
||||||
from gpodder import config, util
|
from gpodder import config, util
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_ = gpodder.gettext
|
_ = gpodder.gettext
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,6 +160,25 @@ class UIConfig(config.Config):
|
||||||
if gpodder.ui.win32:
|
if gpodder.ui.win32:
|
||||||
window.set_gravity(Gdk.Gravity.STATIC)
|
window.set_gravity(Gdk.Gravity.STATIC)
|
||||||
|
|
||||||
|
if -1 not in (cfg.x, cfg.y, cfg.width, cfg.height):
|
||||||
|
# get screen resolution
|
||||||
|
screen = Gdk.Screen.get_default()
|
||||||
|
screen_width = 0
|
||||||
|
screen_height = 0
|
||||||
|
for i in range(0, screen.get_n_monitors()):
|
||||||
|
monitor = screen.get_monitor_geometry(i)
|
||||||
|
screen_width += monitor.width
|
||||||
|
screen_height += monitor.height
|
||||||
|
logger.debug('Screen %d x %d' % (screen_width, screen_height))
|
||||||
|
# reset window position if more than 50% is off-screen
|
||||||
|
half_width = cfg.width / 2
|
||||||
|
half_height = cfg.height / 2
|
||||||
|
if (cfg.x + cfg.width - half_width) < 0 or (cfg.y + cfg.height - half_height) < 0 \
|
||||||
|
or cfg.x > (screen_width - half_width) or cfg.y > (screen_height - half_height):
|
||||||
|
logger.warning('"%s" window was off-screen at (%d, %d), resetting to default position' % (config_prefix, cfg.x, cfg.y))
|
||||||
|
cfg.x = -1
|
||||||
|
cfg.y = -1
|
||||||
|
|
||||||
if cfg.width != -1 and cfg.height != -1:
|
if cfg.width != -1 and cfg.height != -1:
|
||||||
window.resize(cfg.width, cfg.height)
|
window.resize(cfg.width, cfg.height)
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ class gPodderEpisodeSelector(BuilderWidget):
|
||||||
self.size_attribute = 'file_size'
|
self.size_attribute = 'file_size'
|
||||||
|
|
||||||
if not hasattr(self, 'tooltip_attribute'):
|
if not hasattr(self, 'tooltip_attribute'):
|
||||||
self.tooltip_attribute = 'description'
|
self.tooltip_attribute = '_text_description'
|
||||||
|
|
||||||
if not hasattr(self, 'selection_buttons'):
|
if not hasattr(self, 'selection_buttons'):
|
||||||
self.selection_buttons = {}
|
self.selection_buttons = {}
|
||||||
|
|
|
@ -193,7 +193,10 @@ class DownloadStatusModel(Gtk.ListStore):
|
||||||
# as only the main thread is allowed to manipulate the list store.
|
# as only the main thread is allowed to manipulate the list store.
|
||||||
def get_next(self):
|
def get_next(self):
|
||||||
dqr = DequeueRequest()
|
dqr = DequeueRequest()
|
||||||
util.idle_add(self.__get_next, dqr)
|
# this can not be idle_add because update_downloads_list() is called from a higher
|
||||||
|
# priority timeout_add and would spin forever, never calling this.
|
||||||
|
from gi.repository import GLib
|
||||||
|
GLib.timeout_add(0, self.__get_next, dqr)
|
||||||
return dqr.dequeue()
|
return dqr.dequeue()
|
||||||
|
|
||||||
def _work_gen(self):
|
def _work_gen(self):
|
||||||
|
|
|
@ -41,6 +41,7 @@ class gPodderAddPodcast(BuilderWidget):
|
||||||
if hasattr(self, 'preset_url'):
|
if hasattr(self, 'preset_url'):
|
||||||
self.entry_url.set_text(self.preset_url)
|
self.entry_url.set_text(self.preset_url)
|
||||||
self.entry_url.connect('activate', self.on_entry_url_activate)
|
self.entry_url.connect('activate', self.on_entry_url_activate)
|
||||||
|
self.entry_url.connect('icon-press', self.on_clear_url)
|
||||||
self.gPodderAddPodcast.show()
|
self.gPodderAddPodcast.show()
|
||||||
|
|
||||||
if not hasattr(self, 'preset_url'):
|
if not hasattr(self, 'preset_url'):
|
||||||
|
@ -67,6 +68,9 @@ class gPodderAddPodcast(BuilderWidget):
|
||||||
clipboard.request_text(receive_clipboard_text, True)
|
clipboard.request_text(receive_clipboard_text, True)
|
||||||
clipboard.request_text(receive_clipboard_text, False)
|
clipboard.request_text(receive_clipboard_text, False)
|
||||||
|
|
||||||
|
def on_clear_url(self, widget, icon_position, event):
|
||||||
|
self.entry_url.set_text('')
|
||||||
|
|
||||||
def on_btn_close_clicked(self, widget):
|
def on_btn_close_clicked(self, widget):
|
||||||
self.gPodderAddPodcast.destroy()
|
self.gPodderAddPodcast.destroy()
|
||||||
|
|
||||||
|
@ -76,7 +80,7 @@ class gPodderAddPodcast(BuilderWidget):
|
||||||
|
|
||||||
def receive_clipboard_text(self, clipboard, text, data=None):
|
def receive_clipboard_text(self, clipboard, text, data=None):
|
||||||
if text is not None:
|
if text is not None:
|
||||||
self.entry_url.set_text(text).strip()
|
self.entry_url.set_text(text.strip())
|
||||||
else:
|
else:
|
||||||
self.show_message(_('Nothing to paste.'), _('Clipboard is empty'))
|
self.show_message(_('Nothing to paste.'), _('Clipboard is empty'))
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
from gi.repository import GObject, Gtk, Pango
|
import time
|
||||||
|
|
||||||
|
from gi.repository import GLib, Gtk, Pango
|
||||||
|
|
||||||
import gpodder
|
import gpodder
|
||||||
from gpodder.gtkui.widgets import SpinningProgressIndicator
|
from gpodder.gtkui.widgets import SpinningProgressIndicator
|
||||||
|
@ -35,7 +37,10 @@ class ProgressIndicator(object):
|
||||||
def __init__(self, title, subtitle=None, cancellable=False, parent=None):
|
def __init__(self, title, subtitle=None, cancellable=False, parent=None):
|
||||||
self.title = title
|
self.title = title
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
self.cancellable = cancellable
|
self.cancellable = True if cancellable else False
|
||||||
|
self.cancel_callback = cancellable
|
||||||
|
self.cancel_id = 0
|
||||||
|
self.next_update = time.time() + (self.DELAY / 1000)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.dialog = None
|
self.dialog = None
|
||||||
self.progressbar = None
|
self.progressbar = None
|
||||||
|
@ -43,11 +48,12 @@ class ProgressIndicator(object):
|
||||||
self._initial_message = None
|
self._initial_message = None
|
||||||
self._initial_progress = None
|
self._initial_progress = None
|
||||||
self._progress_set = False
|
self._progress_set = False
|
||||||
self.source_id = GObject.timeout_add(self.DELAY, self._create_progress)
|
self.source_id = GLib.timeout_add(self.DELAY, self._create_progress)
|
||||||
|
|
||||||
def _on_delete_event(self, window, event):
|
def _on_delete_event(self, window, event):
|
||||||
if self.cancellable:
|
if self.cancellable:
|
||||||
self.dialog.response(Gtk.ResponseType.CANCEL)
|
self.dialog.response(Gtk.ResponseType.CANCEL)
|
||||||
|
self.cancellable = False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _create_progress(self):
|
def _create_progress(self):
|
||||||
|
@ -55,6 +61,14 @@ class ProgressIndicator(object):
|
||||||
0, 0, Gtk.ButtonsType.CANCEL, self.subtitle or self.title)
|
0, 0, Gtk.ButtonsType.CANCEL, self.subtitle or self.title)
|
||||||
self.dialog.set_modal(True)
|
self.dialog.set_modal(True)
|
||||||
self.dialog.connect('delete-event', self._on_delete_event)
|
self.dialog.connect('delete-event', self._on_delete_event)
|
||||||
|
if self.cancellable:
|
||||||
|
def cancel_callback(dialog, response):
|
||||||
|
self.cancellable = False
|
||||||
|
self.dialog.set_deletable(False)
|
||||||
|
self.dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False)
|
||||||
|
if callable(self.cancel_callback):
|
||||||
|
self.cancel_callback(dialog, response)
|
||||||
|
self.cancel_id = self.dialog.connect('response', cancel_callback)
|
||||||
self.dialog.set_title(self.title)
|
self.dialog.set_title(self.title)
|
||||||
self.dialog.set_deletable(self.cancellable)
|
self.dialog.set_deletable(self.cancellable)
|
||||||
|
|
||||||
|
@ -83,8 +97,10 @@ class ProgressIndicator(object):
|
||||||
self.dialog.set_image(self.indicator)
|
self.dialog.set_image(self.indicator)
|
||||||
self.dialog.show_all()
|
self.dialog.show_all()
|
||||||
|
|
||||||
GObject.source_remove(self.source_id)
|
self._update_gui()
|
||||||
self.source_id = GObject.timeout_add(self.INTERVAL, self._update_gui)
|
|
||||||
|
# previous self.source_id timeout is removed when this returns False
|
||||||
|
self.source_id = GLib.timeout_add(self.INTERVAL, self._update_gui)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _update_gui(self):
|
def _update_gui(self):
|
||||||
|
@ -92,8 +108,16 @@ class ProgressIndicator(object):
|
||||||
self.indicator.step_animation()
|
self.indicator.step_animation()
|
||||||
if not self._progress_set and self.progressbar:
|
if not self._progress_set and self.progressbar:
|
||||||
self.progressbar.pulse()
|
self.progressbar.pulse()
|
||||||
|
self.next_update = time.time() + (self.INTERVAL / 1000)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def update_gui(self):
|
||||||
|
if self.dialog:
|
||||||
|
if self.source_id:
|
||||||
|
GLib.source_remove(self.source_id)
|
||||||
|
self.source_id = 0
|
||||||
|
self._update_gui()
|
||||||
|
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
if self.progressbar:
|
if self.progressbar:
|
||||||
self.progressbar.set_text(message)
|
self.progressbar.set_text(message)
|
||||||
|
@ -109,5 +133,8 @@ class ProgressIndicator(object):
|
||||||
|
|
||||||
def on_finished(self):
|
def on_finished(self):
|
||||||
if self.dialog is not None:
|
if self.dialog is not None:
|
||||||
|
if self.cancel_id:
|
||||||
|
self.dialog.disconnect(self.cancel_id)
|
||||||
self.dialog.destroy()
|
self.dialog.destroy()
|
||||||
GObject.source_remove(self.source_id)
|
if self.source_id:
|
||||||
|
GLib.source_remove(self.source_id)
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
import html
|
import html
|
||||||
|
|
||||||
from gi.repository import GObject, Gtk
|
from gi.repository import GLib, GObject, Gtk
|
||||||
|
|
||||||
|
|
||||||
class TagCloud(Gtk.Layout):
|
class TagCloud(Gtk.Layout):
|
||||||
|
@ -100,7 +100,7 @@ class TagCloud(Gtk.Layout):
|
||||||
def unrelayout():
|
def unrelayout():
|
||||||
self._in_relayout = False
|
self._in_relayout = False
|
||||||
return False
|
return False
|
||||||
GObject.idle_add(unrelayout)
|
GLib.idle_add(unrelayout)
|
||||||
|
|
||||||
|
|
||||||
GObject.type_register(TagCloud)
|
GObject.type_register(TagCloud)
|
||||||
|
|
|
@ -62,7 +62,7 @@ import gi # isort:skip
|
||||||
gi.require_version('Gtk', '3.0') # isort:skip
|
gi.require_version('Gtk', '3.0') # isort:skip
|
||||||
gi.require_version('Gdk', '3.0') # isort:skip
|
gi.require_version('Gdk', '3.0') # isort:skip
|
||||||
gi.require_version('Handy', '1') # isort:skip
|
gi.require_version('Handy', '1') # isort:skip
|
||||||
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk, Pango # isort:skip
|
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk, Pango # isort:skip
|
||||||
from gi.repository import Handy # isort:skip
|
from gi.repository import Handy # isort:skip
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,6 +204,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
self.config.connect_gtk_spinbutton('limit_rate_value', self.spinLimitDownloads)
|
self.config.connect_gtk_spinbutton('limit_rate_value', self.spinLimitDownloads)
|
||||||
self.config.connect_gtk_togglebutton('limit_rate', self.cbLimitDownloads)
|
self.config.connect_gtk_togglebutton('limit_rate', self.cbLimitDownloads)
|
||||||
|
|
||||||
|
self.spinMaxDownloads.set_sensitive(self.cbMaxDownloads.get_active())
|
||||||
|
self.spinLimitDownloads.set_sensitive(self.cbLimitDownloads.get_active())
|
||||||
|
|
||||||
# When the amount of maximum downloads changes, notify the queue manager
|
# When the amount of maximum downloads changes, notify the queue manager
|
||||||
def changed_cb(spinbutton):
|
def changed_cb(spinbutton):
|
||||||
return self.download_queue_manager.update_max_downloads()
|
return self.download_queue_manager.update_max_downloads()
|
||||||
|
@ -530,7 +533,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
if response_id == Gtk.ResponseType.OK:
|
if response_id == Gtk.ResponseType.OK:
|
||||||
selection = self.treeDownloads.get_selection()
|
selection = self.treeDownloads.get_selection()
|
||||||
selection.select_all()
|
selection.select_all()
|
||||||
selected_tasks, _, _, _, _, _ = self.downloads_list_get_selection()
|
selected_tasks = self.downloads_list_get_selection()[0]
|
||||||
selection.unselect_all()
|
selection.unselect_all()
|
||||||
self._for_each_task_set_status(selected_tasks, download.DownloadTask.QUEUED)
|
self._for_each_task_set_status(selected_tasks, download.DownloadTask.QUEUED)
|
||||||
self.resume_all_infobar.set_revealed(False)
|
self.resume_all_infobar.set_revealed(False)
|
||||||
|
@ -549,14 +552,16 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
def progress_callback(title, progress):
|
def progress_callback(title, progress):
|
||||||
self.partial_downloads_indicator.on_message(title)
|
self.partial_downloads_indicator.on_message(title)
|
||||||
self.partial_downloads_indicator.on_progress(progress)
|
self.partial_downloads_indicator.on_progress(progress)
|
||||||
|
if time.time() >= self.partial_downloads_indicator.next_update:
|
||||||
|
self.partial_downloads_indicator.update_gui()
|
||||||
|
self.force_ui_update()
|
||||||
|
|
||||||
def finish_progress_callback(resumable_episodes):
|
def finish_progress_callback(resumable_episodes):
|
||||||
def offer_resuming():
|
def offer_resuming():
|
||||||
if resumable_episodes:
|
if resumable_episodes:
|
||||||
self.download_episode_list_paused(resumable_episodes)
|
self.download_episode_list_paused(resumable_episodes, hide_progress=True)
|
||||||
self.resume_all_infobar.set_revealed(True)
|
self.resume_all_infobar.set_revealed(True)
|
||||||
self.on_show_progress_activate()
|
self.on_show_progress_activate()
|
||||||
|
|
||||||
logger.debug("find_partial_downloads done, calling extensions")
|
logger.debug("find_partial_downloads done, calling extensions")
|
||||||
gpodder.user_extensions.on_find_partial_downloads_done()
|
gpodder.user_extensions.on_find_partial_downloads_done()
|
||||||
|
|
||||||
|
@ -778,7 +783,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
important=True)
|
important=True)
|
||||||
util.idle_add(show_error, e)
|
util.idle_add(show_error, e)
|
||||||
|
|
||||||
util.idle_add(indicator.on_finished)
|
indicator.on_finished()
|
||||||
|
|
||||||
def on_button_subscribe_clicked(self, button):
|
def on_button_subscribe_clicked(self, button):
|
||||||
self.on_itemImportChannels_activate(button)
|
self.on_itemImportChannels_activate(button)
|
||||||
|
@ -1336,7 +1341,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
# (('text/uri-list', 0, 0),), Gdk.DragAction.COPY)
|
# (('text/uri-list', 0, 0),), Gdk.DragAction.COPY)
|
||||||
#
|
#
|
||||||
# def drag_data_get(tree, context, selection_data, info, timestamp):
|
# def drag_data_get(tree, context, selection_data, info, timestamp):
|
||||||
# uris = ['file://' + e.local_filename(create=False)
|
# uris = ['file://' + urllib.parse.quote(e.local_filename(create=False))
|
||||||
# for e in self.get_selected_episodes()
|
# for e in self.get_selected_episodes()
|
||||||
# if e.was_downloaded(and_exists=True)]
|
# if e.was_downloaded(and_exists=True)]
|
||||||
# selection_data.set_uris(uris)
|
# selection_data.set_uris(uris)
|
||||||
|
@ -1497,7 +1502,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
self.things_adding_tasks -= 1
|
self.things_adding_tasks -= 1
|
||||||
if not self.download_list_update_enabled:
|
if not self.download_list_update_enabled:
|
||||||
self.update_downloads_list()
|
self.update_downloads_list()
|
||||||
GObject.timeout_add(1500, self.update_downloads_list)
|
util.IdleTimeout(1500, self.update_downloads_list)
|
||||||
self.download_list_update_enabled = True
|
self.download_list_update_enabled = True
|
||||||
|
|
||||||
def cleanup_downloads(self):
|
def cleanup_downloads(self):
|
||||||
|
@ -1544,7 +1549,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
try:
|
try:
|
||||||
model = self.download_status_model
|
model = self.download_status_model
|
||||||
|
|
||||||
downloading, synchronizing, pausing, cancelling, queued, paused, failed, finished, others = (0,) * 9
|
downloading, synchronizing, pausing, cancelling, queued, paused, failed, finished = (0,) * 8
|
||||||
total_speed, total_size, done_size = 0, 0, 0
|
total_speed, total_size, done_size = 0, 0, 0
|
||||||
files_downloading = 0
|
files_downloading = 0
|
||||||
|
|
||||||
|
@ -1577,8 +1582,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
total_speed += speed
|
total_speed += speed
|
||||||
elif activity == download.DownloadTask.ACTIVITY_SYNCHRONIZE:
|
elif activity == download.DownloadTask.ACTIVITY_SYNCHRONIZE:
|
||||||
synchronizing += 1
|
synchronizing += 1
|
||||||
else:
|
|
||||||
others += 1
|
|
||||||
elif status == download.DownloadTask.PAUSING:
|
elif status == download.DownloadTask.PAUSING:
|
||||||
pausing += 1
|
pausing += 1
|
||||||
if activity == download.DownloadTask.ACTIVITY_DOWNLOAD:
|
if activity == download.DownloadTask.ACTIVITY_DOWNLOAD:
|
||||||
|
@ -1595,10 +1598,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
failed += 1
|
failed += 1
|
||||||
elif status == download.DownloadTask.DONE:
|
elif status == download.DownloadTask.DONE:
|
||||||
finished += 1
|
finished += 1
|
||||||
else:
|
|
||||||
others += 1
|
|
||||||
|
|
||||||
# TODO: 'others' is not used
|
|
||||||
|
|
||||||
# Remember which tasks we have seen after this run
|
# Remember which tasks we have seen after this run
|
||||||
self.download_tasks_seen = download_tasks_seen
|
self.download_tasks_seen = download_tasks_seen
|
||||||
|
@ -1706,6 +1705,24 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
elif name == 'ui.gtk.episode_list.columns':
|
elif name == 'ui.gtk.episode_list.columns':
|
||||||
# self.update_episode_list_columns_visibility()
|
# self.update_episode_list_columns_visibility()
|
||||||
pass
|
pass
|
||||||
|
elif name == 'limit.downloads.concurrent_max':
|
||||||
|
# Do not allow value to be set below 1
|
||||||
|
if new_value < 1:
|
||||||
|
self.config.limit.downloads.concurrent_max = 1
|
||||||
|
return
|
||||||
|
# Clamp current value to new maximum value
|
||||||
|
if self.config.limit.downloads.concurrent > new_value:
|
||||||
|
self.config.limit.downloads.concurrent = new_value
|
||||||
|
self.spinMaxDownloads.get_adjustment().set_upper(new_value)
|
||||||
|
elif name == 'limit.downloads.concurrent':
|
||||||
|
if self.config.clamp_range('limit.downloads.concurrent', 1, self.config.limit.downloads.concurrent_max):
|
||||||
|
return
|
||||||
|
self.spinMaxDownloads.set_value(new_value)
|
||||||
|
elif name == 'limit.bandwidth.kbps':
|
||||||
|
adjustment = self.spinLimitDownloads.get_adjustment()
|
||||||
|
if self.config.clamp_range('limit.bandwidth.kbps', adjustment.get_lower(), adjustment.get_upper()):
|
||||||
|
return
|
||||||
|
self.spinLimitDownloads.set_value(new_value)
|
||||||
|
|
||||||
def on_treeview_query_tooltip(self, treeview, x, y, keyboard_tooltip, tooltip):
|
def on_treeview_query_tooltip(self, treeview, x, y, keyboard_tooltip, tooltip):
|
||||||
# With get_bin_window, we get the window that contains the rows without
|
# With get_bin_window, we get the window that contains the rows without
|
||||||
|
@ -1962,7 +1979,40 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
else:
|
else:
|
||||||
self.download_queue_manager.queue_task(task)
|
self.download_queue_manager.queue_task(task)
|
||||||
|
|
||||||
|
def force_ui_update(self):
|
||||||
|
def callback():
|
||||||
|
Gtk.main_quit()
|
||||||
|
GLib.timeout_add(1, callback)
|
||||||
|
Gtk.main()
|
||||||
|
|
||||||
def _for_each_task_set_status(self, tasks, status, force_start=False):
|
def _for_each_task_set_status(self, tasks, status, force_start=False):
|
||||||
|
count = len(tasks)
|
||||||
|
if count:
|
||||||
|
progress_indicator = ProgressIndicator(
|
||||||
|
_('Queueing') if status == download.DownloadTask.QUEUED else
|
||||||
|
_('Removing') if status is None else download.DownloadTask.STATUS_MESSAGE[status],
|
||||||
|
'', True, self.get_dialog_parent())
|
||||||
|
progress_indicator.on_message('0 / %d' % count)
|
||||||
|
else:
|
||||||
|
progress_indicator = None
|
||||||
|
|
||||||
|
def progress_callback(title, progress):
|
||||||
|
progress_indicator.on_message(title)
|
||||||
|
progress_indicator.on_progress(progress)
|
||||||
|
if time.time() >= progress_indicator.next_update:
|
||||||
|
progress_indicator.update_gui()
|
||||||
|
self.force_ui_update()
|
||||||
|
if not progress_indicator.cancellable:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
self.__for_each_task_set_status(tasks, status, force_start=force_start, progress_callback=progress_callback)
|
||||||
|
|
||||||
|
if progress_indicator:
|
||||||
|
progress_indicator.on_finished()
|
||||||
|
|
||||||
|
def __for_each_task_set_status(self, tasks, status, force_start=False, progress_callback=None):
|
||||||
|
count = len(tasks)
|
||||||
|
n = 0
|
||||||
episode_urls = set()
|
episode_urls = set()
|
||||||
model = self.treeDownloads.get_model()
|
model = self.treeDownloads.get_model()
|
||||||
for row_reference, task in tasks:
|
for row_reference, task in tasks:
|
||||||
|
@ -2011,6 +2061,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
else:
|
else:
|
||||||
# We can (hopefully) simply set the task status here
|
# We can (hopefully) simply set the task status here
|
||||||
task.status = status
|
task.status = status
|
||||||
|
if progress_callback:
|
||||||
|
n += 1
|
||||||
|
if not progress_callback('%d / %d' % (n, count), n / count):
|
||||||
|
break
|
||||||
# Tell the podcasts tab to update icons for our removed podcasts
|
# Tell the podcasts tab to update icons for our removed podcasts
|
||||||
self.update_episode_list_icons(episode_urls)
|
self.update_episode_list_icons(episode_urls)
|
||||||
# Update the tab title and downloads list
|
# Update the tab title and downloads list
|
||||||
|
@ -2340,10 +2394,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
|
|
||||||
# play icon and label
|
# play icon and label
|
||||||
# if open_instead_of_play or not is_episode_selected:
|
# if open_instead_of_play or not is_episode_selected:
|
||||||
# self.toolPlay.set_icon_name('document-open')
|
# self.toolPlay.set_icon_name('document-open-symbolic')
|
||||||
# self.toolPlay.set_label(_('Open'))
|
# self.toolPlay.set_label(_('Open'))
|
||||||
# else:
|
# else:
|
||||||
# self.toolPlay.set_icon_name('media-playback-start')
|
# self.toolPlay.set_icon_name('media-playback-start-symbolic')
|
||||||
#
|
#
|
||||||
# downloaded = all(e.was_downloaded(and_exists=True) for e in episodes)
|
# downloaded = all(e.was_downloaded(and_exists=True) for e in episodes)
|
||||||
# downloading = any(e.downloading for e in episodes)
|
# downloading = any(e.downloading for e in episodes)
|
||||||
|
@ -2601,7 +2655,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
if remaining_seconds > 3600:
|
if remaining_seconds > 3600:
|
||||||
# timeout an hour early in the event daylight savings changes the clock forward
|
# timeout an hour early in the event daylight savings changes the clock forward
|
||||||
remaining_seconds = remaining_seconds - 3600
|
remaining_seconds = remaining_seconds - 3600
|
||||||
GObject.timeout_add(remaining_seconds * 1000, self.refresh_episode_dates)
|
GLib.timeout_add(remaining_seconds * 1000, self.refresh_episode_dates)
|
||||||
|
|
||||||
def update_podcast_list_model(self, urls=None, selected=False, select_url=None,
|
def update_podcast_list_model(self, urls=None, selected=False, select_url=None,
|
||||||
sections_changed=False):
|
sections_changed=False):
|
||||||
|
@ -2793,6 +2847,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
|
|
||||||
def on_after_update():
|
def on_after_update():
|
||||||
progress.on_finished()
|
progress.on_finished()
|
||||||
|
|
||||||
# Report already-existing subscriptions to the user
|
# Report already-existing subscriptions to the user
|
||||||
if existing:
|
if existing:
|
||||||
title = _('Existing subscriptions skipped')
|
title = _('Existing subscriptions skipped')
|
||||||
|
@ -2960,9 +3015,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
|
|
||||||
self.mygpo_client.process_episode_actions(self.find_episode)
|
self.mygpo_client.process_episode_actions(self.find_episode)
|
||||||
|
|
||||||
indicator.on_finished()
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
indicator.on_finished()
|
||||||
|
|
||||||
def _update_cover(self, channel):
|
def _update_cover(self, channel):
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
self.cover_downloader.request_cover(channel)
|
self.cover_downloader.request_cover(channel)
|
||||||
|
@ -3217,7 +3273,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
selected_tasks = [(Gtk.TreeRowReference.new(model, path),
|
selected_tasks = [(Gtk.TreeRowReference.new(model, path),
|
||||||
model.get_value(model.get_iter(path),
|
model.get_value(model.get_iter(path),
|
||||||
DownloadStatusModel.C_TASK)) for path in paths]
|
DownloadStatusModel.C_TASK)) for path in paths]
|
||||||
self._for_each_task_set_status(selected_tasks, status=None, force_start=False)
|
self._for_each_task_set_status(selected_tasks, status=None)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not episodes:
|
if not episodes:
|
||||||
|
@ -3251,8 +3307,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
parent=self.get_dialog_parent())
|
parent=self.get_dialog_parent())
|
||||||
|
|
||||||
def finish_deletion(episode_urls, channel_urls):
|
def finish_deletion(episode_urls, channel_urls):
|
||||||
progress.on_finished()
|
|
||||||
|
|
||||||
# Episodes have been deleted - persist the database
|
# Episodes have been deleted - persist the database
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
@ -3261,6 +3315,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
self.update_header_bar_subtitle()
|
self.update_header_bar_subtitle()
|
||||||
self.play_or_download()
|
self.play_or_download()
|
||||||
|
|
||||||
|
progress.on_finished()
|
||||||
|
|
||||||
@util.run_in_background
|
@util.run_in_background
|
||||||
def thread_proc():
|
def thread_proc():
|
||||||
episode_urls = set()
|
episode_urls = set()
|
||||||
|
@ -3445,11 +3501,21 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
|
|
||||||
util.idle_add(show_welcome_window)
|
util.idle_add(show_welcome_window)
|
||||||
|
|
||||||
def download_episode_list_paused(self, episodes):
|
def download_episode_list_paused(self, episodes, hide_progress=False):
|
||||||
self.download_episode_list(episodes, True)
|
self.download_episode_list(episodes, True, hide_progress=hide_progress)
|
||||||
|
|
||||||
def download_episode_list(self, episodes, add_paused=False, force_start=False, downloader=None):
|
def download_episode_list(self, episodes, add_paused=False, force_start=False, downloader=None, hide_progress=False):
|
||||||
def queue_tasks(tasks, queued_existing_task):
|
def queue_tasks(tasks, queued_existing_task):
|
||||||
|
n = 0
|
||||||
|
count = len(episodes)
|
||||||
|
if count and not hide_progress:
|
||||||
|
progress_indicator = ProgressIndicator(
|
||||||
|
_('Queueing'),
|
||||||
|
'', True, self.get_dialog_parent())
|
||||||
|
progress_indicator.on_message('0 / %d' % count)
|
||||||
|
else:
|
||||||
|
progress_indicator = None
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
with task:
|
with task:
|
||||||
if add_paused:
|
if add_paused:
|
||||||
|
@ -3457,12 +3523,24 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
else:
|
else:
|
||||||
self.mygpo_client.on_download([task.episode])
|
self.mygpo_client.on_download([task.episode])
|
||||||
self.queue_task(task, force_start)
|
self.queue_task(task, force_start)
|
||||||
|
if progress_indicator:
|
||||||
|
n += 1
|
||||||
|
progress_indicator.on_message('%d / %d' % (n, count))
|
||||||
|
progress_indicator.on_progress(n / count)
|
||||||
|
if time.time() >= progress_indicator.next_update:
|
||||||
|
progress_indicator.update_gui()
|
||||||
|
self.force_ui_update()
|
||||||
|
if not progress_indicator.cancellable:
|
||||||
|
break
|
||||||
if tasks or queued_existing_task:
|
if tasks or queued_existing_task:
|
||||||
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
|
self.set_download_list_state(gPodderSyncUI.DL_ONEOFF)
|
||||||
# Flush updated episode status
|
# Flush updated episode status
|
||||||
if self.mygpo_client.can_access_webservice():
|
if self.mygpo_client.can_access_webservice():
|
||||||
self.mygpo_client.flush()
|
self.mygpo_client.flush()
|
||||||
|
|
||||||
|
if progress_indicator:
|
||||||
|
progress_indicator.on_finished()
|
||||||
|
|
||||||
queued_existing_task = False
|
queued_existing_task = False
|
||||||
new_tasks = []
|
new_tasks = []
|
||||||
|
|
||||||
|
@ -3907,10 +3985,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
|
|
||||||
def on_wNotebook_switch_page(self, notebook, page, page_num):
|
def on_wNotebook_switch_page(self, notebook, page, page_num):
|
||||||
self.play_or_download(in_downloads=page_num > 0)
|
self.play_or_download(in_downloads=page_num > 0)
|
||||||
if page_num == 0:
|
|
||||||
# The infobar in the downloads tab should be hidden
|
|
||||||
# when the user switches away from the downloads tab
|
|
||||||
self.resume_all_infobar.set_revealed(False)
|
|
||||||
|
|
||||||
def on_treeChannels_row_activated(self, widget, path, *args):
|
def on_treeChannels_row_activated(self, widget, path, *args):
|
||||||
self.navigate_from_shownotes()
|
self.navigate_from_shownotes()
|
||||||
|
@ -4081,7 +4155,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
selected_tasks = [(Gtk.TreeRowReference.new(model, path),
|
selected_tasks = [(Gtk.TreeRowReference.new(model, path),
|
||||||
model.get_value(model.get_iter(path),
|
model.get_value(model.get_iter(path),
|
||||||
DownloadStatusModel.C_TASK)) for path in paths]
|
DownloadStatusModel.C_TASK)) for path in paths]
|
||||||
self._for_each_task_set_status(selected_tasks, status=download.DownloadTask.QUEUED, force_start=False)
|
self._for_each_task_set_status(selected_tasks, download.DownloadTask.QUEUED)
|
||||||
else:
|
else:
|
||||||
episodes = [e for e in self.get_selected_episodes() if e.can_download()]
|
episodes = [e for e in self.get_selected_episodes() if e.can_download()]
|
||||||
self.download_episode_list(episodes)
|
self.download_episode_list(episodes)
|
||||||
|
@ -4094,7 +4168,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
selected_tasks = [(Gtk.TreeRowReference.new(model, path),
|
selected_tasks = [(Gtk.TreeRowReference.new(model, path),
|
||||||
model.get_value(model.get_iter(path),
|
model.get_value(model.get_iter(path),
|
||||||
DownloadStatusModel.C_TASK)) for path in paths]
|
DownloadStatusModel.C_TASK)) for path in paths]
|
||||||
self._for_each_task_set_status(selected_tasks, status=download.DownloadTask.PAUSING, force_start=False)
|
self._for_each_task_set_status(selected_tasks, download.DownloadTask.PAUSING)
|
||||||
else:
|
else:
|
||||||
for episode in self.get_selected_episodes():
|
for episode in self.get_selected_episodes():
|
||||||
if episode.can_pause():
|
if episode.can_pause():
|
||||||
|
@ -4158,7 +4232,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
def restart_auto_update_timer(self):
|
def restart_auto_update_timer(self):
|
||||||
if self._auto_update_timer_source_id is not None:
|
if self._auto_update_timer_source_id is not None:
|
||||||
logger.debug('Removing existing auto update timer.')
|
logger.debug('Removing existing auto update timer.')
|
||||||
GObject.source_remove(self._auto_update_timer_source_id)
|
GLib.source_remove(self._auto_update_timer_source_id)
|
||||||
self._auto_update_timer_source_id = None
|
self._auto_update_timer_source_id = None
|
||||||
|
|
||||||
if (self.config.auto_update_feeds and
|
if (self.config.auto_update_feeds and
|
||||||
|
@ -4166,7 +4240,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
interval = 60 * 1000 * self.config.auto_update_frequency
|
interval = 60 * 1000 * self.config.auto_update_frequency
|
||||||
logger.debug('Setting up auto update timer with interval %d.',
|
logger.debug('Setting up auto update timer with interval %d.',
|
||||||
self.config.auto_update_frequency)
|
self.config.auto_update_frequency)
|
||||||
self._auto_update_timer_source_id = GObject.timeout_add(
|
self._auto_update_timer_source_id = GLib.timeout_add(
|
||||||
interval, self._on_auto_update_timer)
|
interval, self._on_auto_update_timer)
|
||||||
|
|
||||||
def _on_auto_update_timer(self):
|
def _on_auto_update_timer(self):
|
||||||
|
|
|
@ -30,7 +30,7 @@ import re
|
||||||
import time
|
import time
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from gi.repository import GdkPixbuf, GObject, Gtk
|
from gi.repository import GdkPixbuf, GLib, GObject, Gtk
|
||||||
|
|
||||||
import gpodder
|
import gpodder
|
||||||
from gpodder import coverart, model, query, util
|
from gpodder import coverart, model, query, util
|
||||||
|
@ -331,10 +331,10 @@ class EpisodeListModel(Gtk.ListStore):
|
||||||
|
|
||||||
def _update_from_episodes(self, episodes, include_description):
|
def _update_from_episodes(self, episodes, include_description):
|
||||||
if self.background_update_tag is not None:
|
if self.background_update_tag is not None:
|
||||||
GObject.source_remove(self.background_update_tag)
|
GLib.source_remove(self.background_update_tag)
|
||||||
|
|
||||||
self.background_update = BackgroundUpdate(self, episodes, include_description)
|
self.background_update = BackgroundUpdate(self, episodes, include_description)
|
||||||
self.background_update_tag = GObject.idle_add(self._update_background)
|
self.background_update_tag = GLib.idle_add(self._update_background)
|
||||||
|
|
||||||
def _update_background(self):
|
def _update_background(self):
|
||||||
if self.background_update is not None:
|
if self.background_update is not None:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# gPodder - A media aggregator and podcast client
|
# gPodder - A media aggregator and podcast client
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# gPodder - A media aggregator and podcast client
|
# gPodder - A media aggregator and podcast client
|
||||||
|
|
|
@ -497,7 +497,7 @@ class PodcastEpisode(PodcastModelObject):
|
||||||
PAUSING and PAUSED tasks can be resumed.
|
PAUSING and PAUSED tasks can be resumed.
|
||||||
"""
|
"""
|
||||||
return not self.was_downloaded(and_exists=True) and (
|
return not self.was_downloaded(and_exists=True) and (
|
||||||
not self.download_task
|
self.download_task is None
|
||||||
or self.download_task.can_queue()
|
or self.download_task.can_queue()
|
||||||
or self.download_task.status == self.download_task.PAUSING)
|
or self.download_task.status == self.download_task.PAUSING)
|
||||||
|
|
||||||
|
@ -505,20 +505,20 @@ class PodcastEpisode(PodcastModelObject):
|
||||||
"""
|
"""
|
||||||
gPodder.on_pause_selected_episodes() filters selection with this method.
|
gPodder.on_pause_selected_episodes() filters selection with this method.
|
||||||
"""
|
"""
|
||||||
return self.download_task and self.download_task.can_pause()
|
return self.download_task is not None and self.download_task.can_pause()
|
||||||
|
|
||||||
def can_cancel(self):
|
def can_cancel(self):
|
||||||
"""
|
"""
|
||||||
DownloadTask.cancel() only cancels the following tasks.
|
DownloadTask.cancel() only cancels the following tasks.
|
||||||
"""
|
"""
|
||||||
return self.download_task and self.download_task.can_cancel()
|
return self.download_task is not None and self.download_task.can_cancel()
|
||||||
|
|
||||||
def can_delete(self):
|
def can_delete(self):
|
||||||
"""
|
"""
|
||||||
gPodder.delete_episode_list() filters out locked episodes, and cancels all unlocked tasks in selection.
|
gPodder.delete_episode_list() filters out locked episodes, and cancels all unlocked tasks in selection.
|
||||||
"""
|
"""
|
||||||
return self.state != gpodder.STATE_DELETED and not self.archive and (
|
return self.state != gpodder.STATE_DELETED and not self.archive and (
|
||||||
not self.download_task or self.download_task.status == self.download_task.FAILED)
|
self.download_task is None or self.download_task.status == self.download_task.FAILED)
|
||||||
|
|
||||||
def can_lock(self):
|
def can_lock(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# gPodder - A media aggregator and podcast client
|
# gPodder - A media aggregator and podcast client
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# gPodder - A media aggregator and podcast client
|
# gPodder - A media aggregator and podcast client
|
||||||
|
|
|
@ -458,16 +458,20 @@ class MP3PlayerDevice(Device):
|
||||||
self.destination.get_uri(), err.message)
|
self.destination.get_uri(), err.message)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if info.get_file_type() != Gio.FileType.DIRECTORY:
|
||||||
|
logger.error('destination %s is not a directory', self.destination.get_uri())
|
||||||
|
return False
|
||||||
|
|
||||||
# open is ok if the target is a directory, and it can be written to
|
# open is ok if the target is a directory, and it can be written to
|
||||||
# for smb, query_info doesn't return FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
|
# for smb, query_info doesn't return FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
|
||||||
# -- if that's the case, just assume that it's writable
|
# -- if that's the case, just assume that it's writable
|
||||||
if (info.get_file_type() == Gio.FileType.DIRECTORY and (
|
if (not info.has_attribute(Gio.FILE_ATTRIBUTE_ACCESS_CAN_WRITE) or
|
||||||
not info.has_attribute(Gio.FILE_ATTRIBUTE_ACCESS_CAN_WRITE) or
|
info.get_attribute_boolean(Gio.FILE_ATTRIBUTE_ACCESS_CAN_WRITE)):
|
||||||
info.get_attribute_boolean(Gio.FILE_ATTRIBUTE_ACCESS_CAN_WRITE))):
|
|
||||||
self.notify('status', _('MP3 player opened'))
|
self.notify('status', _('MP3 player opened'))
|
||||||
self.tracks_list = self.get_all_tracks()
|
self.tracks_list = self.get_all_tracks()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
logger.error('destination %s is not writable', self.destination.get_uri())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_episode_folder_on_device(self, episode):
|
def get_episode_folder_on_device(self, episode):
|
||||||
|
|
|
@ -105,14 +105,22 @@ class gPodderSyncUI(object):
|
||||||
done_callback()
|
done_callback()
|
||||||
return
|
return
|
||||||
|
|
||||||
if not device.open():
|
try:
|
||||||
|
if not device.open():
|
||||||
|
self._show_message_cannot_open()
|
||||||
|
if done_callback:
|
||||||
|
done_callback()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Only set if device is configured and opened successfully
|
||||||
|
self.device = device
|
||||||
|
except Exception as err:
|
||||||
|
logger.error('opening destination %s failed with %s',
|
||||||
|
device.destination.get_uri(), err.message)
|
||||||
self._show_message_cannot_open()
|
self._show_message_cannot_open()
|
||||||
if done_callback:
|
if done_callback:
|
||||||
done_callback()
|
done_callback()
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
# Only set if device is configured and opened successfully
|
|
||||||
self.device = device
|
|
||||||
|
|
||||||
if episodes is None:
|
if episodes is None:
|
||||||
force_played = False
|
force_played = False
|
||||||
|
|
|
@ -1318,12 +1318,40 @@ def idle_add(func, *args):
|
||||||
as possible from the main UI thread.
|
as possible from the main UI thread.
|
||||||
"""
|
"""
|
||||||
if gpodder.ui.gtk:
|
if gpodder.ui.gtk:
|
||||||
from gi.repository import GObject
|
from gi.repository import GLib
|
||||||
GObject.idle_add(func, *args)
|
GLib.idle_add(func, *args)
|
||||||
else:
|
else:
|
||||||
func(*args)
|
func(*args)
|
||||||
|
|
||||||
|
|
||||||
|
class IdleTimeout(object):
|
||||||
|
"""Run a function in the main GUI thread at regular intervals since the last run
|
||||||
|
|
||||||
|
A simple timeout_add() continuously calls the function if it exceeds the interval,
|
||||||
|
which lags the UI and prevents idle_add() calls from happening. This class restarts
|
||||||
|
the timer after the function finishes, allowing other callbacks to run.
|
||||||
|
"""
|
||||||
|
def __init__(self, milliseconds, func, *args):
|
||||||
|
if not gpodder.ui.gtk:
|
||||||
|
raise Exception('util.IdleTimeout() is only supported by Gtk+')
|
||||||
|
self.milliseconds = milliseconds
|
||||||
|
self.func = func
|
||||||
|
from gi.repository import GLib
|
||||||
|
self.id = GLib.timeout_add(milliseconds, self._callback, *args)
|
||||||
|
|
||||||
|
def _callback(self, *args):
|
||||||
|
self.cancel()
|
||||||
|
if self.func(*args):
|
||||||
|
from gi.repository import GLib
|
||||||
|
self.id = GLib.timeout_add(self.milliseconds, self._callback, *args)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
if self.id:
|
||||||
|
from gi.repository import GLib
|
||||||
|
GLib.source_remove(self.id)
|
||||||
|
self.id = 0
|
||||||
|
|
||||||
|
|
||||||
def bluetooth_available():
|
def bluetooth_available():
|
||||||
"""
|
"""
|
||||||
Returns True or False depending on the availability
|
Returns True or False depending on the availability
|
||||||
|
@ -1358,7 +1386,7 @@ def bluetooth_send_file(filename):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def format_time(value):
|
def format_time(seconds):
|
||||||
"""Format a seconds value to a string
|
"""Format a seconds value to a string
|
||||||
|
|
||||||
>>> format_time(0)
|
>>> format_time(0)
|
||||||
|
@ -1369,12 +1397,22 @@ def format_time(value):
|
||||||
'01:00:00'
|
'01:00:00'
|
||||||
>>> format_time(10921)
|
>>> format_time(10921)
|
||||||
'03:02:01'
|
'03:02:01'
|
||||||
|
>>> format_time(86401)
|
||||||
|
'24:00:01'
|
||||||
"""
|
"""
|
||||||
dt = datetime.datetime.utcfromtimestamp(value)
|
hours = 0
|
||||||
if dt.hour == 0:
|
minutes = 0
|
||||||
return dt.strftime('%M:%S')
|
if seconds >= 3600:
|
||||||
|
hours = seconds // 3600
|
||||||
|
seconds -= hours * 3600
|
||||||
|
if seconds >= 60:
|
||||||
|
minutes = seconds // 60
|
||||||
|
seconds -= minutes * 60
|
||||||
|
|
||||||
|
if hours == 0:
|
||||||
|
return '%02d:%02d' % (minutes, seconds)
|
||||||
else:
|
else:
|
||||||
return dt.strftime('%H:%M:%S')
|
return '%02d:%02d:%02d' % (hours, minutes, seconds)
|
||||||
|
|
||||||
|
|
||||||
def parse_time(value):
|
def parse_time(value):
|
||||||
|
|
|
@ -66,14 +66,14 @@ cp -a "$checkout"/tools/mac-osx/launcher.py "$resources"/
|
||||||
cp -a "$checkout"/tools/mac-osx/make_cert_pem.py "$resources"/bin
|
cp -a "$checkout"/tools/mac-osx/make_cert_pem.py "$resources"/bin
|
||||||
|
|
||||||
# install gPodder hard dependencies
|
# install gPodder hard dependencies
|
||||||
$run_pip install setuptools wheel
|
$run_pip install setuptools==64.0.3 wheel || exit 1
|
||||||
$run_pip install mygpoclient==1.9 podcastparser==0.6.8 requests[socks]==2.28.1
|
$run_pip install mygpoclient==1.9 podcastparser==0.6.8 requests[socks]==2.28.1 || exit 1
|
||||||
# install brotli and pycryptodomex (build from source)
|
# install brotli and pycryptodomex (build from source)
|
||||||
$run_pip debug -v
|
$run_pip debug -v
|
||||||
$run_pip install -v brotli
|
$run_pip install -v brotli || exit 1
|
||||||
$run_pip install -v pycryptodomex
|
$run_pip install -v pycryptodomex || exit 1
|
||||||
# install extension dependencies; no explicit version for yt-dlp
|
# install extension dependencies; no explicit version for yt-dlp
|
||||||
$run_pip install html5lib==1.1 mutagen==1.45.1 yt-dlp
|
$run_pip install html5lib==1.1 mutagen==1.45.1 yt-dlp || exit 1
|
||||||
|
|
||||||
cd "$checkout"
|
cd "$checkout"
|
||||||
touch share/applications/gpodder{,-url-handler}.desktop
|
touch share/applications/gpodder{,-url-handler}.desktop
|
||||||
|
|
|
@ -473,10 +473,10 @@ function build_portable_installer {
|
||||||
mkdir "$PORTABLE"/config
|
mkdir "$PORTABLE"/config
|
||||||
cp -RT "${MINGW_ROOT}" "$PORTABLE"/data
|
cp -RT "${MINGW_ROOT}" "$PORTABLE"/data
|
||||||
|
|
||||||
rm -Rf 7zout 7z1604.exe
|
rm -Rf 7zout 7z2201.exe
|
||||||
7z a payload.7z "$PORTABLE"
|
7z a payload.7z "$PORTABLE"
|
||||||
wget -P "$DIR" -c http://www.7-zip.org/a/7z1604.exe
|
wget -P "$DIR" -c http://www.7-zip.org/a/7z2201.exe
|
||||||
7z x -o7zout 7z1604.exe
|
7z x -o7zout 7z2201.exe
|
||||||
cat 7zout/7z.sfx payload.7z > "$PORTABLE".exe
|
cat 7zout/7z.sfx payload.7z > "$PORTABLE".exe
|
||||||
rm -Rf 7zout 7z1604.exe payload.7z "$PORTABLE"
|
rm -Rf 7zout 7z2201.exe payload.7z "$PORTABLE"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue