Pipe: Experimental pipe-based IPC interface

This commit is contained in:
Thomas Perl 2012-11-25 00:49:42 +01:00
parent 131417e677
commit 24feaf4eea
2 changed files with 208 additions and 0 deletions

View file

@ -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:

View 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()