--- a/src/mediaserver/cdplugins/uprcl/uprclhttp.py
+++ b/src/mediaserver/cdplugins/uprcl/uprclhttp.py
@@ -1,253 +1,112 @@
-#!/usr/bin/env python
+# Copyright (C) 2017 J.F.Dockes
+# This program 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 2 of the License, or
+# (at your option) any later version.
#
-# Copyright (C) 2017 J.F.Dockes
+# This program 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.
#
-# HTTP Range code:
-# Portions Copyright (C) 2009,2010 Xyne
-# Portions Copyright (C) 2011 Sean Goller
-# https://github.com/smgoller/rangehttpserver/blob/master/RangeHTTPServer.py
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
-# This program 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.
-#
-# This program 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/>.
+
from __future__ import print_function
-import SocketServer
-import BaseHTTPServer
import os
-import posixpath
-import urllib
-import urlparse
-import shutil
-import mimetypes
-import sys
+import time
+import bottle
-import mutagen
+from uprclutils import uplog, embedded_open
+import uprclinit
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
+@bottle.route('/')
+@bottle.post('/')
+@bottle.view('main')
+def main():
+ sub = bottle.request.forms.get('sub')
+ #uplog("Main: sub value is %s" % sub)
+ if uprclinit.updaterunning():
+ status = 'Updating'
+ else:
+ status = 'Ready'
-from uprclutils import uplog,printable,embedded_open
+ if sub == 'Update Index':
+ uprclinit.start_update()
+
+ if sub:
+ headers = dict()
+ headers["Location"] = '/'
+ return bottle.HTTPResponse(status=302, **headers)
+ else:
+ return {'title':status, 'status':status,
+ 'friendlyname':uprclinit.g_friendlyname}
+
+@bottle.route('/static/<filepath:path>')
+def static(filepath):
+ #uplog("control: static: filepath %s datadir %s" % (filepath, datadir))
+ return bottle.static_file(filepath, root=os.path.join(datadir, 'static'))
-__version__ = "0.1"
+# Object for streaming data from a given subtree (topdirs entry more
+# or less). This is needed just because as far as I can see, a
+# callback can't know the route it was called for, so we record it
+# when creating the object.
+class Streamer(object):
+ def __init__(self, root):
+ self.root = root
-class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def __call__(self, filepath):
+ embedded = True if 'embed' in bottle.request.query else False
+ if embedded:
+ # Embedded image urls have had a .jpg or .png
+ # appended. Remove it to restore the track path name.
+ i = filepath.rfind('.')
+ filepath = filepath[:i]
+ apath = os.path.join(self.root,filepath)
+ ctype, size, f = embedded_open(apath)
+ fs = os.stat(apath)
+ lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(fs.st_mtime))
+ bottle.response.set_header("Last-Modified", lm)
+ bottle.response.set_header("Content-type", ctype)
+ bottle.response.set_header("Content-Length", size)
+ return f
+ uplog("Uprcl: streaming: %s" % os.path.join(self.root,filepath))
+ return bottle.static_file(filepath, root=self.root)
- """Simple HTTP request handler with GET and HEAD commands.
+
- The MIME type for files is determined by calling the .guess_type() method.
+# Bottle handle both the streaming and control requests.
+def runbottle(host='0.0.0.0', port=9278, pthstr='', pathprefix=''):
+ global datadir
+ uplog("runbottle: host %s port %d pthstr %s pathprefix %s" %
+ (host, port, pthstr, pathprefix))
+ datadir = os.path.dirname(__file__)
+ datadir = os.path.join(datadir, 'bottle')
+ bottle.TEMPLATE_PATH = (os.path.join(datadir, 'views'),)
- The GET and HEAD requests are identical except that the HEAD
- request omits the actual contents of the file.
-
- """
-
- server_version = "RangeHTTP/" + __version__
-
- def do_GET(self):
- """Serve a GET request."""
- f, start_range, end_range = self.send_head()
- if f:
- #uplog("do_GET: Got (%d,%d)" % (start_range,end_range))
- f.seek(start_range, 0)
- chunk = 0x1000
- total = 0
- while chunk > 0:
- if start_range + chunk > end_range:
- chunk = end_range - start_range
- try:
- self.wfile.write(f.read(chunk))
- except:
- break
- total += chunk
- start_range += chunk
- f.close()
-
- def do_HEAD(self):
- """Serve a HEAD request."""
- f, start_range, end_range = self.send_head()
- if f:
- f.close()
-
- def send_head(self):
- """Common code for GET and HEAD commands.
-
- This sends the response code and MIME headers.
-
- Return value is either a file object (which has to be copied
- to the outputfile by the caller unless the command was HEAD,
- and must be closed by the caller under all circumstances), or
- None, in which case the caller has nothing further to do.
-
- """
-
- path,embedded = self.translate_path(self.path)
- #uplog("HTTP: translated: embedded %s path: %s" %
- # (embedded, printable(path)))
-
- if not path or not os.path.exists(path):
- self.send_error(404)
- return (None, 0, 0)
-
- if not os.path.isfile(path):
- self.send_error(405)
- return (None, 0, 0)
-
- f = None
- try:
- if embedded:
- ctype, size, f = embedded_open(path)
- fs = os.stat(path)
- #uplog("embedded, got ctype %s size %s" %(ctype, size))
- else:
- ctype = self.guess_type(path)
- f = open(path, 'rb')
- fs = os.fstat(f.fileno())
- size = int(fs[6])
- except Exception as err:
- self.send_error(404, "File not found")
- return (None, 0, 0)
-
- if "Range" in self.headers:
- self.send_response(206)
- else:
- self.send_response(200)
-
- self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
- self.send_header("Content-type", ctype)
- start_range = 0
- end_range = size
- self.send_header("Accept-Ranges", "bytes")
- if "Range" in self.headers:
- s, e = self.headers['range'][6:].split('-', 1)
- sl = len(s)
- el = len(e)
- if sl > 0:
- start_range = int(s)
- if el > 0:
- end_range = int(e) + 1
- elif el > 0:
- ei = int(e)
- if ei < size:
- start_range = size - ei
- self.send_header("Content-Range",
- 'bytes ' + str(start_range) + '-' +
- str(end_range - 1) + '/' + str(size))
- self.send_header("Content-Length", end_range - start_range)
- self.end_headers()
- #uplog("Sending Bytes %d to %d" % (start_range, end_range))
- return (f, start_range, end_range)
-
-
- def translate_path(self, opath):
- path = urllib.unquote(opath)
- # pathprefix is something like /uprcl
- path = path.replace(self.uprclpathprefix, '', 1)
-
- q = urlparse.urlparse(path)
- path = q.path
- embedded = False
- pq = urlparse.parse_qs(q.query)
- if 'embed' in pq:
- embedded = True
-
- for fsp,htp in self.uprclpathmap.iteritems():
- if path.startswith(fsp):
- path = path.replace(fsp, htp, 1)
- if embedded:
- # Embedded image urls have had a .jpg or .png
- # appended. Remove it to restore the track path
- # name.
- i = path.rfind('.')
- path = path[:i]
- return path, embedded
-
- # Security feature here: never allow access to anything not in
- # the path map
- uplog("HTTP: translate_path: %s not found in path map" % opath)
- return None, None
-
-
- def guess_type(self, path):
- """Guess the type of a file.
-
- Argument is a PATH (a filename).
-
- Return value is a string of the form type/subtype,
- usable for a MIME Content-type header.
-
- The default implementation looks the file's extension
- up in the table self.extensions_map, using application/octet-stream
- as a default; however it would be permissible (if
- slow) to look inside the data to make a better guess.
-
- """
-
- base, ext = posixpath.splitext(path)
- if ext in self.extensions_map:
- return self.extensions_map[ext]
- ext = ext.lower()
- if ext in self.extensions_map:
- return self.extensions_map[ext]
- else:
- return self.extensions_map['']
-
- if not mimetypes.inited:
- mimetypes.init() # try to read system mime.types
- extensions_map = mimetypes.types_map.copy()
- extensions_map.update({
- '': 'application/octet-stream', # Default
- '.mp4': 'video/mp4',
- '.ogg': 'video/ogg',
- })
-
-
-class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
- BaseHTTPServer.HTTPServer):
- # Override handle_error as the default version writes to stdout !
- def handle_error(self, request, client_address):
- # Actually, we generally don't care about errors...
- return
-
- uplog('-'*40)
- uplog('Exception happened during processing of request from %s' %
- str(client_address))
- import traceback
- traceback.print_exc() # XXX But this goes to stderr! (jf: yep :)
- uplog('-'*40)
-
-
-def runHttp(host='', port=8080, pthstr='', pathprefix=''):
-
+ # All the file urls must be like /some/prefix/path where
+ # /some/prefix must be in the path translation map (which I'm not
+ # sure what the use of is). By default the map is an identical
+ # translation of all topdirs entries. We create one route for each
+ # prefix. As I don't know how a bottle method can retrieve the
+ # route it was called from, we create a callable for each prefix.
+ # Each route is built on the translation input, and the processor
+ # uses the translated path as root
lpth = pthstr.split(',')
- pathmap = {}
for ptt in lpth:
l = ptt.split(':')
- pathmap[l[0]] = l[1]
- # Set pathmap as request handler class variable
- RangeHTTPRequestHandler.uprclpathmap = pathmap
- RangeHTTPRequestHandler.uprclpathprefix = pathprefix
-
- server = ThreadingSimpleServer((host, port), RangeHTTPRequestHandler)
- while 1:
- server.handle_request()
+ rt = l[0]
+ if rt[-1] != '/':
+ rt += '/'
+ rt += '<filepath:path>'
+ uplog("runbottle: adding route for: %s"%rt)
+ # We build the streamer with the translated
+ streamer = Streamer(l[1])
+ bottle.route(rt, 'GET', streamer)
-if __name__ == '__main__':
- if len(sys.argv) != 5:
- print("Usage: uprclhttp.py <host> <port> <pthmap> <pthprefix>",
- file=sys.stderr)
- sys.exit(1)
- runHttp(host=sys.argv[1], port = int(sys.argv[2]), pthstr=sys.argv[3],
- pathprefix=sys.argv[4])
+ bottle.run(server='paste', host=host, port=port)