Pipe: Experimental pipe-based IPC interface
This commit is contained in:
parent
131417e677
commit
24feaf4eea
2 changed files with 208 additions and 0 deletions
6
bin/gpo
6
bin/gpo
|
@ -61,6 +61,7 @@
|
|||
rewrite OLDURL NEWURL Change the feed URL of [OLDURL] to [NEWURL]
|
||||
webui [public] Start gPodder's Web UI server
|
||||
(public = listen on all network interfaces)
|
||||
pipe Start gPodder in pipe-based IPC server mode
|
||||
|
||||
"""
|
||||
|
||||
|
@ -113,6 +114,7 @@ import gpodder
|
|||
_ = gpodder.gettext
|
||||
N_ = gpodder.ngettext
|
||||
|
||||
gpodder.images_folder = os.path.join(prefix, 'share', 'gpodder', 'images')
|
||||
gpodder.prefix = prefix
|
||||
|
||||
# This is the command-line UI variant
|
||||
|
@ -584,6 +586,10 @@ class gPodderCli(object):
|
|||
else:
|
||||
webui.main(core=self.core)
|
||||
|
||||
def pipe(self):
|
||||
from gpodder import pipe
|
||||
pipe.main(core=self.core)
|
||||
|
||||
def search(self, *terms):
|
||||
query = ' '.join(terms)
|
||||
if not query:
|
||||
|
|
202
src/gpodder/pipe/__init__.py
Normal file
202
src/gpodder/pipe/__init__.py
Normal file
|
@ -0,0 +1,202 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# gPodder - A media aggregator and podcast client
|
||||
# Copyright (c) 2005-2012 Thomas Perl and the gPodder Team
|
||||
#
|
||||
# gPodder is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# gPodder is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# gpodder.pipe - Socket/pipe-based backend for New UIs
|
||||
# Thomas Perl <thp@gpodder.org>; 2012-11-24
|
||||
|
||||
|
||||
import gpodder
|
||||
|
||||
from gpodder import core
|
||||
from gpodder import model
|
||||
from gpodder import util
|
||||
from gpodder import coverart
|
||||
|
||||
try:
|
||||
# For Python < 2.6, we use the "simplejson" add-on module
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
# Python 2.6 already ships with a nice "json" module
|
||||
import json
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import Queue
|
||||
import time
|
||||
import fcntl
|
||||
|
||||
def cmd(*signature):
|
||||
def wrapper(f):
|
||||
setattr(f, '_pipecmd', True)
|
||||
return f
|
||||
return wrapper
|
||||
|
||||
def to_json(o):
|
||||
keys = list(('id',) + o.__slots__)
|
||||
values = list(getattr(o, key) for key in keys)
|
||||
yield keys
|
||||
yield values
|
||||
|
||||
class PipeError(BaseException): pass
|
||||
|
||||
class Pipe:
|
||||
def __init__(self, core, reader, writer):
|
||||
self.core = core
|
||||
|
||||
self.model = self.core.model
|
||||
self.cover_download = coverart.CoverDownloader()
|
||||
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
self.events_in = Queue.Queue()
|
||||
self.events_out = Queue.Queue()
|
||||
|
||||
def event_writer_proc(self):
|
||||
while True:
|
||||
item = self.events_out.get(True)
|
||||
self.writer.write(item.encode('utf-8') + '\n')
|
||||
self.writer.flush()
|
||||
|
||||
def event_reader_proc(self):
|
||||
while True:
|
||||
args = self.events_in.get(True)
|
||||
cmd = args.pop(0)
|
||||
func = getattr(self, cmd, None)
|
||||
if func is not None:
|
||||
if getattr(func, '_pipecmd', False):
|
||||
try:
|
||||
result = func(*args)
|
||||
if result:
|
||||
self.event_out(result)
|
||||
except PipeError, e:
|
||||
self.event_out('! %s' % e)
|
||||
except Exception, e:
|
||||
print >>sys.stderr, 'FAIL:', e
|
||||
self.event_out('! %s' % e)
|
||||
raise
|
||||
continue
|
||||
|
||||
self.event_out('? %s' % line)
|
||||
|
||||
def event_in(self, data):
|
||||
self.events_in.put(data)
|
||||
|
||||
def event_out(self, data):
|
||||
self.events_out.put(data)
|
||||
|
||||
def find_episode(self, id):
|
||||
for podcast in self.model.get_podcasts():
|
||||
for episode in podcast.get_all_episodes():
|
||||
if episode.id == int(id):
|
||||
return episode
|
||||
raise PipeError('episode not found')
|
||||
|
||||
def find_podcast(self, id):
|
||||
for podcast in self.model.get_podcasts():
|
||||
if podcast.id == int(id):
|
||||
return podcast
|
||||
raise PipeError('podcast not found')
|
||||
|
||||
def summarize_podcasts(self, podcasts):
|
||||
yield ('id', 'title', 'downloads', 'cover')
|
||||
for podcast in podcasts:
|
||||
total, deleted, new, downloaded, unplayed = podcast.get_statistics()
|
||||
cover_filename = self.cover_download.get_cover(podcast.cover_file,
|
||||
podcast.cover_url, podcast.url, podcast.title,
|
||||
podcast.auth_username, podcast.auth_password, True)
|
||||
yield (podcast.id, podcast.title, downloaded, cover_filename)
|
||||
|
||||
def summarize_episodes(self, episodes):
|
||||
yield ('id', 'title', 'state')
|
||||
for episode in episodes:
|
||||
yield (episode.id, episode.title, episode.state)
|
||||
|
||||
def serialize(self, data):
|
||||
return json.dumps(list(data), ensure_ascii=False)
|
||||
|
||||
@cmd()
|
||||
def podcasts(self):
|
||||
return 'podcasts ' + self.serialize(self.summarize_podcasts(self.model.get_podcasts()))
|
||||
|
||||
@cmd(int)
|
||||
def episodes(self, id):
|
||||
podcast = self.find_podcast(id)
|
||||
return 'episodes ' + id + ' ' + self.serialize(self.summarize_episodes(podcast.get_all_episodes()))
|
||||
|
||||
@cmd(int)
|
||||
def episode(self, id):
|
||||
episode = self.find_episode(id)
|
||||
return 'episode ' + id + ' ' + self.serialize(to_json(episode))
|
||||
|
||||
@cmd(int)
|
||||
def podcast(self, id):
|
||||
podcast = self.find_podcast(id)
|
||||
return 'podcast ' + id + ' ' + self.serialize(to_json(podcast))
|
||||
|
||||
@cmd()
|
||||
def update_all(self):
|
||||
@util.run_in_background
|
||||
def update_proc():
|
||||
for podcast in self.model.get_podcasts():
|
||||
self.event_out('updating %d' % podcast.id)
|
||||
podcast.update()
|
||||
self.event_out('updated %d' % podcast.id)
|
||||
self.event_out('updated_all')
|
||||
|
||||
@cmd(int)
|
||||
def update(self, id):
|
||||
@util.run_in_background
|
||||
def update_proc():
|
||||
podcast = self.find_podcast(id)
|
||||
self.event_out('updating %d' % podcast.id)
|
||||
podcast.update()
|
||||
self.event_out('updated %d' % podcast.id)
|
||||
|
||||
def run(self):
|
||||
def post_random_events():
|
||||
while True:
|
||||
time.sleep(1)
|
||||
self.event_out('hello')
|
||||
time.sleep(2)
|
||||
self.event_out('hello 10')
|
||||
random_event_thread = threading.Thread(target=post_random_events)
|
||||
random_event_thread.setDaemon(True)
|
||||
#random_event_thread.start()
|
||||
|
||||
reader_thread = threading.Thread(target=self.event_reader_proc)
|
||||
writer_thread = threading.Thread(target=self.event_writer_proc)
|
||||
reader_thread.setDaemon(True)
|
||||
writer_thread.setDaemon(True)
|
||||
reader_thread.start()
|
||||
writer_thread.start()
|
||||
|
||||
while True:
|
||||
line = self.reader.readline()
|
||||
if not line:
|
||||
break
|
||||
line = line.rstrip()
|
||||
if line:
|
||||
self.event_in(line.split())
|
||||
|
||||
def main(core):
|
||||
pipe = Pipe(core, sys.stdin, sys.stdout)
|
||||
pipe.run()
|
||||
|
Loading…
Reference in a new issue