Helpviewer/OpenSearch/OpenSearchManager.py

changeset 0
de9c2efb9d02
child 6
52e8c820d0dd
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a manager for open search engines.
8 """
9
10 import os
11
12 from PyQt4.QtCore import *
13 from PyQt4.QtGui import QMessageBox
14 from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply
15
16 from OpenSearchDefaultEngines import OpenSearchDefaultEngines
17 from OpenSearchEngine import OpenSearchEngine
18 from OpenSearchReader import OpenSearchReader
19 from OpenSearchWriter import OpenSearchWriter
20
21 from Utilities.AutoSaver import AutoSaver
22 import Utilities
23 import Preferences
24
25 class OpenSearchManager(QObject):
26 """
27 Class implementing a manager for open search engines.
28
29 @signal changed() emitted to indicate a change
30 @signal currentEngineChanged() emitted to indicate a change of
31 the current search engine
32 """
33 def __init__(self, parent = None):
34 """
35 Constructor
36
37 @param parent reference to the parent object (QObject)
38 """
39 QObject.__init__(self, parent)
40
41 self.__engines = {}
42 self.__keywords = {}
43 self.__current = ""
44 self.__loading = False
45 self.__saveTimer = AutoSaver(self, self.save)
46
47 self.connect(self, SIGNAL("changed()"), self.__saveTimer.changeOccurred)
48
49 self.load()
50
51 def close(self):
52 """
53 Public method to close the open search engines manager.
54 """
55 self.__saveTimer.saveIfNeccessary()
56
57 def currentEngineName(self):
58 """
59 Public method to get the name of the current search engine.
60
61 @return name of the current search engine (string)
62 """
63 return self.__current
64
65 def setCurrentEngineName(self, name):
66 """
67 Public method to set the current engine by name.
68
69 @param name name of the new current engine (string)
70 """
71 if name not in self.__engines:
72 return
73
74 self.__current = name
75 self.emit(SIGNAL("currentEngineChanged()"))
76 self.emit(SIGNAL("changed()"))
77
78 def currentEngine(self):
79 """
80 Public method to get a reference to the current engine.
81
82 @return reference to the current engine (OpenSearchEngine)
83 """
84 if not self.__current or self.__current not in self.__engines:
85 return None
86
87 return self.__engines[self.__current]
88
89 def setCurrentEngine(self, engine):
90 """
91 Public method to set the current engine.
92
93 @param engine reference to the new current engine (OpenSearchEngine)
94 """
95 if engine is None:
96 return
97
98 for engineName in self.__engines:
99 if self.__engines[engineName] == engine:
100 self.setCurrentEngineName(engineName)
101 break
102
103 def engine(self, name):
104 """
105 Public method to get a reference to the named engine.
106
107 @param name name of the engine (string)
108 @return reference to the engine (OpenSearchEngine)
109 """
110 if name not in self.__engines:
111 return None
112
113 return self.__engines[name]
114
115 def engineExists(self, name):
116 """
117 Public method to check, if an engine exists.
118
119 @param name name of the engine (string)
120 """
121 return name in self.__engines
122
123 def allEnginesNames(self):
124 """
125 Public method to get a list of all engine names.
126
127 @return sorted list of all engine names (list of strings)
128 """
129 return sorted(self.__engines.keys())
130
131 def enginesCount(self):
132 """
133 Public method to get the number of available engines.
134
135 @return number of engines (integer)
136 """
137 return len(self.__engines)
138
139 def addEngine(self, engine):
140 """
141 Public method to add a new search engine.
142
143 @param engine URL of the engine definition file (QUrl) or
144 name of a file containing the engine definition (string)
145 or reference to an engine object (OpenSearchEngine)
146 @return flag indicating success (boolean)
147 """
148 if isinstance(engine, QUrl):
149 return self.__addEngineByUrl(engine)
150 elif isinstance(engine, OpenSearchEngine):
151 return self.__addEngineByEngine(engine)
152 else:
153 return self.__addEngineByFile(engine)
154
155 def __addEngineByUrl(self, url):
156 """
157 Private method to add a new search engine given it's URL.
158
159 @param url URL of the engine definition file (QUrl)
160 @return flag indicating success (boolean)
161 """
162 if not url.isValid():
163 return
164
165 from Helpviewer.HelpWindow import HelpWindow
166
167 reply = HelpWindow.networkAccessManager().get(QNetworkRequest(url))
168 self.connect(reply, SIGNAL("finished()"), self.__engineFromUrlAvailable)
169 reply.setParent(self)
170
171 return True
172
173 def __addEngineByFile(self, filename):
174 """
175 Private method to add a new search engine given a filename.
176
177 @param filename name of a file containing the engine definition (string)
178 @return flag indicating success (boolean)
179 """
180 file_ = QFile(filename)
181 if not file_.open(QIODevice.ReadOnly):
182 return False
183
184 reader = OpenSearchReader()
185 engine = reader.read(file_)
186
187 if not self.__addEngineByEngine(engine):
188 return False
189
190 return True
191
192 def __addEngineByEngine(self, engine):
193 """
194 Private method to add a new search engine given a reference to an engine.
195
196 @param engine reference to an engine object (OpenSearchEngine)
197 @return flag indicating success (boolean)
198 """
199 if engine is None:
200 return False
201
202 if not engine.isValid():
203 return False
204
205 if engine.name() in self.__engines:
206 return False
207
208 self.__engines[engine.name()] = engine
209
210 self.emit(SIGNAL("changed()"))
211
212 return True
213
214 def removeEngine(self, name):
215 """
216 Public method to remove an engine.
217
218 @param name name of the engine (string)
219 """
220 if len(self.__engines) <= 1:
221 return
222
223 if name not in self.__engines:
224 return
225
226 engine = self.__engines[name]
227 for keyword in [k for k in self.__keywords if self.__keywords[k] == engine]:
228 del self.__keywords[k]
229 del self.__engines[name]
230
231 file_ = QDir(self.enginesDirectory()).filePath(self.generateEngineFileName(name))
232 QFile.remove(file_)
233
234 if name == self.__current:
235 self.setCurrentEngineName(self.__engines.keys()[0])
236
237 self.emit(SIGNAL("changed()"))
238
239 def generateEngineFileName(self, engineName):
240 """
241 Public method to generate a valid engine file name.
242
243 @param engineName name of the engine (string)
244 @return valid engine file name (string)
245 """
246 fileName = ""
247
248 # strip special characters
249 for c in engineName:
250 if c.isspace():
251 fileName += '_'
252 continue
253
254 if c.isalnum():
255 fileName += c
256
257 fileName += ".xml"
258
259 return fileName
260
261 def saveDirectory(self, dirName):
262 """
263 Public method to save the search engine definitions to files.
264
265 @param dirName name of the directory to write the files to (string)
266 """
267 dir = QDir()
268 if not dir.mkpath(dirName):
269 return
270 dir.setPath(dirName)
271
272 writer = OpenSearchWriter()
273
274 for engine in self.__engines.values():
275 name = self.generateEngineFileName(engine.name())
276 fileName = dir.filePath(name)
277
278 file = QFile(fileName)
279 if not file.open(QIODevice.WriteOnly):
280 continue
281
282 writer.write(file, engine)
283
284 def save(self):
285 """
286 Public method to save the search engines configuration.
287 """
288 if self.__loading:
289 return
290
291 self.saveDirectory(self.enginesDirectory())
292
293 Preferences.setHelp("WebSearchEngine", self.__current)
294 keywords = []
295 for k in self.__keywords:
296 if self.__keywords[k]:
297 keywords.append((k, self.__keywords[k].name()))
298 Preferences.setHelp("WebSearchKeywords", keywords)
299
300 def loadDirectory(self, dirName):
301 """
302 Public method to load the search engine definitions from files.
303
304 @param dirName name of the directory to load the files from (string)
305 @return flag indicating success (boolean)
306 """
307 if not QFile.exists(dirName):
308 return False
309
310 success = False
311
312 dir = QDir(dirName)
313 for name in dir.entryList(["*.xml"]):
314 fileName = dir.filePath(name)
315 if self.__addEngineByFile(fileName):
316 success = True
317
318 return success
319
320 def load(self):
321 """
322 Public method to load the search engines configuration.
323 """
324 self.__loading = True
325 self.__current = Preferences.getHelp("WebSearchEngine")
326 keywords = Preferences.getHelp("WebSearchKeywords")
327
328 if not self.loadDirectory(self.enginesDirectory()):
329 self.restoreDefaults()
330
331 for keyword, engineName in keywords:
332 self.__keywords[keyword] = self.engine(engineName)
333
334 if self.__current not in self.__engines and \
335 len(self.__engines) > 0:
336 self.__current = self.__engines.keys()[0]
337
338 self.__loading = False
339 self.emit(SIGNAL("currentEngineChanged()"))
340
341 def restoreDefaults(self):
342 """
343 Public method to restore the default search engines.
344 """
345 reader = OpenSearchReader()
346 for engine in OpenSearchDefaultEngines:
347 engineDescription = QByteArray(OpenSearchDefaultEngines[engine])
348 buffer_ = QBuffer(engineDescription)
349 if not buffer_.open(QIODevice.ReadOnly):
350 continue
351
352 engine = reader.read(buffer_)
353
354 self.__addEngineByEngine(engine)
355
356 def enginesDirectory(self):
357 """
358 Public method to determine the directory containing the search engine
359 descriptions.
360
361 @return directory name (string)
362 """
363 return os.path.join(Utilities.getConfigDir(), "browser", "searchengines")
364
365 def __confirmAddition(self, engine):
366 """
367 Private method to confirm the addition of a new search engine.
368
369 @param engine reference to the engine to be added (OpenSearchEngine)
370 @return flag indicating the engine shall be added (boolean)
371 """
372 if engine is None or not engine.isValid():
373 return False
374
375 host = QUrl(engine.searchUrlTemplate()).host()
376
377 res = QMessageBox.question(None,
378 "",
379 self.trUtf8("""Do you want to add the following engine to your list of"""
380 """ search engines?<br/><br/>Name: {0}<br/>Searches on: {1}""")\
381 .format(engine.name(), host),
382 QMessageBox.StandardButtons(\
383 QMessageBox.No | \
384 QMessageBox.Yes),
385 QMessageBox.No)
386 return res == QMessageBox.Yes
387
388 def __engineFromUrlAvailable(self):
389 """
390 Private slot to add a search engine from the net.
391 """
392 reply = self.sender()
393 if reply is None:
394 return
395
396 if reply.error() != QNetworkReply.NoError:
397 reply.close()
398 reply.deleteLater()
399 return
400
401 reader = OpenSearchReader()
402 engine = reader.read(reply)
403
404 reply.close()
405 reply.deleteLater()
406
407 if not engine.isValid():
408 return
409
410 if self.engineExists(engine.name()):
411 return
412
413 if not self.__confirmAddition(engine):
414 return
415
416 if not self.__addEngineByEngine(engine):
417 return
418
419 def convertKeywordSearchToUrl(self, keywordSearch):
420 """
421 Public method to get the search URL for a keyword search.
422
423 @param keywordSearch search string for keyword search (string)
424 @return search URL (QUrl)
425 """
426 try:
427 keyword, term = unicode(keywordSearch).split(" ", 1)
428 except ValueError:
429 return QUrl()
430
431 if not term:
432 return QUrl()
433
434 engine = self.engineForKeyword(keyword)
435 if engine:
436 return engine.searchUrl(term)
437
438 return QUrl()
439
440 def engineForKeyword(self, keyword):
441 """
442 Public method to get the engine for a keyword.
443
444 @param keyword keyword to get engine for (string)
445 @return reference to the search engine object (OpenSearchEngine)
446 """
447 if keyword and keyword in self.__keywords:
448 return self.__keywords[keyword]
449
450 return None
451
452 def setEngineForKeyword(self, keyword, engine):
453 """
454 Public method to set the engine for a keyword.
455
456 @param keyword keyword to get engine for (string)
457 @param engine reference to the search engine object (OpenSearchEngine)
458 or None to remove the keyword
459 """
460 if not keyword:
461 return
462
463 if engine is None:
464 try:
465 del self.__keywords[keyword]
466 except KeyError:
467 pass
468 else:
469 self.__keywords[keyword] = engine
470
471 self.emit(SIGNAL("changed()"))
472
473 def keywordsForEngine(self, engine):
474 """
475 Public method to get the keywords for a given engine.
476
477 @param engine reference to the search engine object (OpenSearchEngine)
478 @return list of keywords (list of strings)
479 """
480 return [k for k in self.__keywords if self.__keywords[k] == engine]
481
482 def setKeywordsForEngine(self, engine, keywords):
483 """
484 Public method to set the keywords for an engine.
485
486 @param engine reference to the search engine object (OpenSearchEngine)
487 @param keywords list of keywords (list of strings)
488 """
489 if engine is None:
490 return
491
492 for keyword in self.keywordsForEngine(engine):
493 del self.__keywords[keyword]
494
495 for keyword in keywords:
496 if not keyword:
497 continue
498
499 self.__keywords[keyword] = engine
500
501 self.emit(SIGNAL("changed()"))
502
503 def enginesChanged(self):
504 """
505 Public slot to tell the search engine manager, that something has changed.
506 """
507 self.emit(SIGNAL("changed()"))

eric ide

mercurial