diff -r 000000000000 -r de9c2efb9d02 Helpviewer/OpenSearch/OpenSearchManager.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Helpviewer/OpenSearch/OpenSearchManager.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,507 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a manager for open search engines. +""" + +import os + +from PyQt4.QtCore import * +from PyQt4.QtGui import QMessageBox +from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply + +from OpenSearchDefaultEngines import OpenSearchDefaultEngines +from OpenSearchEngine import OpenSearchEngine +from OpenSearchReader import OpenSearchReader +from OpenSearchWriter import OpenSearchWriter + +from Utilities.AutoSaver import AutoSaver +import Utilities +import Preferences + +class OpenSearchManager(QObject): + """ + Class implementing a manager for open search engines. + + @signal changed() emitted to indicate a change + @signal currentEngineChanged() emitted to indicate a change of + the current search engine + """ + def __init__(self, parent = None): + """ + Constructor + + @param parent reference to the parent object (QObject) + """ + QObject.__init__(self, parent) + + self.__engines = {} + self.__keywords = {} + self.__current = "" + self.__loading = False + self.__saveTimer = AutoSaver(self, self.save) + + self.connect(self, SIGNAL("changed()"), self.__saveTimer.changeOccurred) + + self.load() + + def close(self): + """ + Public method to close the open search engines manager. + """ + self.__saveTimer.saveIfNeccessary() + + def currentEngineName(self): + """ + Public method to get the name of the current search engine. + + @return name of the current search engine (string) + """ + return self.__current + + def setCurrentEngineName(self, name): + """ + Public method to set the current engine by name. + + @param name name of the new current engine (string) + """ + if name not in self.__engines: + return + + self.__current = name + self.emit(SIGNAL("currentEngineChanged()")) + self.emit(SIGNAL("changed()")) + + def currentEngine(self): + """ + Public method to get a reference to the current engine. + + @return reference to the current engine (OpenSearchEngine) + """ + if not self.__current or self.__current not in self.__engines: + return None + + return self.__engines[self.__current] + + def setCurrentEngine(self, engine): + """ + Public method to set the current engine. + + @param engine reference to the new current engine (OpenSearchEngine) + """ + if engine is None: + return + + for engineName in self.__engines: + if self.__engines[engineName] == engine: + self.setCurrentEngineName(engineName) + break + + def engine(self, name): + """ + Public method to get a reference to the named engine. + + @param name name of the engine (string) + @return reference to the engine (OpenSearchEngine) + """ + if name not in self.__engines: + return None + + return self.__engines[name] + + def engineExists(self, name): + """ + Public method to check, if an engine exists. + + @param name name of the engine (string) + """ + return name in self.__engines + + def allEnginesNames(self): + """ + Public method to get a list of all engine names. + + @return sorted list of all engine names (list of strings) + """ + return sorted(self.__engines.keys()) + + def enginesCount(self): + """ + Public method to get the number of available engines. + + @return number of engines (integer) + """ + return len(self.__engines) + + def addEngine(self, engine): + """ + Public method to add a new search engine. + + @param engine URL of the engine definition file (QUrl) or + name of a file containing the engine definition (string) + or reference to an engine object (OpenSearchEngine) + @return flag indicating success (boolean) + """ + if isinstance(engine, QUrl): + return self.__addEngineByUrl(engine) + elif isinstance(engine, OpenSearchEngine): + return self.__addEngineByEngine(engine) + else: + return self.__addEngineByFile(engine) + + def __addEngineByUrl(self, url): + """ + Private method to add a new search engine given it's URL. + + @param url URL of the engine definition file (QUrl) + @return flag indicating success (boolean) + """ + if not url.isValid(): + return + + from Helpviewer.HelpWindow import HelpWindow + + reply = HelpWindow.networkAccessManager().get(QNetworkRequest(url)) + self.connect(reply, SIGNAL("finished()"), self.__engineFromUrlAvailable) + reply.setParent(self) + + return True + + def __addEngineByFile(self, filename): + """ + Private method to add a new search engine given a filename. + + @param filename name of a file containing the engine definition (string) + @return flag indicating success (boolean) + """ + file_ = QFile(filename) + if not file_.open(QIODevice.ReadOnly): + return False + + reader = OpenSearchReader() + engine = reader.read(file_) + + if not self.__addEngineByEngine(engine): + return False + + return True + + def __addEngineByEngine(self, engine): + """ + Private method to add a new search engine given a reference to an engine. + + @param engine reference to an engine object (OpenSearchEngine) + @return flag indicating success (boolean) + """ + if engine is None: + return False + + if not engine.isValid(): + return False + + if engine.name() in self.__engines: + return False + + self.__engines[engine.name()] = engine + + self.emit(SIGNAL("changed()")) + + return True + + def removeEngine(self, name): + """ + Public method to remove an engine. + + @param name name of the engine (string) + """ + if len(self.__engines) <= 1: + return + + if name not in self.__engines: + return + + engine = self.__engines[name] + for keyword in [k for k in self.__keywords if self.__keywords[k] == engine]: + del self.__keywords[k] + del self.__engines[name] + + file_ = QDir(self.enginesDirectory()).filePath(self.generateEngineFileName(name)) + QFile.remove(file_) + + if name == self.__current: + self.setCurrentEngineName(self.__engines.keys()[0]) + + self.emit(SIGNAL("changed()")) + + def generateEngineFileName(self, engineName): + """ + Public method to generate a valid engine file name. + + @param engineName name of the engine (string) + @return valid engine file name (string) + """ + fileName = "" + + # strip special characters + for c in engineName: + if c.isspace(): + fileName += '_' + continue + + if c.isalnum(): + fileName += c + + fileName += ".xml" + + return fileName + + def saveDirectory(self, dirName): + """ + Public method to save the search engine definitions to files. + + @param dirName name of the directory to write the files to (string) + """ + dir = QDir() + if not dir.mkpath(dirName): + return + dir.setPath(dirName) + + writer = OpenSearchWriter() + + for engine in self.__engines.values(): + name = self.generateEngineFileName(engine.name()) + fileName = dir.filePath(name) + + file = QFile(fileName) + if not file.open(QIODevice.WriteOnly): + continue + + writer.write(file, engine) + + def save(self): + """ + Public method to save the search engines configuration. + """ + if self.__loading: + return + + self.saveDirectory(self.enginesDirectory()) + + Preferences.setHelp("WebSearchEngine", self.__current) + keywords = [] + for k in self.__keywords: + if self.__keywords[k]: + keywords.append((k, self.__keywords[k].name())) + Preferences.setHelp("WebSearchKeywords", keywords) + + def loadDirectory(self, dirName): + """ + Public method to load the search engine definitions from files. + + @param dirName name of the directory to load the files from (string) + @return flag indicating success (boolean) + """ + if not QFile.exists(dirName): + return False + + success = False + + dir = QDir(dirName) + for name in dir.entryList(["*.xml"]): + fileName = dir.filePath(name) + if self.__addEngineByFile(fileName): + success = True + + return success + + def load(self): + """ + Public method to load the search engines configuration. + """ + self.__loading = True + self.__current = Preferences.getHelp("WebSearchEngine") + keywords = Preferences.getHelp("WebSearchKeywords") + + if not self.loadDirectory(self.enginesDirectory()): + self.restoreDefaults() + + for keyword, engineName in keywords: + self.__keywords[keyword] = self.engine(engineName) + + if self.__current not in self.__engines and \ + len(self.__engines) > 0: + self.__current = self.__engines.keys()[0] + + self.__loading = False + self.emit(SIGNAL("currentEngineChanged()")) + + def restoreDefaults(self): + """ + Public method to restore the default search engines. + """ + reader = OpenSearchReader() + for engine in OpenSearchDefaultEngines: + engineDescription = QByteArray(OpenSearchDefaultEngines[engine]) + buffer_ = QBuffer(engineDescription) + if not buffer_.open(QIODevice.ReadOnly): + continue + + engine = reader.read(buffer_) + + self.__addEngineByEngine(engine) + + def enginesDirectory(self): + """ + Public method to determine the directory containing the search engine + descriptions. + + @return directory name (string) + """ + return os.path.join(Utilities.getConfigDir(), "browser", "searchengines") + + def __confirmAddition(self, engine): + """ + Private method to confirm the addition of a new search engine. + + @param engine reference to the engine to be added (OpenSearchEngine) + @return flag indicating the engine shall be added (boolean) + """ + if engine is None or not engine.isValid(): + return False + + host = QUrl(engine.searchUrlTemplate()).host() + + res = QMessageBox.question(None, + "", + self.trUtf8("""Do you want to add the following engine to your list of""" + """ search engines?<br/><br/>Name: {0}<br/>Searches on: {1}""")\ + .format(engine.name(), host), + QMessageBox.StandardButtons(\ + QMessageBox.No | \ + QMessageBox.Yes), + QMessageBox.No) + return res == QMessageBox.Yes + + def __engineFromUrlAvailable(self): + """ + Private slot to add a search engine from the net. + """ + reply = self.sender() + if reply is None: + return + + if reply.error() != QNetworkReply.NoError: + reply.close() + reply.deleteLater() + return + + reader = OpenSearchReader() + engine = reader.read(reply) + + reply.close() + reply.deleteLater() + + if not engine.isValid(): + return + + if self.engineExists(engine.name()): + return + + if not self.__confirmAddition(engine): + return + + if not self.__addEngineByEngine(engine): + return + + def convertKeywordSearchToUrl(self, keywordSearch): + """ + Public method to get the search URL for a keyword search. + + @param keywordSearch search string for keyword search (string) + @return search URL (QUrl) + """ + try: + keyword, term = unicode(keywordSearch).split(" ", 1) + except ValueError: + return QUrl() + + if not term: + return QUrl() + + engine = self.engineForKeyword(keyword) + if engine: + return engine.searchUrl(term) + + return QUrl() + + def engineForKeyword(self, keyword): + """ + Public method to get the engine for a keyword. + + @param keyword keyword to get engine for (string) + @return reference to the search engine object (OpenSearchEngine) + """ + if keyword and keyword in self.__keywords: + return self.__keywords[keyword] + + return None + + def setEngineForKeyword(self, keyword, engine): + """ + Public method to set the engine for a keyword. + + @param keyword keyword to get engine for (string) + @param engine reference to the search engine object (OpenSearchEngine) + or None to remove the keyword + """ + if not keyword: + return + + if engine is None: + try: + del self.__keywords[keyword] + except KeyError: + pass + else: + self.__keywords[keyword] = engine + + self.emit(SIGNAL("changed()")) + + def keywordsForEngine(self, engine): + """ + Public method to get the keywords for a given engine. + + @param engine reference to the search engine object (OpenSearchEngine) + @return list of keywords (list of strings) + """ + return [k for k in self.__keywords if self.__keywords[k] == engine] + + def setKeywordsForEngine(self, engine, keywords): + """ + Public method to set the keywords for an engine. + + @param engine reference to the search engine object (OpenSearchEngine) + @param keywords list of keywords (list of strings) + """ + if engine is None: + return + + for keyword in self.keywordsForEngine(engine): + del self.__keywords[keyword] + + for keyword in keywords: + if not keyword: + continue + + self.__keywords[keyword] = engine + + self.emit(SIGNAL("changed()")) + + def enginesChanged(self): + """ + Public slot to tell the search engine manager, that something has changed. + """ + self.emit(SIGNAL("changed()"))