Commit af36ad88 authored by Jean-Francois Dockes's avatar Jean-Francois Dockes
Browse files


parent ce30a15b
from __future__ import print_function
import os
import shlex
import urllib
import sys
from uprclutils import *
from recoll import recoll
from recoll import rclconfig
......@@ -21,8 +22,8 @@ topdirs = [os.path.expanduser(d) for d in
# The dirvec vector has one entry for each directory. Each entry is a
# dictionary, mapping the names inside the directory to a pair (i,j),
# where:
# - i is an index into dirvec if the name is a directory, else 0
# - j is the index of the doc inside the doc array
# - i is an index into dirvec if the name is a directory, else -1
# - j is the index of the doc inside the doc array (or -1 if there is no doc)
# Entry 0 in dirvec is special: it holds the 'topdirs' from the recoll
# configuration. The entries are paths instead of simple names, and
......@@ -36,9 +37,12 @@ def rcl2folders(docs):
for d in topdirs:
topidx += 1
dirvec[0][d] = (topidx, 0)
dirvec[0][d] = (topidx, -1)
# Walk the doc list and update the directory tree according to the
# url (create intermediary directories if needed, create leaf
# entry
for docidx in range(len(docs)):
doc = docs[docidx]
url = doc.getbinurl()
......@@ -48,48 +52,74 @@ def rcl2folders(docs):
decoded = urllib.quote(url).decode('utf-8')
# Determine the root entry (topdirs element). Special because
# path not simple name
fathidx = -1
for rtpath,idx in dirvec[0].iteritems():
if url.startswith(rtpath):
fathidx = idx[0]
if fathidx == -1:
print("No parent in topdirs: %s" % decoded)
uplog("No parent in topdirs: %s" % decoded)
# Compute rest of path
url1 = url[len(rtpath):]
if len(url1) == 0:
# Split path, then walk the vector, possibly creating
# directory entries as needed
path = url1.split('/')[1:]
#print("%s"%path, file=sys.stderr)
#uplog("%s"%path, file=sys.stderr)
for idx in range(len(path)):
elt = path[idx]
if elt in dirvec[fathidx]:
# This path element was already seen
# If this is the last entry in the path, maybe update
# the doc idx (previous entries were created for
# intermediate elements without a Doc).
dirvec[fathidx][elt] = (dirvec[fathidx][elt][0], docidx)
# Update fathidx for next iteration
fathidx = dirvec[fathidx][elt][0]
if idx != len(path) -1 or doc.mtype == 'inode/directory':
# Element has no entry in father directory (hence no
# dirvec entry either).
if idx != len(path) -1:
# This is an intermediate element. Create a
# Doc-less directory
topidx += 1
dirvec[fathidx][elt] = (topidx, docidx)
dirvec[fathidx][elt] = (topidx, -1)
fathidx = topidx
dirvec[fathidx][elt] = (topidx, docidx)
# Last element. If directory, needs a dirvec entry
if doc.mtype == 'inode/directory':
topidx += 1
dirvec[fathidx][elt] = (topidx, docidx)
fathidx = topidx
dirvec[fathidx][elt] = (-1, docidx)
if False:
for ent in dirvec:
print("%s" % ent)
uplog("%s" % ent)
return dirvec
# Fetch all the docs by querying Recoll with [mime:*], which is
# guaranteed to match every doc without overflowing the query size
# (because the number of mime types is limited). Something like
# title:* would overflow.
def fetchalldocs(confdir):
global allthedocs
allthedocs = []
rcldb = recoll.connect(confdir=confdir)
rclq = rcldb.query()
rclq.execute("mime:*", stemming=0)
print("Estimated query results: %d" % (rclq.rowcount))
uplog("Estimated alldocs query results: %d" % (rclq.rowcount))
maxcnt = 0
totcnt = 0
......@@ -100,12 +130,50 @@ def fetchalldocs(confdir):
totcnt += 1
if (maxcnt > 0 and totcnt >= maxcnt) or len(docs) != rclq.arraysize:
print("Retrieved %d docs" % (totcnt,))
print("%s" % dirvec[0])
print("%s" % dirvec[1])
print("%s" % dirvec[2])
print("%s" % dirvec[3])
uplog("Retrieved %d docs" % (totcnt,))
return allthedocs
def inittree(confdir):
global g_alldocs, g_dirvec
g_alldocs = fetchalldocs(confdir)
g_dirvec = rcl2folders(g_alldocs)
g_myprefix = '0$uprcl$folders'
# objid is like folders$index
# flag is meta or children.
def browse(pid, flag):
global g_alldocs, g_dirvec
if not pid.startswith(g_myprefix):
uplog("folders.browse: bad pid %s" % pid)
return []
if len(g_alldocs) == 0:
uplog("folders:browse: no docs")
return []
diridx = pid[len(g_myprefix):]
if not diridx:
diridx = 0
diridx = int(diridx[1:])
if diridx >= len(g_dirvec):
uplog("folders:browse: bad pid %s" % pid)
return []
entries = []
Need to treat diridx 0 special (take simple paths)
for nm,ids in g_dirvec[diridx].iteritems():
id = g_myprefix + '$' + str(ids[0])
entries.append(rcldirentry(id, pid, nm))
return entries
#!/usr/bin/env python
# Copyright (C) 2016 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 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
# 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 <>.
from __future__ import print_function
import sys
import os
import json
import posixpath
import re
import conftree
import cmdtalkplugin
import folders
from uprclutils import *
# Func name to method mapper
dispatcher = cmdtalkplugin.Dispatch()
# Pipe message handler
msgproc = cmdtalkplugin.Processor(dispatcher)
is_logged_in = False
def maybelogin():
global httphp
global pathprefix
global is_logged_in
if is_logged_in:
return True
if "UPMPD_HTTPHOSTPORT" not in os.environ:
raise Exception("No UPMPD_HTTPHOSTPORT in environment")
httphp = os.environ["UPMPD_HTTPHOSTPORT"]
if "UPMPD_PATHPREFIX" not in os.environ:
raise Exception("No UPMPD_PATHPREFIX in environment")
pathprefix = os.environ["UPMPD_PATHPREFIX"]
if "UPMPD_CONFIG" not in os.environ:
raise Exception("No UPMPD_CONFIG in environment")
upconfig = conftree.ConfSimple(os.environ["UPMPD_CONFIG"])
def trackuri(a):
msgproc.log("trackuri: [%s]" % a)
trackid = trackid_from_urlpath(pathprefix, a)
mime, kbs = get_mimeandkbs()
return {'media_url' : media_url, 'mimetype' : mime, 'kbs' : kbs}
def browse(a):
msgproc.log("browse: [%s]" % a)
if 'objid' not in a:
raise Exception("No objid in args")
objid = a['objid']
bflg = a['flag'] if 'flag' in a else 'children'
if re.match('0\$uprcl\$', objid) is None:
raise Exception("bad objid [%s]" % objid)
idpath = objid.replace('0$uprcl$', '', 1)
msgproc.log("browse: idpath: %s" % idpath)
entries = []
if bflg == 'meta':
m = re.match('.*\$(.+)$', idpath)
if m:
trackid =
# get doc from trackid or whatever
doc = {}
entries += trackentries(httphp, pathprefix,
objid, [])
if not idpath:
# Root dir
entries.append(rcldirentry('0$uprcl$' + 'folders', '0$uprcl$',
if idpath.startswith("folders"):
entries = folders.browse(objid, bflg)
#msgproc.log("%s" % entries)
encoded = json.dumps(entries)
return {"entries" : encoded}
def search(a):
msgproc.log("search: [%s]" % a)
objid = a['objid']
field = a['field'] if 'field' in a else None
value = a['value']
objkind = a['objkind'] if 'objkind' in a else None
if re.match('0\$uprcl\$', objid) is None:
raise Exception("bad objid [%s]" % objid)
searchresults =
if objkind and objkind not in ['artist', 'album', 'playlist', 'track']:
msgproc.log('Unknown objkind \'%s\'' % objkind)
objkind = None
if objkind is None or objkind == 'artist':
urls_from_id(artist_view, searchresults.artists), end=False)
if objkind is None or objkind == 'album':
urls_from_id(album_view, searchresults.albums), end=False)
if objkind is None or objkind == 'playlist':
urls_from_id(playlist_view, searchresults.playlists), end=False)
if objkind is None or objkind == 'track':
#msgproc.log("%s" % xbmcplugin.entries)
encoded = json.dumps(xbmcplugin.entries)
return {"entries" : encoded}
msgproc.log("Uprcl running")
from __future__ import print_function
import sys
def rcldocs2entries(httphp, pathprefix, objid, docs):
Transform a list of Doc objects into the format expected by the parent
httphp: the hostport part of the generated track urls
pathprefix: is provided by our parent process (it's used to
what plugin an url belongs too when needed for
translating the internal into the real url (for plugins
based on external-services)
objid (str): objid for the browsed object (the parent container)
docs is the array of Doc objects to be translated
A list of dicts, each representing an UPnP item, with the
keys as expected in the plgwithslave.cxx resultToEntries() function.
The permanent URIs, are of the following form, based on the
configured host:port and pathprefix arguments and track Id:
global default_mime, default_samplerate
entries = []
for doc in docs:
li = {}
li['pid'] = objid
li['id'] = objid + '$' + "%s" %
li['tt'] = doc.title
# URL: we need transformation rules from the file:// recoll url to an http
# one appropriate for our media server
li['uri'] = 'http://%s' % httphp + \
'track?version=1&trackId=%s' %
li['tp'] = 'it'
if doc.album:
li['upnp:album'] = doc.album
# !! Albumart will have to come from somewhere else !
### #if doc.albumarturi:
### #li['upnp:albumArtURI'] = track.album.image
# Date format ?
li['releasedate'] =
li['upnp:originalTrackNumber'] = str(doc.tracknumber)
li['upnp:artist'] = doc.artist
li['upnp:genre'] = doc.genre
li['dc:title'] = doc.title
li['upnp:class'] = track.upnpclass
li['res:mime'] = doc.mtype
### li['discnumber'] = str(track.disc_num)
### Need to extract the audio params from mutagen output !
#li['duration'] = track.duration
#li['res:samplefreq'] = default_samplerate
return entries
def rcldirentry(id, pid, title, arturi=None, artist=None, upnpclass=None):
""" Create container entry in format expected by parent """
ret = {'id':id, 'pid':pid, 'tt':title, 'tp':'ct', 'searchable':'1'}
if arturi:
ret['upnp:albumArtURI'] = arturi
if artist:
ret['upnp:artist'] = artist
if upnpclass:
ret['upnp:class'] = upnpclass
ret['upnp:class'] = 'object.container'
return ret
def uplog(s):
print(("%s: %s" % ('uprcl', s)).encode('utf-8'), file=sys.stderr)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment