|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a scheme access handler for QtHelp. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import mimetypes |
|
13 import os |
|
14 |
|
15 from PyQt5.QtCore import pyqtSignal, QByteArray, QIODevice, QBuffer, QMutex, \ |
|
16 QMutexLocker |
|
17 from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, \ |
|
18 QWebEngineUrlRequestJob |
|
19 |
|
20 QtDocPath = "qthelp://org.qt-project." |
|
21 |
|
22 ExtensionMap = { |
|
23 ".bmp": "image/bmp", |
|
24 ".css": "text/css", |
|
25 ".gif": "image/gif", |
|
26 ".html": "text/html", |
|
27 ".htm": "text/html", |
|
28 ".ico": "image/x-icon", |
|
29 ".jpeg": "image/jpeg", |
|
30 ".jpg": "image/jpeg", |
|
31 ".js": "application/x-javascript", |
|
32 ".mng": "video/x-mng", |
|
33 ".pbm": "image/x-portable-bitmap", |
|
34 ".pgm": "image/x-portable-graymap", |
|
35 ".pdf": "application/pdf", |
|
36 ".png": "image/png", |
|
37 ".ppm": "image/x-portable-pixmap", |
|
38 ".rss": "application/rss+xml", |
|
39 ".svg": "image/svg+xml", |
|
40 ".svgz": "image/svg+xml", |
|
41 ".text": "text/plain", |
|
42 ".tif": "image/tiff", |
|
43 ".tiff": "image/tiff", |
|
44 ".txt": "text/plain", |
|
45 ".xbm": "image/x-xbitmap", |
|
46 ".xml": "text/xml", |
|
47 ".xpm": "image/x-xpm", |
|
48 ".xsl": "text/xsl", |
|
49 ".xhtml": "application/xhtml+xml", |
|
50 ".wml": "text/vnd.wap.wml", |
|
51 ".wmlc": "application/vnd.wap.wmlc", |
|
52 } |
|
53 |
|
54 |
|
55 class QtHelpSchemeHandler(QWebEngineUrlSchemeHandler): |
|
56 """ |
|
57 Class implementing a scheme handler for the qthelp: scheme. |
|
58 """ |
|
59 def __init__(self, engine, parent=None): |
|
60 """ |
|
61 Constructor |
|
62 |
|
63 @param engine reference to the help engine |
|
64 @type QHelpEngine |
|
65 @param parent reference to the parent object |
|
66 @type QObject |
|
67 """ |
|
68 super(QtHelpSchemeHandler, self).__init__(parent) |
|
69 |
|
70 self.__engine = engine |
|
71 |
|
72 self.__replies = [] |
|
73 |
|
74 def requestStarted(self, job): |
|
75 """ |
|
76 Public method handling the URL request. |
|
77 |
|
78 @param job URL request job |
|
79 @type QWebEngineUrlRequestJob |
|
80 """ |
|
81 if job.requestUrl().scheme() == "qthelp": |
|
82 reply = QtHelpSchemeReply(job, self.__engine) |
|
83 reply.closed.connect(lambda: self.__replyClosed(reply)) |
|
84 self.__replies.append(reply) |
|
85 job.reply(reply.mimeType(), reply) |
|
86 else: |
|
87 job.fail(QWebEngineUrlRequestJob.UrlInvalid) |
|
88 |
|
89 def __replyClosed(self, reply): |
|
90 """ |
|
91 Private slot handling the closed signal of a reply. |
|
92 |
|
93 @param reply reference to the network reply |
|
94 @type QtHelpSchemeReply |
|
95 """ |
|
96 if reply in self.__replies: |
|
97 self.__replies.remove(reply) |
|
98 |
|
99 |
|
100 class QtHelpSchemeReply(QIODevice): |
|
101 """ |
|
102 Class implementing a reply for a requested qthelp: page. |
|
103 |
|
104 @signal closed emitted to signal that the web engine has read |
|
105 the data |
|
106 """ |
|
107 closed = pyqtSignal() |
|
108 |
|
109 def __init__(self, job, engine, parent=None): |
|
110 """ |
|
111 Constructor |
|
112 |
|
113 @param job reference to the URL request |
|
114 @type QWebEngineUrlRequestJob |
|
115 @param engine reference to the help engine |
|
116 @type QHelpEngine |
|
117 @param parent reference to the parent object |
|
118 @type QObject |
|
119 """ |
|
120 super(QtHelpSchemeReply, self).__init__(parent) |
|
121 |
|
122 self.__job = job |
|
123 self.__engine = engine |
|
124 self.__mutex = QMutex() |
|
125 |
|
126 self.__buffer = QBuffer() |
|
127 |
|
128 # determine mimetype |
|
129 url = self.__job.requestUrl() |
|
130 strUrl = url.toString() |
|
131 |
|
132 # For some reason the url to load maybe wrong (passed from web engine) |
|
133 # though the css file and the references inside should work that way. |
|
134 # One possible problem might be that the css is loaded at the same |
|
135 # level as the html, thus a path inside the css like |
|
136 # (../images/foo.png) might cd out of the virtual folder |
|
137 if not self.__engine.findFile(url).isValid(): |
|
138 if strUrl.startswith(QtDocPath): |
|
139 newUrl = self.__job.requestUrl() |
|
140 if not newUrl.path().startswith("/qdoc/"): |
|
141 newUrl.setPath("/qdoc" + newUrl.path()) |
|
142 url = newUrl |
|
143 strUrl = url.toString() |
|
144 |
|
145 self.__mimeType = mimetypes.guess_type(strUrl)[0] |
|
146 if self.__mimeType is None: |
|
147 # do our own (limited) guessing |
|
148 self.__mimeType = self.__mimeFromUrl(url) |
|
149 |
|
150 self.__loadQtHelpPage(url) |
|
151 |
|
152 def __loadQtHelpPage(self, url): |
|
153 """ |
|
154 Private method to load the requested QtHelp page. |
|
155 |
|
156 @param url URL of the requested page |
|
157 @type QUrl |
|
158 """ |
|
159 if self.__engine.findFile(url).isValid(): |
|
160 data = self.__engine.fileData(url) |
|
161 else: |
|
162 data = QByteArray(self.tr( |
|
163 """<html>""" |
|
164 """<head><title>Error 404...</title></head>""" |
|
165 """<body><div align="center"><br><br>""" |
|
166 """<h1>The page could not be found</h1><br>""" |
|
167 """<h3>'{0}'</h3></div></body>""" |
|
168 """</html>""").format(url.toString()) |
|
169 .encode("utf-8")) |
|
170 |
|
171 lock = QMutexLocker(self.__mutex) |
|
172 self.__buffer.setData(data) |
|
173 self.__buffer.open(QIODevice.ReadOnly) |
|
174 self.open(QIODevice.ReadOnly) |
|
175 lock.unlock() |
|
176 |
|
177 self.readyRead.emit() |
|
178 |
|
179 def bytesAvailable(self): |
|
180 """ |
|
181 Public method to get the number of available bytes. |
|
182 |
|
183 @return number of available bytes |
|
184 @rtype int |
|
185 """ |
|
186 lock = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ |
|
187 return self.__buffer.bytesAvailable() |
|
188 |
|
189 def readData(self, maxlen): |
|
190 """ |
|
191 Public method to retrieve data from the reply object. |
|
192 |
|
193 @param maxlen maximum number of bytes to read (integer) |
|
194 @return string containing the data (bytes) |
|
195 """ |
|
196 lock = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ |
|
197 return self.__buffer.read(maxlen) |
|
198 |
|
199 def close(self): |
|
200 """ |
|
201 Public method used to cloase the reply. |
|
202 """ |
|
203 super(QtHelpSchemeReply, self).close() |
|
204 self.closed.emit() |
|
205 |
|
206 def __mimeFromUrl(self, url): |
|
207 """ |
|
208 Private method to guess the mime type given an URL. |
|
209 |
|
210 @param url URL to guess the mime type from (QUrl) |
|
211 @return mime type for the given URL (string) |
|
212 """ |
|
213 path = url.path() |
|
214 ext = os.path.splitext(path)[1].lower() |
|
215 if ext in ExtensionMap: |
|
216 return ExtensionMap[ext] |
|
217 else: |
|
218 return "application/octet-stream" |
|
219 |
|
220 def mimeType(self): |
|
221 """ |
|
222 Public method to get the reply mime type. |
|
223 |
|
224 @return mime type of the reply |
|
225 @rtype bytes |
|
226 """ |
|
227 return self.__mimeType.encode("utf-8") |