# # This file is part of Documancer (http://documancer.sf.net) # # Copyright (C) 2004-2005 Vaclav Slavik # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: _external.py,v 1.6 2005/02/06 10:14:10 vaclavslavik Exp $ # # Fulltext indexer running in another process, communication via XML-RPC import sys, time, os, socket, re from gettext import gettext as _ import xmlrpclib from indexers import FulltextIndexer import utils # ------------------------------------------------------------------------- # ExtProcess helper class # ------------------------------------------------------------------------- if sys.platform == 'win32': import win32api, win32con, win32process class ExtProcess: def __init__(self, cmd): cmd2 = ' '.join(['"%s"' % x for x in cmd]) procArgs = (None, # appName cmd2, # commandLine None, # processAttributes None, # threadAttributes 1, # bInheritHandles win32con.CREATE_NO_WINDOW, # dwCreationFlags None, # newEnvironment None, # currentDirectory win32process.STARTUPINFO()) # startupinfo procHandles = win32process.CreateProcess(*procArgs) self.handle, self.thread, self.pid, self.tid = procHandles def isAlive(self): try: st = win32process.GetExitCodeProcess(self.handle) return st == win32con.STILL_ACTIVE except win32api.error: return False def kill(self): win32api.TerminateProcess(self.handle, 0) else: # Unix/Mac class ExtProcess: def __init__(self, cmd): self.pid = os.spawnv(os.P_NOWAIT, cmd[0], cmd) def kill(self): os.waitpid(self.pid, 0) def isAlive(self): try: pid, exitstatus = os.waitpid(self.pid, os.WNOHANG) return pid == 0 except os.error: return False # ------------------------------------------------------------------------- # ExternalIndexer # ------------------------------------------------------------------------- class LaunchingError(Exception): pass """Error caused by failure to run a subprocess""" class ExternalIndexer(FulltextIndexer): def __init__(self, url): utils.uiCallback.showBusyIndicator() utils.uiCallback.setBusyText(_('Starting fulltext indexer')) cmd = self.getStartCommand() self.proc = ExtProcess(cmd) self.proxy = xmlrpclib.ServerProxy(url) # wait until the server comes alive: while True: # is the subprocess still alive or did it die during startup? if not self.proc.isAlive(): raise LaunchingError(_('Indexer subprocess "%s" died.') % cmd) # does its XML-RPC server run or not yet? try: st = self.proxy.getStatus() if st == 'OK': break else: raise Exception(st) except socket.error: pass # server didn't start yet, wait # don't waste too much CPU time when busy waiting: time.sleep(0.1) utils.uiCallback.hideBusyIndicator() def __del__(self): if self.proc.isAlive(): self.proxy.kill() self.proc.kill() # implementation of relevant parts of the API: def getNameAndVersion(self): return self.proxy.getNameAndVersion() def search(self, directory, query): results=[] for r in self.proxy.search(directory, query): results.append( FulltextIndexer.Result(r['title'], r['url'], r['score'])) return results def startIndexing(self, directory): self.proxy.startIndexing(directory) def indexDocument(self, url, data): # xmlrpclib doesn't like characters with code < 0x20, so let's replace # them with whitespace: regex = '|'.join([chr(i) for i in xrange(0,32)]) data2 = {} for d in data: data2[d] = re.sub(regex, ' ', data[d]) self.proxy.indexDocument(url, data2) def stopIndexing(self): self.proxy.stopIndexing() # functions needed to implement RPC: def getStartCommand(self): """Called to obtain the command that should be used to start the backend process. Returns array whose first value is program to execute and the rest are its arguments.""" raise NotImplementedError