WebBrowser/OpenSearch/OpenSearchEngine.py

branch
QtWebEngine
changeset 4741
f9e1adc69076
parent 4631
5c1a96925da4
child 4801
71bdc520f640
equal deleted inserted replaced
4735:84e78ee0f361 4741:f9e1adc69076
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the open search engine.
8 """
9
10 from __future__ import unicode_literals
11
12 import re
13 import json
14
15 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QLocale, QUrl, QByteArray, \
16 QBuffer, QIODevice, QObject, qVersion
17 from PyQt5.QtGui import QImage
18 from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, \
19 QNetworkReply
20
21 from UI.Info import Program
22
23 import Preferences
24 import Utilities
25
26
27 class OpenSearchEngine(QObject):
28 """
29 Class implementing the open search engine.
30
31 @signal imageChanged() emitted after the icon has been changed
32 @signal suggestions(list of strings) emitted after the suggestions have
33 been received
34 """
35 imageChanged = pyqtSignal()
36 suggestions = pyqtSignal(list)
37
38 def __init__(self, parent=None):
39 """
40 Constructor
41
42 @param parent reference to the parent object (QObject)
43 """
44 super(OpenSearchEngine, self).__init__(parent)
45
46 self.__suggestionsReply = None
47 self.__networkAccessManager = None
48 self._name = ""
49 self._description = ""
50 self._searchUrlTemplate = ""
51 self._suggestionsUrlTemplate = ""
52 self._searchParameters = [] # list of two tuples
53 self._suggestionsParameters = [] # list of two tuples
54 self._imageUrl = ""
55 self.__image = QImage()
56 self.__iconMoved = False
57 self.__searchMethod = "get"
58 self.__suggestionsMethod = "get"
59 self.__requestMethods = {
60 "get": QNetworkAccessManager.GetOperation,
61 "post": QNetworkAccessManager.PostOperation,
62 }
63
64 self.__replies = []
65
66 @classmethod
67 def parseTemplate(cls, searchTerm, searchTemplate):
68 """
69 Class method to parse a search template.
70
71 @param searchTerm term to search for (string)
72 @param searchTemplate template to be parsed (string)
73 @return parsed template (string)
74 """
75 locale = QLocale(Preferences.getWebBrowser("SearchLanguage"))
76 language = locale.name().replace("_", "-")
77 country = locale.name().split("_")[0].lower()
78
79 result = searchTemplate
80 result = result.replace("{count}", "20")
81 result = result.replace("{startIndex}", "0")
82 result = result.replace("{startPage}", "0")
83 result = result.replace("{language}", language)
84 result = result.replace("{country}", country)
85 result = result.replace("{inputEncoding}", "UTF-8")
86 result = result.replace("{outputEncoding}", "UTF-8")
87 result = result.replace(
88 "{searchTerms}",
89 bytes(QUrl.toPercentEncoding(searchTerm)).decode())
90 result = re.sub(r"""\{([^\}]*:|)source\??\}""", Program, result)
91
92 return result
93
94 @pyqtSlot(result=str)
95 def name(self):
96 """
97 Public method to get the name of the engine.
98
99 @return name of the engine (string)
100 """
101 return self._name
102
103 def setName(self, name):
104 """
105 Public method to set the engine name.
106
107 @param name name of the engine (string)
108 """
109 self._name = name
110
111 def description(self):
112 """
113 Public method to get the description of the engine.
114
115 @return description of the engine (string)
116 """
117 return self._description
118
119 def setDescription(self, description):
120 """
121 Public method to set the engine description.
122
123 @param description description of the engine (string)
124 """
125 self._description = description
126
127 def searchUrlTemplate(self):
128 """
129 Public method to get the search URL template of the engine.
130
131 @return search URL template of the engine (string)
132 """
133 return self._searchUrlTemplate
134
135 def setSearchUrlTemplate(self, searchUrlTemplate):
136 """
137 Public method to set the engine search URL template.
138
139 The URL template is processed according to the specification:
140 <a
141 href="http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax">
142 http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax</a>
143
144 A list of template parameters currently supported and what they are
145 replaced with:
146 <table>
147 <tr><td><b>Parameter</b></td><td><b>Value</b></td></tr>
148 <tr><td>{count}</td><td>20</td></tr>
149 <tr><td>{startIndex}</td><td>0</td></tr>
150 <tr><td>{startPage}</td><td>0</td></tr>
151 <tr><td>{language}</td>
152 <td>the default language code (RFC 3066)</td></tr>
153 <tr><td>{country}</td>
154 <td>the default country code (first part of language)</td></tr>
155 <tr><td>{inputEncoding}</td><td>UTF-8</td></tr>
156 <tr><td>{outputEncoding}</td><td>UTF-8</td></tr>
157 <tr><td>{searchTerms}</td><td>the string supplied by the user</td></tr>
158 <tr><td>{*:source}</td>
159 <td>application name, QCoreApplication::applicationName()</td></tr>
160 </table>
161
162 @param searchUrlTemplate search URL template of the engine (string)
163 """
164 self._searchUrlTemplate = searchUrlTemplate
165
166 def searchUrl(self, searchTerm):
167 """
168 Public method to get a URL ready for searching.
169
170 @param searchTerm term to search for (string)
171 @return URL (QUrl)
172 """
173 if not self._searchUrlTemplate:
174 return QUrl()
175
176 ret = QUrl.fromEncoded(
177 self.parseTemplate(searchTerm, self._searchUrlTemplate)
178 .encode("utf-8"))
179
180 if self.__searchMethod != "post":
181 if qVersion() >= "5.0.0":
182 from PyQt5.QtCore import QUrlQuery
183 urlQuery = QUrlQuery(ret)
184 for parameter in self._searchParameters:
185 urlQuery.addQueryItem(
186 parameter[0],
187 self.parseTemplate(searchTerm, parameter[1]))
188 ret.setQuery(urlQuery)
189 else:
190 for parameter in self._searchParameters:
191 ret.addQueryItem(
192 parameter[0],
193 self.parseTemplate(searchTerm, parameter[1]))
194
195 return ret
196
197 def providesSuggestions(self):
198 """
199 Public method to check, if the engine provides suggestions.
200
201 @return flag indicating suggestions are provided (boolean)
202 """
203 return self._suggestionsUrlTemplate != ""
204
205 def suggestionsUrlTemplate(self):
206 """
207 Public method to get the search URL template of the engine.
208
209 @return search URL template of the engine (string)
210 """
211 return self._suggestionsUrlTemplate
212
213 def setSuggestionsUrlTemplate(self, suggestionsUrlTemplate):
214 """
215 Public method to set the engine suggestions URL template.
216
217 @param suggestionsUrlTemplate suggestions URL template of the
218 engine (string)
219 """
220 self._suggestionsUrlTemplate = suggestionsUrlTemplate
221
222 def suggestionsUrl(self, searchTerm):
223 """
224 Public method to get a URL ready for suggestions.
225
226 @param searchTerm term to search for (string)
227 @return URL (QUrl)
228 """
229 if not self._suggestionsUrlTemplate:
230 return QUrl()
231
232 ret = QUrl.fromEncoded(QByteArray(self.parseTemplate(
233 searchTerm, self._suggestionsUrlTemplate).encode("utf-8")))
234
235 if self.__searchMethod != "post":
236 if qVersion() >= "5.0.0":
237 from PyQt5.QtCore import QUrlQuery
238 urlQuery = QUrlQuery(ret)
239 for parameter in self._suggestionsParameters:
240 urlQuery.addQueryItem(
241 parameter[0],
242 self.parseTemplate(searchTerm, parameter[1]))
243 ret.setQuery(urlQuery)
244 else:
245 for parameter in self._suggestionsParameters:
246 ret.addQueryItem(
247 parameter[0],
248 self.parseTemplate(searchTerm, parameter[1]))
249
250 return ret
251
252 def searchParameters(self):
253 """
254 Public method to get the search parameters of the engine.
255
256 @return search parameters of the engine (list of two tuples)
257 """
258 return self._searchParameters[:]
259
260 def setSearchParameters(self, searchParameters):
261 """
262 Public method to set the engine search parameters.
263
264 @param searchParameters search parameters of the engine
265 (list of two tuples)
266 """
267 self._searchParameters = searchParameters[:]
268
269 def suggestionsParameters(self):
270 """
271 Public method to get the suggestions parameters of the engine.
272
273 @return suggestions parameters of the engine (list of two tuples)
274 """
275 return self._suggestionsParameters[:]
276
277 def setSuggestionsParameters(self, suggestionsParameters):
278 """
279 Public method to set the engine suggestions parameters.
280
281 @param suggestionsParameters suggestions parameters of the
282 engine (list of two tuples)
283 """
284 self._suggestionsParameters = suggestionsParameters[:]
285
286 def searchMethod(self):
287 """
288 Public method to get the HTTP request method used to perform search
289 requests.
290
291 @return HTTP request method (string)
292 """
293 return self.__searchMethod
294
295 def setSearchMethod(self, method):
296 """
297 Public method to set the HTTP request method used to perform search
298 requests.
299
300 @param method HTTP request method (string)
301 """
302 requestMethod = method.lower()
303 if requestMethod not in self.__requestMethods:
304 return
305
306 self.__searchMethod = requestMethod
307
308 def suggestionsMethod(self):
309 """
310 Public method to get the HTTP request method used to perform
311 suggestions requests.
312
313 @return HTTP request method (string)
314 """
315 return self.__suggestionsMethod
316
317 def setSuggestionsMethod(self, method):
318 """
319 Public method to set the HTTP request method used to perform
320 suggestions requests.
321
322 @param method HTTP request method (string)
323 """
324 requestMethod = method.lower()
325 if requestMethod not in self.__requestMethods:
326 return
327
328 self.__suggestionsMethod = requestMethod
329
330 def imageUrl(self):
331 """
332 Public method to get the image URL of the engine.
333
334 @return image URL of the engine (string)
335 """
336 return self._imageUrl
337
338 def setImageUrl(self, imageUrl):
339 """
340 Public method to set the engine image URL.
341
342 @param imageUrl image URL of the engine (string)
343 """
344 self._imageUrl = imageUrl
345
346 def setImageUrlAndLoad(self, imageUrl):
347 """
348 Public method to set the engine image URL.
349
350 @param imageUrl image URL of the engine (string)
351 """
352 self.setImageUrl(imageUrl)
353 self.__iconMoved = False
354 self.loadImage()
355
356 def loadImage(self):
357 """
358 Public method to load the image of the engine.
359 """
360 if self.__networkAccessManager is None or not self._imageUrl:
361 return
362
363 reply = self.__networkAccessManager.get(
364 QNetworkRequest(QUrl.fromEncoded(self._imageUrl.encode("utf-8"))))
365 reply.finished.connect(self.__imageObtained)
366 self.__replies.append(reply)
367
368 def __imageObtained(self):
369 """
370 Private slot to receive the image of the engine.
371 """
372 reply = self.sender()
373 if reply is None:
374 return
375
376 response = reply.readAll()
377
378 reply.close()
379 if reply in self.__replies:
380 self.__replies.remove(reply)
381 reply.deleteLater()
382
383 if response.isEmpty():
384 return
385
386 if response.startsWith(b"<html>") or response.startsWith(b"HTML"):
387 self.__iconMoved = True
388 self.__image = QImage()
389 else:
390 self.__image.loadFromData(response)
391 self.imageChanged.emit()
392
393 def image(self):
394 """
395 Public method to get the image of the engine.
396
397 @return image of the engine (QImage)
398 """
399 if not self.__iconMoved and self.__image.isNull():
400 self.loadImage()
401
402 return self.__image
403
404 def setImage(self, image):
405 """
406 Public method to set the image of the engine.
407
408 @param image image to be set (QImage)
409 """
410 if not self._imageUrl:
411 imageBuffer = QBuffer()
412 imageBuffer.open(QIODevice.ReadWrite)
413 if image.save(imageBuffer, "PNG"):
414 self._imageUrl = "data:image/png;base64,{0}".format(
415 bytes(imageBuffer.buffer().toBase64()).decode())
416
417 self.__image = QImage(image)
418 self.imageChanged.emit()
419
420 def isValid(self):
421 """
422 Public method to check, if the engine is valid.
423
424 @return flag indicating validity (boolean)
425 """
426 return self._name and self._searchUrlTemplate
427
428 def __eq__(self, other):
429 """
430 Special method implementing the == operator.
431
432 @param other reference to an open search engine (OpenSearchEngine)
433 @return flag indicating equality (boolean)
434 """
435 if not isinstance(other, OpenSearchEngine):
436 return NotImplemented
437
438 return self._name == other._name and \
439 self._description == other._description and \
440 self._imageUrl == other._imageUrl and \
441 self._searchUrlTemplate == other._searchUrlTemplate and \
442 self._suggestionsUrlTemplate == other._suggestionsUrlTemplate and \
443 self._searchParameters == other._searchParameters and \
444 self._suggestionsParameters == other._suggestionsParameters
445
446 def __lt__(self, other):
447 """
448 Special method implementing the < operator.
449
450 @param other reference to an open search engine (OpenSearchEngine)
451 @return flag indicating less than (boolean)
452 """
453 if not isinstance(other, OpenSearchEngine):
454 return NotImplemented
455
456 return self._name < other._name
457
458 def requestSuggestions(self, searchTerm):
459 """
460 Public method to request suggestions.
461
462 @param searchTerm term to get suggestions for (string)
463 """
464 if not searchTerm or not self.providesSuggestions():
465 return
466
467 if self.__networkAccessManager is None:
468 return
469
470 if self.__suggestionsReply is not None:
471 self.__suggestionsReply.finished.disconnect(
472 self.__suggestionsObtained)
473 self.__suggestionsReply.abort()
474 self.__suggestionsReply.deleteLater()
475 self.__suggestionsReply = None
476
477 if self.__suggestionsMethod not in self.__requestMethods:
478 # ignore
479 return
480
481 if self.__suggestionsMethod == "get":
482 self.__suggestionsReply = self.networkAccessManager().get(
483 QNetworkRequest(self.suggestionsUrl(searchTerm)))
484 else:
485 parameters = []
486 for parameter in self._suggestionsParameters:
487 parameters.append(parameter[0] + "=" + parameter[1])
488 data = "&".join(parameters)
489 self.__suggestionsReply = self.networkAccessManager().post(
490 QNetworkRequest(self.suggestionsUrl(searchTerm)), data)
491 self.__suggestionsReply.finished.connect(
492 self.__suggestionsObtained)
493
494 def __suggestionsObtained(self):
495 """
496 Private slot to receive the suggestions.
497 """
498 if self.__suggestionsReply.error() == QNetworkReply.NoError:
499 buffer = bytes(self.__suggestionsReply.readAll())
500 response = Utilities.decodeBytes(buffer)
501 response = response.strip()
502
503 self.__suggestionsReply.close()
504 self.__suggestionsReply.deleteLater()
505 self.__suggestionsReply = None
506
507 if len(response) == 0:
508 return
509
510 try:
511 result = json.loads(response)
512 except ValueError:
513 return
514
515 try:
516 suggestions = result[1]
517 except IndexError:
518 return
519
520 self.suggestions.emit(suggestions)
521
522 def networkAccessManager(self):
523 """
524 Public method to get a reference to the network access manager object.
525
526 @return reference to the network access manager object
527 (QNetworkAccessManager)
528 """
529 return self.__networkAccessManager
530
531 def setNetworkAccessManager(self, networkAccessManager):
532 """
533 Public method to set the reference to the network access manager.
534
535 @param networkAccessManager reference to the network access manager
536 object (QNetworkAccessManager)
537 """
538 self.__networkAccessManager = networkAccessManager

eric ide

mercurial