fix #243 add sync command to gpo
This commit is contained in:
parent
d34c814909
commit
97db9c97d2
169
bin/gpo
169
bin/gpo
|
@ -52,6 +52,10 @@
|
||||||
pending [URL] List new episodes (all or only from URL)
|
pending [URL] List new episodes (all or only from URL)
|
||||||
episodes [--guid] [URL] List episodes with or without GUIDs (all or only from URL)
|
episodes [--guid] [URL] List episodes with or without GUIDs (all or only from URL)
|
||||||
|
|
||||||
|
- Episode management -
|
||||||
|
|
||||||
|
sync Sync podcasts to device
|
||||||
|
|
||||||
- Configuration -
|
- Configuration -
|
||||||
|
|
||||||
set [key] [value] List one (all) keys or set to a new value
|
set [key] [value] List one (all) keys or set to a new value
|
||||||
|
@ -75,6 +79,7 @@ import pydoc
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import readline
|
import readline
|
||||||
|
@ -112,8 +117,9 @@ if os.path.exists(os.path.join(src_dir, 'gpodder', '__init__.py')):
|
||||||
sys.path.insert(0, src_dir)
|
sys.path.insert(0, src_dir)
|
||||||
|
|
||||||
import gpodder # isort:skip
|
import gpodder # isort:skip
|
||||||
from gpodder import common, core, download, log, model, my, opml, util, youtube # isort:skip
|
from gpodder import common, core, download, log, model, my, opml, sync, util, youtube # isort:skip
|
||||||
from gpodder.config import config_value_to_string # isort:skip
|
from gpodder.config import config_value_to_string # isort:skip
|
||||||
|
from gpodder.syncui import gPodderSyncUI # isort:skip
|
||||||
|
|
||||||
_ = gpodder.gettext
|
_ = gpodder.gettext
|
||||||
N_ = gpodder.ngettext
|
N_ = gpodder.ngettext
|
||||||
|
@ -742,6 +748,167 @@ class gPodderCli(object):
|
||||||
print(stylize(__doc__), file=sys.stderr, end='')
|
print(stylize(__doc__), file=sys.stderr, end='')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def sync(self):
|
||||||
|
def ep_repr(episode):
|
||||||
|
return '{} / {}'.format(episode.channel.title, episode.title)
|
||||||
|
|
||||||
|
def msg_title(title, message):
|
||||||
|
if title:
|
||||||
|
msg = '{}: {}'.format(title, message)
|
||||||
|
else:
|
||||||
|
msg = '{}'.format(message)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def _notification(message, title=None, important=False, widget=None):
|
||||||
|
print(msg_title(message, title))
|
||||||
|
|
||||||
|
def _show_confirmation(message, title=None):
|
||||||
|
msg = msg_title(message, title)
|
||||||
|
msg = _("%(title)s: %(msg)s ([yes]/no): ") % dict(title=title, msg=message)
|
||||||
|
if not interactive_console:
|
||||||
|
return True
|
||||||
|
line = input(msg)
|
||||||
|
return not line or (line.lower() == _('yes'))
|
||||||
|
|
||||||
|
def _delete_episode_list(episodes, confirm=True, skip_locked=True, callback=None):
|
||||||
|
if not episodes:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if skip_locked:
|
||||||
|
episodes = [e for e in episodes if not e.archive]
|
||||||
|
|
||||||
|
if not episodes:
|
||||||
|
title = _('Episodes are locked')
|
||||||
|
message = _(
|
||||||
|
'The selected episodes are locked. Please unlock the '
|
||||||
|
'episodes that you want to delete before trying '
|
||||||
|
'to delete them.')
|
||||||
|
_notification(message, title)
|
||||||
|
return False
|
||||||
|
|
||||||
|
count = len(episodes)
|
||||||
|
title = N_('Delete %(count)d episode?', 'Delete %(count)d episodes?',
|
||||||
|
count) % {'count': count}
|
||||||
|
message = _('Deleting episodes removes downloaded files.')
|
||||||
|
|
||||||
|
if confirm and not _show_confirmation(message, title):
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(_('Please wait while episodes are deleted'))
|
||||||
|
|
||||||
|
def finish_deletion(episode_urls, channel_urls):
|
||||||
|
# Episodes have been deleted - persist the database
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
episode_urls = set()
|
||||||
|
channel_urls = set()
|
||||||
|
|
||||||
|
episodes_status_update = []
|
||||||
|
for idx, episode in enumerate(episodes):
|
||||||
|
if not episode.archive or not skip_locked:
|
||||||
|
self._start_action(_('Deleting episode: %(episode)s') % {
|
||||||
|
'episode': episode.title})
|
||||||
|
episode.delete_from_disk()
|
||||||
|
self._finish_action(success=True)
|
||||||
|
episode_urls.add(episode.url)
|
||||||
|
channel_urls.add(episode.channel.url)
|
||||||
|
episodes_status_update.append(episode)
|
||||||
|
|
||||||
|
# Notify the web service about the status update + upload
|
||||||
|
if self.mygpo_client.can_access_webservice():
|
||||||
|
self.mygpo_client.on_delete(episodes_status_update)
|
||||||
|
self.mygpo_client.flush()
|
||||||
|
|
||||||
|
if callback is None:
|
||||||
|
util.idle_add(finish_deletion, episode_urls, channel_urls)
|
||||||
|
else:
|
||||||
|
util.idle_add(callback, episode_urls, channel_urls, None)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _episode_selector(parent_window, title=None, instructions=None, episodes=None,
|
||||||
|
selected=None, columns=None, callback=None, _config=None):
|
||||||
|
if not interactive_console:
|
||||||
|
return callback([e for i, e in enumerate(episodes) if selected[i]])
|
||||||
|
|
||||||
|
def show_list():
|
||||||
|
self._pager('\n'.join(
|
||||||
|
'[%s] %3d: %s' % (('X' if selected[index] else ' '), index + 1, ep_repr(e))
|
||||||
|
for index, e in enumerate(episodes)))
|
||||||
|
|
||||||
|
print("{}. {}".format(title, instructions))
|
||||||
|
show_list()
|
||||||
|
|
||||||
|
msg = _('Enter episode index to toggle, ? for list, X to select all, space to select none, empty when ready')
|
||||||
|
while True:
|
||||||
|
index = input(msg + ': ')
|
||||||
|
|
||||||
|
if not index:
|
||||||
|
return callback([e for i, e in enumerate(episodes) if selected[i]])
|
||||||
|
|
||||||
|
if index == '?':
|
||||||
|
show_list()
|
||||||
|
continue
|
||||||
|
elif index == 'X':
|
||||||
|
selected = [True, ] * len(episodes)
|
||||||
|
show_list()
|
||||||
|
continue
|
||||||
|
elif index == ' ':
|
||||||
|
selected = [False, ] * len(episodes)
|
||||||
|
show_list()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
index = int(index)
|
||||||
|
except ValueError:
|
||||||
|
self._error(_('Invalid value.'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not (1 <= index <= len(episodes)):
|
||||||
|
self._error(_('Invalid value.'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
e = episodes[index - 1]
|
||||||
|
selected[index - 1] = not selected[index - 1]
|
||||||
|
if selected[index - 1]:
|
||||||
|
self._info(_('Will delete %(episode)s') % dict(episode=ep_repr(e)))
|
||||||
|
else:
|
||||||
|
self._info(_("Won't delete %(episode)s") % dict(episode=ep_repr(e)))
|
||||||
|
|
||||||
|
def _not_applicable(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DownloadStatusModel(object):
|
||||||
|
def register_task(self, ask):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DownloadQueueManager(object):
|
||||||
|
def queue_task(x, task):
|
||||||
|
def progress_updated(progress):
|
||||||
|
self._update_action(progress)
|
||||||
|
with self._action(_('Syncing %s'), ep_repr(task.episode)):
|
||||||
|
task.status = sync.SyncTask.DOWNLOADING
|
||||||
|
task.add_progress_callback(progress_updated)
|
||||||
|
task.run()
|
||||||
|
|
||||||
|
done_lock = threading.Lock()
|
||||||
|
self.mygpo_client = my.MygPoClient(self._config)
|
||||||
|
sync_ui = gPodderSyncUI(self._config,
|
||||||
|
_notification,
|
||||||
|
None,
|
||||||
|
_show_confirmation,
|
||||||
|
_not_applicable,
|
||||||
|
self._model.get_podcasts(),
|
||||||
|
DownloadStatusModel(),
|
||||||
|
DownloadQueueManager(),
|
||||||
|
_not_applicable,
|
||||||
|
self._db.commit,
|
||||||
|
_delete_episode_list,
|
||||||
|
_episode_selector)
|
||||||
|
done_lock.acquire()
|
||||||
|
sync_ui.on_synchronize_episodes(self._model.get_podcasts(), episodes=None, force_played=True, done_callback=done_lock.release)
|
||||||
|
done_lock.acquire() # block until done
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
def _pager(self, output):
|
def _pager(self, output):
|
||||||
|
|
|
@ -51,7 +51,6 @@ from gpodder.gtkui.desktop.episodeselector import gPodderEpisodeSelector
|
||||||
from gpodder.gtkui.desktop.exportlocal import gPodderExportToLocalFolder
|
from gpodder.gtkui.desktop.exportlocal import gPodderExportToLocalFolder
|
||||||
from gpodder.gtkui.desktop.podcastdirectory import gPodderPodcastDirectory
|
from gpodder.gtkui.desktop.podcastdirectory import gPodderPodcastDirectory
|
||||||
from gpodder.gtkui.desktop.preferences import gPodderPreferences
|
from gpodder.gtkui.desktop.preferences import gPodderPreferences
|
||||||
from gpodder.gtkui.desktop.sync import gPodderSyncUI
|
|
||||||
from gpodder.gtkui.desktop.welcome import gPodderWelcome
|
from gpodder.gtkui.desktop.welcome import gPodderWelcome
|
||||||
from gpodder.gtkui.desktopfile import UserAppsReader
|
from gpodder.gtkui.desktopfile import UserAppsReader
|
||||||
from gpodder.gtkui.download import DownloadStatusModel
|
from gpodder.gtkui.download import DownloadStatusModel
|
||||||
|
@ -64,6 +63,7 @@ from gpodder.gtkui.model import EpisodeListModel, Model, PodcastListModel
|
||||||
from gpodder.gtkui.services import CoverDownloader
|
from gpodder.gtkui.services import CoverDownloader
|
||||||
from gpodder.gtkui.widgets import SimpleMessageArea
|
from gpodder.gtkui.widgets import SimpleMessageArea
|
||||||
from gpodder.model import PodcastEpisode, check_root_folder_path
|
from gpodder.model import PodcastEpisode, check_root_folder_path
|
||||||
|
from gpodder.syncui import gPodderSyncUI
|
||||||
|
|
||||||
import gi # isort:skip
|
import gi # isort:skip
|
||||||
gi.require_version('Gtk', '3.0') # isort:skip
|
gi.require_version('Gtk', '3.0') # isort:skip
|
||||||
|
@ -3612,9 +3612,11 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
||||||
self.download_queue_manager,
|
self.download_queue_manager,
|
||||||
self.enable_download_list_update,
|
self.enable_download_list_update,
|
||||||
self.commit_changes_to_database,
|
self.commit_changes_to_database,
|
||||||
self.delete_episode_list)
|
self.delete_episode_list,
|
||||||
|
gPodderEpisodeSelector)
|
||||||
|
|
||||||
self.sync_ui.on_synchronize_episodes(self.channels, episodes, force_played)
|
self.sync_ui.on_synchronize_episodes(self.channels, episodes, force_played,
|
||||||
|
self.enable_download_list_update)
|
||||||
|
|
||||||
def on_update_youtube_subscriptions_activate(self, action, param):
|
def on_update_youtube_subscriptions_activate(self, action, param):
|
||||||
if not self.config.youtube.api_key_v3:
|
if not self.config.youtube.api_key_v3:
|
||||||
|
|
|
@ -26,8 +26,7 @@ import os
|
||||||
|
|
||||||
import gpodder
|
import gpodder
|
||||||
from gpodder import sync, util
|
from gpodder import sync, util
|
||||||
from gpodder.gtkui.desktop.deviceplaylist import gPodderDevicePlaylist
|
from gpodder.deviceplaylist import gPodderDevicePlaylist
|
||||||
from gpodder.gtkui.desktop.episodeselector import gPodderEpisodeSelector
|
|
||||||
|
|
||||||
_ = gpodder.gettext
|
_ = gpodder.gettext
|
||||||
|
|
||||||
|
@ -44,7 +43,8 @@ class gPodderSyncUI(object):
|
||||||
download_queue_manager,
|
download_queue_manager,
|
||||||
enable_download_list_update,
|
enable_download_list_update,
|
||||||
commit_changes_to_database,
|
commit_changes_to_database,
|
||||||
delete_episode_list):
|
delete_episode_list,
|
||||||
|
select_episodes_to_delete):
|
||||||
self.device = None
|
self.device = None
|
||||||
|
|
||||||
self._config = config
|
self._config = config
|
||||||
|
@ -59,6 +59,7 @@ class gPodderSyncUI(object):
|
||||||
self.enable_download_list_update = enable_download_list_update
|
self.enable_download_list_update = enable_download_list_update
|
||||||
self.commit_changes_to_database = commit_changes_to_database
|
self.commit_changes_to_database = commit_changes_to_database
|
||||||
self.delete_episode_list = delete_episode_list
|
self.delete_episode_list = delete_episode_list
|
||||||
|
self.select_episodes_to_delete = select_episodes_to_delete
|
||||||
|
|
||||||
def _filter_sync_episodes(self, channels, only_downloaded=False):
|
def _filter_sync_episodes(self, channels, only_downloaded=False):
|
||||||
"""Return a list of episodes for device synchronization
|
"""Return a list of episodes for device synchronization
|
||||||
|
@ -90,7 +91,7 @@ class gPodderSyncUI(object):
|
||||||
message = _('Please check the settings in the preferences dialog.')
|
message = _('Please check the settings in the preferences dialog.')
|
||||||
self.notification(message, title, important=True)
|
self.notification(message, title, important=True)
|
||||||
|
|
||||||
def on_synchronize_episodes(self, channels, episodes=None, force_played=True):
|
def on_synchronize_episodes(self, channels, episodes=None, force_played=True, done_callback=None):
|
||||||
device = sync.open_device(self)
|
device = sync.open_device(self)
|
||||||
|
|
||||||
if device is None:
|
if device is None:
|
||||||
|
@ -205,7 +206,7 @@ class gPodderSyncUI(object):
|
||||||
@util.run_in_background
|
@util.run_in_background
|
||||||
def sync_thread_func():
|
def sync_thread_func():
|
||||||
device.add_sync_tasks(episodes, force_played=force_played,
|
device.add_sync_tasks(episodes, force_played=force_played,
|
||||||
done_callback=self.enable_download_list_update)
|
done_callback=done_callback)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -269,7 +270,7 @@ class gPodderSyncUI(object):
|
||||||
('markup_delete_episodes', None, None, _('Episode')),
|
('markup_delete_episodes', None, None, _('Episode')),
|
||||||
)
|
)
|
||||||
|
|
||||||
gPodderEpisodeSelector(
|
self.select_episodes_to_delete(
|
||||||
self.parent_window,
|
self.parent_window,
|
||||||
title=_('Episodes have been deleted on device'),
|
title=_('Episodes have been deleted on device'),
|
||||||
instructions='Select the episodes you want to delete:',
|
instructions='Select the episodes you want to delete:',
|
Loading…
Reference in a new issue