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)
|
||||
episodes [--guid] [URL] List episodes with or without GUIDs (all or only from URL)
|
||||
|
||||
- Episode management -
|
||||
|
||||
sync Sync podcasts to device
|
||||
|
||||
- Configuration -
|
||||
|
||||
set [key] [value] List one (all) keys or set to a new value
|
||||
|
@ -75,6 +79,7 @@ import pydoc
|
|||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import threading
|
||||
|
||||
try:
|
||||
import readline
|
||||
|
@ -112,8 +117,9 @@ if os.path.exists(os.path.join(src_dir, 'gpodder', '__init__.py')):
|
|||
sys.path.insert(0, src_dir)
|
||||
|
||||
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.syncui import gPodderSyncUI # isort:skip
|
||||
|
||||
_ = gpodder.gettext
|
||||
N_ = gpodder.ngettext
|
||||
|
@ -742,6 +748,167 @@ class gPodderCli(object):
|
|||
print(stylize(__doc__), file=sys.stderr, end='')
|
||||
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):
|
||||
|
|
|
@ -51,7 +51,6 @@ from gpodder.gtkui.desktop.episodeselector import gPodderEpisodeSelector
|
|||
from gpodder.gtkui.desktop.exportlocal import gPodderExportToLocalFolder
|
||||
from gpodder.gtkui.desktop.podcastdirectory import gPodderPodcastDirectory
|
||||
from gpodder.gtkui.desktop.preferences import gPodderPreferences
|
||||
from gpodder.gtkui.desktop.sync import gPodderSyncUI
|
||||
from gpodder.gtkui.desktop.welcome import gPodderWelcome
|
||||
from gpodder.gtkui.desktopfile import UserAppsReader
|
||||
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.widgets import SimpleMessageArea
|
||||
from gpodder.model import PodcastEpisode, check_root_folder_path
|
||||
from gpodder.syncui import gPodderSyncUI
|
||||
|
||||
import gi # 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.enable_download_list_update,
|
||||
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):
|
||||
if not self.config.youtube.api_key_v3:
|
||||
|
|
|
@ -26,8 +26,7 @@ import os
|
|||
|
||||
import gpodder
|
||||
from gpodder import sync, util
|
||||
from gpodder.gtkui.desktop.deviceplaylist import gPodderDevicePlaylist
|
||||
from gpodder.gtkui.desktop.episodeselector import gPodderEpisodeSelector
|
||||
from gpodder.deviceplaylist import gPodderDevicePlaylist
|
||||
|
||||
_ = gpodder.gettext
|
||||
|
||||
|
@ -44,7 +43,8 @@ class gPodderSyncUI(object):
|
|||
download_queue_manager,
|
||||
enable_download_list_update,
|
||||
commit_changes_to_database,
|
||||
delete_episode_list):
|
||||
delete_episode_list,
|
||||
select_episodes_to_delete):
|
||||
self.device = None
|
||||
|
||||
self._config = config
|
||||
|
@ -59,6 +59,7 @@ class gPodderSyncUI(object):
|
|||
self.enable_download_list_update = enable_download_list_update
|
||||
self.commit_changes_to_database = commit_changes_to_database
|
||||
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):
|
||||
"""Return a list of episodes for device synchronization
|
||||
|
@ -90,7 +91,7 @@ class gPodderSyncUI(object):
|
|||
message = _('Please check the settings in the preferences dialog.')
|
||||
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)
|
||||
|
||||
if device is None:
|
||||
|
@ -205,7 +206,7 @@ class gPodderSyncUI(object):
|
|||
@util.run_in_background
|
||||
def sync_thread_func():
|
||||
device.add_sync_tasks(episodes, force_played=force_played,
|
||||
done_callback=self.enable_download_list_update)
|
||||
done_callback=done_callback)
|
||||
|
||||
return
|
||||
|
||||
|
@ -269,7 +270,7 @@ class gPodderSyncUI(object):
|
|||
('markup_delete_episodes', None, None, _('Episode')),
|
||||
)
|
||||
|
||||
gPodderEpisodeSelector(
|
||||
self.select_episodes_to_delete(
|
||||
self.parent_window,
|
||||
title=_('Episodes have been deleted on device'),
|
||||
instructions='Select the episodes you want to delete:',
|
Loading…
Reference in New Issue