--- a
+++ b/src/mediaserver/cdplugins/uprcl/uprclinit.py
@@ -0,0 +1,197 @@
+#!/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 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/>.
+
+import sys
+import os
+import conftree
+import threading
+import subprocess
+import time
+from timeit import default_timer as timer
+
+from rwlock import ReadWriteLock
+import uprclfolders
+import uprcltags
+import uprcluntagged
+import uprclsearch
+import uprclhttp
+import uprclindex
+from uprclcontrol import runbottle
+
+from uprclutils import uplog, findmyip, stringToStrings
+
+# The recoll documents
+g_rcldocs = []
+g_pathprefix = ""
+g_httphp = ""
+g_dblock = ReadWriteLock()
+g_rclconfdir = ""
+
+# Create or update Recoll index, then read and process the data.
+def _update_index():
+ uplog("Creating/updating index in %s for %s" % (g_rclconfdir, g_rcltopdirs))
+
+ # We take the writer lock, making sure that no browse/search
+ # thread are active, then set the busy flag and release the
+ # lock. This allows future browse operations to signal the
+ # condition to the user instead of blocking (if we kept the write
+ # lock).
+ global g_initrunning
+ g_dblock.acquire_write()
+ g_initrunning = True
+ g_dblock.release_write()
+
+ start = timer()
+ uprclindex.runindexer(g_rclconfdir, g_rcltopdirs)
+ # Wait for indexer
+ while not uprclindex.indexerdone():
+ time.sleep(.5)
+ fin = timer()
+ uplog("Indexing took %.2f Seconds" % (fin - start))
+
+ global g_rcldocs
+ g_rcldocs = uprclfolders.inittree(g_rclconfdir, g_httphp, g_pathprefix)
+ uprcltags.recolltosql(g_rcldocs)
+ uprcluntagged.recoll2untagged(g_rcldocs)
+
+ g_dblock.acquire_write()
+ g_initrunning = False
+ g_dblock.release_write()
+
+
+# This runs in a thread because of the possibly long index initialization.
+def _uprcl_init_worker():
+
+ #######
+ # Acquire configuration data.
+
+ global g_pathprefix
+ # pathprefix would typically be something like "/uprcl". It's used
+ # for dispatching URLs to the right plugin for processing. We
+ # strip it whenever we need a real file path
+ if "UPMPD_PATHPREFIX" not in os.environ:
+ raise Exception("No UPMPD_PATHPREFIX in environment")
+ g_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"])
+
+ global g_httphp
+ g_httphp = upconfig.get("uprclhostport")
+ if g_httphp is None:
+ ip = findmyip()
+ g_httphp = ip + ":" + "9090"
+ uplog("uprclhostport not in config, using %s" % g_httphp)
+
+ global g_rclconfdir
+ g_rclconfdir = upconfig.get("uprclconfdir")
+ if g_rclconfdir is None:
+ uplog("uprclconfdir not in config, using /var/cache/upmpdcli/uprcl")
+ g_rclconfdir = "/var/cache/upmpdcli/uprcl"
+
+ global g_rcltopdirs
+ g_rcltopdirs = upconfig.get("uprclmediadirs")
+ if g_rcltopdirs is None:
+ raise Exception("uprclmediadirs not in config")
+
+ pthstr = upconfig.get("uprclpaths")
+ if pthstr is None:
+ uplog("uprclpaths not in config")
+ pthlist = stringToStrings(g_rcltopdirs)
+ pthstr = ""
+ for p in pthlist:
+ pthstr += p + ":" + p + ","
+ pthstr = pthstr.rstrip(",")
+ uplog("Path translation: pthstr: %s" % pthstr)
+ lpth = pthstr.split(',')
+ pathmap = {}
+ for ptt in lpth:
+ l = ptt.split(':')
+ pathmap[l[0]] = l[1]
+
+
+ host,port = g_httphp.split(':')
+ if True:
+ # Running the server as a thread. We get into trouble because
+ # something somewhere writes to stdout a bunch of --------.
+ # Could not find where they come from, happens after a sigpipe
+ # when a client closes a stream. The --- seem to happen before
+ # and after the exception strack trace, e.g:
+ # ----------------------------------------
+ # Exception happened during processing of request from ('192...
+ # Traceback...
+ # [...]
+ # error: [Errno 32] Broken pipe
+ # ----------------------------------------
+ #
+ # **Finally**: found it: the handle_error SocketServer method
+ # was writing to stdout. Overriding it should have fixed the
+ # issue. Otoh the separate process approach works, so we kept
+ # it for now
+ httpthread = threading.Thread(target=uprclhttp.runHttp,
+ kwargs = {'host':host ,
+ 'port':int(port),
+ 'pthstr':pthstr,
+ 'pathprefix':g_pathprefix})
+ httpthread.daemon = True
+ httpthread.start()
+ else:
+ # Running the HTTP server as a separate process
+ cmdpath = os.path.join(os.path.dirname(sys.argv[0]), 'uprclhttp.py')
+ cmd = subprocess.Popen((cmdpath, host, port, pthstr,g_pathprefix),
+ stdin = open('/dev/null'),
+ stdout = sys.stderr,
+ stderr = sys.stderr,
+ close_fds = True)
+
+ _update_index()
+
+ uplog("Uprcl: init done")
+
+def uprcl_init():
+ # This lock and counter are used as a read/write lock
+ global g_initrunning
+ g_initrunning = True
+ initthread = threading.Thread(target=_uprcl_init_worker)
+ initthread.daemon = True
+ initthread.start()
+ # Start the control/config interface
+ ctlthread = threading.Thread(target=runbottle)
+ ctlthread.daemon = True
+ ctlthread.start()
+
+
+def ready():
+ g_dblock.acquire_read()
+ if g_initrunning:
+ return False
+ else:
+ return True
+
+def start_update():
+ if not ready():
+ g_dblock.release_read()
+ return
+ idxthread = threading.Thread(target=_update_index)
+ idxthread.daemon = True
+ # We need to release the reader lock before starting the index
+ # update operation, so that there is a small window for
+ # mischief. I would be concerned if this was a highly concurrent
+ # or critical app, but here, not so much...
+ g_dblock.release_read()
+ idxthread.start()
+