Deprecate the old-style command-line interface
Add a interim "sync" command to the "gpo" utility to replace all the functionality of the old "gpodder" CLI parameters (which had a "--sync" switch).
This commit is contained in:
parent
c854935fc9
commit
806c719c4b
4 changed files with 47 additions and 209 deletions
6
bin/gpo
6
bin/gpo
|
@ -62,6 +62,7 @@
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
device Show information about your device
|
device Show information about your device
|
||||||
|
sync Synchronize downloaded episodes to device
|
||||||
|
|
||||||
Helper commands
|
Helper commands
|
||||||
---------------
|
---------------
|
||||||
|
@ -93,6 +94,8 @@ if os.path.exists(src_dir) and os.path.exists(data_dir) and \
|
||||||
import gpodder
|
import gpodder
|
||||||
_ = gpodder.gettext
|
_ = gpodder.gettext
|
||||||
|
|
||||||
|
# This is the command-line interface to gPodder
|
||||||
|
gpodder.interface = gpodder.CLI
|
||||||
|
|
||||||
# Use only the gPodder API here, so this serves both as an example
|
# Use only the gPodder API here, so this serves both as an example
|
||||||
# and as a motivation to provide all functionality in the API :)
|
# and as a motivation to provide all functionality in the API :)
|
||||||
|
@ -216,6 +219,9 @@ class gPodderCli(object):
|
||||||
print count, 'episodes downloaded.'
|
print count, 'episodes downloaded.'
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def sync(self):
|
||||||
|
api.synchronize_device()
|
||||||
|
return True
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
64
bin/gpodder
64
bin/gpodder
|
@ -21,10 +21,13 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
gPodder enables you to subscribe to RSS feeds and download
|
gPodder enables you to subscribe to RSS feeds and download
|
||||||
podcast episodes from these feeds. gPodder can operate in
|
podcast episodes from these feeds.
|
||||||
GUI mode and in CLI mode. Downloaded podcasts can either
|
|
||||||
be synchronized to portable MP3 players (including iPods)
|
Downloaded podcasts can either be synchronized to portable
|
||||||
or played back on the user's desktop.
|
MP3 players (including iPods) or played back on the user's
|
||||||
|
desktop.
|
||||||
|
|
||||||
|
See gpo(1) for the command-line interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
@ -91,53 +94,17 @@ if __name__ == '__main__':
|
||||||
action="store_true", dest="verbose", default=False,
|
action="store_true", dest="verbose", default=False,
|
||||||
help=_("Print debugging output to stdout"))
|
help=_("Print debugging output to stdout"))
|
||||||
|
|
||||||
parser.add_option("-t", "--local",
|
|
||||||
action="store_true", dest="local", default=False,
|
|
||||||
help='Deprecated.')
|
|
||||||
|
|
||||||
parser.add_option("-m", "--maemo",
|
parser.add_option("-m", "--maemo",
|
||||||
action="store_true", dest="maemo", default=False,
|
action="store_true", dest="maemo", default=False,
|
||||||
help=_("Start the Maemo user interface of gPodder"))
|
help=_("Start the Maemo user interface of gPodder"))
|
||||||
|
|
||||||
parser.add_option("-l", "--list",
|
|
||||||
action="store_true", dest="list", default=False,
|
|
||||||
help=_("List all channel subscriptions"))
|
|
||||||
|
|
||||||
parser.add_option("-r", "--run",
|
|
||||||
action="store_true", dest="run", default=False,
|
|
||||||
help=_("Update channel list, download new podcasts"))
|
|
||||||
|
|
||||||
parser.add_option("-u", "--update",
|
|
||||||
action="store_true", dest="update", default=False,
|
|
||||||
help=_("Update channel list and exit"))
|
|
||||||
|
|
||||||
parser.add_option("-s", "--sync",
|
|
||||||
action="store_true", dest="sync", default=False,
|
|
||||||
help=_("Synchronize channels to configured device"))
|
|
||||||
|
|
||||||
parser.add_option("-a", "--add", dest="add",
|
|
||||||
help=_("Subscribe to channel from URL"), metavar="URL")
|
|
||||||
|
|
||||||
parser.add_option("-d", "--delete", dest="delete",
|
|
||||||
help=_("Delete channel specified by URL"), metavar="URL")
|
|
||||||
|
|
||||||
parser.add_option("-S", "--stats",
|
|
||||||
action="store_true", dest="stats", default=False,
|
|
||||||
help=_("Get sync statistics"))
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args(sys.argv)
|
(options, args) = parser.parse_args(sys.argv)
|
||||||
|
|
||||||
if options.maemo:
|
if options.maemo:
|
||||||
gpodder.interface = gpodder.MAEMO
|
gpodder.interface = gpodder.MAEMO
|
||||||
elif options.list or options.run or options.update or \
|
|
||||||
options.sync or options.add or options.delete:
|
|
||||||
gpodder.interface = gpodder.CLI
|
|
||||||
else:
|
else:
|
||||||
gpodder.interface = gpodder.GUI
|
gpodder.interface = gpodder.GUI
|
||||||
|
|
||||||
if options.local:
|
|
||||||
print >>sys.stderr, 'Ignoring deprecated option --local.'
|
|
||||||
|
|
||||||
if options.verbose:
|
if options.verbose:
|
||||||
from gpodder.liblogger import enable_verbose
|
from gpodder.liblogger import enable_verbose
|
||||||
enable_verbose()
|
enable_verbose()
|
||||||
|
@ -155,22 +122,7 @@ if __name__ == '__main__':
|
||||||
# No D-Bus available :/
|
# No D-Bus available :/
|
||||||
remote_object = None
|
remote_object = None
|
||||||
|
|
||||||
from gpodder import console
|
if remote_object is not None:
|
||||||
if options.list:
|
|
||||||
console.list_channels()
|
|
||||||
elif options.run:
|
|
||||||
console.run()
|
|
||||||
elif options.update:
|
|
||||||
console.update()
|
|
||||||
elif options.sync:
|
|
||||||
console.sync_device()
|
|
||||||
elif options.add:
|
|
||||||
console.add_channel( options.add)
|
|
||||||
elif options.delete:
|
|
||||||
console.del_channel( options.delete)
|
|
||||||
elif options.stats:
|
|
||||||
console.sync_stats()
|
|
||||||
elif remote_object is not None:
|
|
||||||
# An instance of GUI is already running
|
# An instance of GUI is already running
|
||||||
remote_object.show_gui_window(dbus_interface=gpodder.dbus_interface)
|
remote_object.show_gui_window(dbus_interface=gpodder.dbus_interface)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -30,6 +30,7 @@ from gpodder.libpodcasts import PodcastChannel
|
||||||
from gpodder.libgpodder import db
|
from gpodder.libgpodder import db
|
||||||
from gpodder.libgpodder import gl
|
from gpodder.libgpodder import gl
|
||||||
from gpodder import download
|
from gpodder import download
|
||||||
|
from gpodder import console
|
||||||
|
|
||||||
class Podcast(object):
|
class Podcast(object):
|
||||||
"""API interface of gPodder podcasts
|
"""API interface of gPodder podcasts
|
||||||
|
@ -152,6 +153,13 @@ def create_podcast(url, title=None):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def synchronize_device():
|
||||||
|
"""Synchronize episodes to a device
|
||||||
|
|
||||||
|
WARNING: API subject to change.
|
||||||
|
"""
|
||||||
|
console.synchronize_device(db, gl.config)
|
||||||
|
|
||||||
|
|
||||||
def finish():
|
def finish():
|
||||||
"""Persist changed data to the database file
|
"""Persist changed data to the database file
|
||||||
|
|
|
@ -17,170 +17,42 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import gpodder
|
|
||||||
from gpodder import util
|
|
||||||
from gpodder import download
|
|
||||||
from gpodder import sync
|
|
||||||
from gpodder import opml
|
|
||||||
from gpodder.libgpodder import gl
|
|
||||||
from gpodder.libgpodder import db
|
|
||||||
from gpodder.liblogger import msg
|
|
||||||
|
|
||||||
from libpodcasts import PodcastChannel
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
import urllib
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import gpodder
|
||||||
|
|
||||||
|
from gpodder import sync
|
||||||
|
from gpodder.libpodcasts import PodcastChannel
|
||||||
|
|
||||||
_ = gpodder.gettext
|
_ = gpodder.gettext
|
||||||
|
|
||||||
def list_channels():
|
def synchronize_device(db, config):
|
||||||
for channel in PodcastChannel.load_from_db(db, gl.config.download_dir):
|
device = sync.open_device(config)
|
||||||
msg('podcast', urllib.unquote(channel.url))
|
|
||||||
|
|
||||||
|
|
||||||
def add_channel( url):
|
|
||||||
callback_error = lambda s: msg( 'error', s)
|
|
||||||
|
|
||||||
url = util.normalize_feed_url(url)
|
|
||||||
|
|
||||||
channels = PodcastChannel.load_from_db(db, gl.config.download_dir)
|
|
||||||
if url in (c.url for c in channels):
|
|
||||||
msg('error', _('Already added: %s'), urllib.unquote(url))
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
channel = PodcastChannel.load(db, url, create=True, max_episodes=gl.config.max_episodes_per_feed, download_dir=gl.config.download_dir)
|
|
||||||
except:
|
|
||||||
msg( 'error', _('Could not load feed from URL: %s'), urllib.unquote( url))
|
|
||||||
return
|
|
||||||
|
|
||||||
if channel:
|
|
||||||
channels.append(channel)
|
|
||||||
exporter = opml.Exporter(gpodder.subscription_file)
|
|
||||||
exporter.write(channels)
|
|
||||||
db.commit()
|
|
||||||
msg('add', urllib.unquote(url))
|
|
||||||
else:
|
|
||||||
msg('error', _('Could not add podcast.'))
|
|
||||||
|
|
||||||
|
|
||||||
def del_channel( url):
|
|
||||||
url = util.normalize_feed_url( url)
|
|
||||||
|
|
||||||
channels = PodcastChannel.load_from_db(db, gl.config.download_dir)
|
|
||||||
keep_channels = []
|
|
||||||
for channel in channels:
|
|
||||||
if channel.url == url:
|
|
||||||
msg( 'delete', urllib.unquote( channel.url))
|
|
||||||
channel.remove_downloaded()
|
|
||||||
channel.delete()
|
|
||||||
else:
|
|
||||||
keep_channels.append( channel)
|
|
||||||
|
|
||||||
if len(keep_channels) < len(channels):
|
|
||||||
exporter = opml.Exporter(gpodder.subscription_file)
|
|
||||||
exporter.write(keep_channels)
|
|
||||||
db.commit()
|
|
||||||
else:
|
|
||||||
msg('error', _('Could not remove podcast.'))
|
|
||||||
|
|
||||||
|
|
||||||
def update():
|
|
||||||
sys.stdout.write(_('Updating podcast feeds...'))
|
|
||||||
sys.stdout.flush()
|
|
||||||
channels = PodcastChannel.load_from_db(db, gl.config.download_dir)
|
|
||||||
for channel in channels:
|
|
||||||
channel.update(gl.config.max_episodes_per_feed)
|
|
||||||
print _('done.')
|
|
||||||
db.commit()
|
|
||||||
return channels
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
|
||||||
channels = update()
|
|
||||||
new_episodes = 0
|
|
||||||
|
|
||||||
for channel in channels:
|
|
||||||
for episode in channel.get_new_episodes():
|
|
||||||
msg( 'downloading', urllib.unquote( episode.url))
|
|
||||||
task = download.DownloadTask(episode)
|
|
||||||
task.status = download.DownloadTask.QUEUED
|
|
||||||
task.run()
|
|
||||||
if task.status == task.DONE:
|
|
||||||
msg('done', 'Finished.')
|
|
||||||
elif task.status == task.FAILED:
|
|
||||||
msg('failed', 'Download error: %s' % task.error_message)
|
|
||||||
new_episodes += 1
|
|
||||||
|
|
||||||
if new_episodes == 0:
|
|
||||||
print _('No new episodes to download.')
|
|
||||||
elif new_episodes == 1:
|
|
||||||
print _('Downloaded one new episode.')
|
|
||||||
else:
|
|
||||||
print _('Downloaded %d new episodes.') % new_episodes
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
def sync_device():
|
|
||||||
device = sync.open_device(gl.config)
|
|
||||||
if device is None:
|
if device is None:
|
||||||
msg('error', _('No device configured. Please use the GUI.'))
|
print >>sys.stderr, _('No device configured.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
callback_status = lambda s: msg('status', '%s', s)
|
def msg(s):
|
||||||
device.register('status', callback_status)
|
print >>sys.stderr, s
|
||||||
callback_done = lambda: msg('done', _('Synchronization finished.'))
|
|
||||||
device.register('done', callback_done)
|
device.register('status', msg)
|
||||||
callback_progress = lambda i, n: msg('progress', _('Synchronizing: %d of %d') % (i, n))
|
callback_progress = lambda i, n: msg(_('Synchronizing: %d of %d') % (i, n))
|
||||||
device.register('progress', callback_progress)
|
device.register('progress', callback_progress)
|
||||||
|
|
||||||
if not device.open():
|
if device.open():
|
||||||
msg('error', _('Cannot open device.'))
|
channels = [c for c in PodcastChannel.load_from_db(db, \
|
||||||
return False
|
config.download_dir) if c.sync_to_devices]
|
||||||
|
|
||||||
for channel in PodcastChannel.load_from_db(db, gl.config.download_dir):
|
for channel in channels:
|
||||||
if not channel.sync_to_devices:
|
episodes = [e for e in channel.get_downloaded_episodes() \
|
||||||
msg('info', _('Skipping podcast: %s') % channel.title)
|
if e.was_downloaded(and_exists=True)]
|
||||||
continue
|
device.add_tracks(episodes)
|
||||||
|
|
||||||
episodes_to_sync = []
|
|
||||||
for episode in channel.get_all_episodes():
|
|
||||||
if episode.was_downloaded(and_exists=True):
|
|
||||||
episodes_to_sync.append(episode)
|
|
||||||
device.add_tracks(episodes_to_sync)
|
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
if not device.close():
|
device.close()
|
||||||
msg('error', _('Cannot close device.'))
|
print >>sys.stderr, _('Device synchronized successfully.')
|
||||||
return False
|
return True
|
||||||
|
|
||||||
def sync_stats():
|
|
||||||
size = 0
|
|
||||||
device = sync.open_device(gl.config)
|
|
||||||
if device is None:
|
|
||||||
msg('error', _('No device configured. Please use the GUI.'))
|
|
||||||
return False
|
|
||||||
|
|
||||||
for channel in PodcastChannel.load_from_db(db, gl.config.download_dir):
|
|
||||||
if not channel.sync_to_devices:
|
|
||||||
continue
|
|
||||||
for episode in channel.get_all_episodes():
|
|
||||||
if episode.was_downloaded(and_exists=True):
|
|
||||||
episode.calculate_filesize()
|
|
||||||
size += episode.length
|
|
||||||
msg('info', _('Free space on device: %s') % (util.format_filesize(device.get_free_space())))
|
|
||||||
msg('info', _('Size of episodes to sync: %s') % util.format_filesize(size))
|
|
||||||
|
|
||||||
difference = device.get_free_space() - size
|
|
||||||
if difference < 0:
|
|
||||||
msg('error', _('Need to free at least %s more') % util.format_filesize(abs(difference)))
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
msg('info', _('Free space after sync: %s') % util.format_filesize(abs(difference)))
|
print >>sys.stderr, _('Error: Cannot open device!')
|
||||||
|
|
||||||
if not device.close():
|
|
||||||
msg('error', _('Cannot close device.'))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
Loading…
Reference in a new issue