|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a thread class populating and updating the QtHelp |
|
8 documentation database. |
|
9 """ |
|
10 |
|
11 import datetime |
|
12 import pathlib |
|
13 |
|
14 from PyQt6.QtCore import QLibraryInfo, QMutex, QThread, pyqtSignal |
|
15 from PyQt6.QtHelp import QHelpEngineCore |
|
16 |
|
17 from eric7.Globals import getConfig |
|
18 from eric7.SystemUtilities import QtUtilities |
|
19 |
|
20 |
|
21 class HelpDocsInstaller(QThread): |
|
22 """ |
|
23 Class implementing the worker thread populating and updating the QtHelp |
|
24 documentation database. |
|
25 |
|
26 @signal errorMessage(str) emitted, if an error occurred during |
|
27 the installation of the documentation |
|
28 @signal docsInstalled(bool) emitted after the installation has finished |
|
29 """ |
|
30 |
|
31 errorMessage = pyqtSignal(str) |
|
32 docsInstalled = pyqtSignal(bool) |
|
33 |
|
34 def __init__(self, collection): |
|
35 """ |
|
36 Constructor |
|
37 |
|
38 @param collection full pathname of the collection file |
|
39 @type str |
|
40 """ |
|
41 super().__init__() |
|
42 |
|
43 self.__abort = False |
|
44 self.__collection = collection |
|
45 self.__mutex = QMutex() |
|
46 |
|
47 def stop(self): |
|
48 """ |
|
49 Public slot to stop the installation procedure. |
|
50 """ |
|
51 if not self.isRunning(): |
|
52 return |
|
53 |
|
54 self.__mutex.lock() |
|
55 self.__abort = True |
|
56 self.__mutex.unlock() |
|
57 self.wait() |
|
58 |
|
59 def installDocs(self): |
|
60 """ |
|
61 Public method to start the installation procedure. |
|
62 """ |
|
63 self.start(QThread.Priority.LowPriority) |
|
64 |
|
65 def run(self): |
|
66 """ |
|
67 Public method executed by the thread. |
|
68 """ |
|
69 engine = QHelpEngineCore(self.__collection) |
|
70 changes = False |
|
71 |
|
72 qt5Docs = [ |
|
73 "activeqt", |
|
74 "qdoc", |
|
75 "qmake", |
|
76 "qt3d", |
|
77 "qt3drenderer", |
|
78 "qtandroidextras", |
|
79 "qtassistant", |
|
80 "qtbluetooth", |
|
81 "qtcanvas3d", |
|
82 "qtcharts", |
|
83 "qtcmake", |
|
84 "qtconcurrent", |
|
85 "qtcore", |
|
86 "qtdatavis3d", |
|
87 "qtdatavisualization", |
|
88 "qtdbus", |
|
89 "qtdesigner", |
|
90 "qtdistancefieldgenerator", |
|
91 "qtdoc", |
|
92 "qtenginio", |
|
93 "qtenginiooverview", |
|
94 "qtenginoqml", |
|
95 "qtgamepad", |
|
96 "qtgraphicaleffects", |
|
97 "qtgui", |
|
98 "qthelp", |
|
99 "qthttpserver", |
|
100 "qtimageformats", |
|
101 "qtlabscalendar", |
|
102 "qtlabsplatform", |
|
103 "qtlabscontrols", |
|
104 "qtlinguist", |
|
105 "qtlocation", |
|
106 "qtlottieanimation", |
|
107 "qtmacextras", |
|
108 "qtmultimedia", |
|
109 "qtmultimediawidgets", |
|
110 "qtnetwork", |
|
111 "qtnetworkauth", |
|
112 "qtnfc", |
|
113 "qtopengl", |
|
114 "qtpdf", |
|
115 "qtplatformheaders", |
|
116 "qtpositioning", |
|
117 "qtprintsupport", |
|
118 "qtpurchasing", |
|
119 "qtqml", |
|
120 "qtqmlcore", |
|
121 "qtqmlmodels", |
|
122 "qtqmltest", |
|
123 "qtqmlworkerscript", |
|
124 "qtqmlxmllistmodel", |
|
125 "qtquick", |
|
126 "qtquick3d", |
|
127 "qtquick3dphysics", |
|
128 "qtquickcontrols", |
|
129 "qtquickcontrols1", |
|
130 "qtquickdialogs", |
|
131 "qtquickextras", |
|
132 "qtquicklayouts", |
|
133 "qtquicktimeline", |
|
134 "qtremoteobjects", |
|
135 "qtscript", |
|
136 "qtscripttools", |
|
137 "qtscxml", |
|
138 "qtsensors", |
|
139 "qtserialbus", |
|
140 "qtserialport", |
|
141 "qtshadertools", |
|
142 "qtspatialaudio", |
|
143 "qtspeech", |
|
144 "qtsql", |
|
145 "qtstatemachine", |
|
146 "qtsvg", |
|
147 "qttest", |
|
148 "qttestlib", |
|
149 "qtuitools", |
|
150 "qtvirtualkeyboard", |
|
151 "qtwaylandcompositor", |
|
152 "qtwebchannel", |
|
153 "qtwebengine", |
|
154 "qtwebenginewidgets", |
|
155 "qtwebkit", |
|
156 "qtwebkitexamples", |
|
157 "qtwebsockets", |
|
158 "qtwebview", |
|
159 "qtwidgets", |
|
160 "qtwinextras", |
|
161 "qtx11extras", |
|
162 "qtxml", |
|
163 "qtxmlpatterns", |
|
164 ] |
|
165 for qtDocs, version in [(qt5Docs, 5)]: |
|
166 for doc in qtDocs: |
|
167 changes |= self.__installQtDoc(doc, version, engine) |
|
168 self.__mutex.lock() |
|
169 if self.__abort: |
|
170 engine = None |
|
171 self.__mutex.unlock() |
|
172 return |
|
173 self.__mutex.unlock() |
|
174 |
|
175 changes |= self.__installEric7Doc(engine) |
|
176 engine = None |
|
177 del engine |
|
178 self.docsInstalled.emit(changes) |
|
179 |
|
180 def __installQtDoc(self, name, version, engine): |
|
181 """ |
|
182 Private method to install/update a Qt help document. |
|
183 |
|
184 @param name name of the Qt help document |
|
185 @type str |
|
186 @param version Qt version of the help documents |
|
187 @type int |
|
188 @param engine reference to the help engine |
|
189 @type QHelpEngineCore |
|
190 @return flag indicating success |
|
191 @rtype bool |
|
192 """ |
|
193 versionKey = "qt_version_{0}@@{1}".format(version, name) |
|
194 info = engine.customValue(versionKey, "") |
|
195 lst = info.split("|") |
|
196 |
|
197 dt = None |
|
198 if len(lst) and lst[0]: |
|
199 dt = datetime.datetime.fromisoformat(lst[0]) |
|
200 |
|
201 qchFile = "" |
|
202 if len(lst) == 2: |
|
203 qchFile = lst[1] |
|
204 |
|
205 if version == 5: |
|
206 docsPath = pathlib.Path( |
|
207 QLibraryInfo.path(QLibraryInfo.LibraryPath.DocumentationPath) |
|
208 ) |
|
209 if not docsPath.is_dir() or len(list(docsPath.glob("*.qch"))) == 0: |
|
210 docsPath = ( |
|
211 docsPath.parents[2] |
|
212 / "Docs" |
|
213 / "Qt-{0}.{1}".format(*QtUtilities.qVersionTuple()) |
|
214 ) |
|
215 else: |
|
216 # unsupported Qt version |
|
217 return False |
|
218 |
|
219 files = list(docsPath.glob("*.qch")) |
|
220 if not files: |
|
221 engine.setCustomValue(versionKey, "|") |
|
222 return False |
|
223 |
|
224 for f in files: |
|
225 if f.stem == name: |
|
226 namespace = QHelpEngineCore.namespaceName(str(f.resolve())) |
|
227 if not namespace: |
|
228 continue |
|
229 |
|
230 if ( |
|
231 dt is not None |
|
232 and namespace in engine.registeredDocumentations() |
|
233 and (datetime.datetime.fromtimestamp(f.stat().st_mtime) == dt) |
|
234 and qchFile == str(f.resolve()) |
|
235 ): |
|
236 return False |
|
237 |
|
238 if namespace in engine.registeredDocumentations(): |
|
239 engine.unregisterDocumentation(namespace) |
|
240 |
|
241 if not engine.registerDocumentation(str(f.resolve())): |
|
242 self.errorMessage.emit( |
|
243 self.tr( |
|
244 """<p>The file <b>{0}</b> could not be""" |
|
245 """ registered. <br/>Reason: {1}</p>""" |
|
246 ).format(f, engine.error()) |
|
247 ) |
|
248 return False |
|
249 |
|
250 engine.setCustomValue( |
|
251 versionKey, |
|
252 datetime.datetime.fromtimestamp(f.stat().st_mtime).isoformat() |
|
253 + "|" |
|
254 + str(f.resolve()), |
|
255 ) |
|
256 return True |
|
257 |
|
258 return False |
|
259 |
|
260 def __installEric7Doc(self, engine): |
|
261 """ |
|
262 Private method to install/update the eric help documentation. |
|
263 |
|
264 @param engine reference to the help engine |
|
265 @type QHelpEngineCore |
|
266 @return flag indicating success |
|
267 @rtype bool |
|
268 """ |
|
269 versionKey = "eric7_ide" |
|
270 info = engine.customValue(versionKey, "") |
|
271 lst = info.split("|") |
|
272 |
|
273 dt = None |
|
274 if len(lst) and lst[0]: |
|
275 dt = datetime.datetime.fromisoformat(lst[0]) |
|
276 |
|
277 qchFile = "" |
|
278 if len(lst) == 2: |
|
279 qchFile = lst[1] |
|
280 |
|
281 docsPath = pathlib.Path(getConfig("ericDocDir")) / "Help" |
|
282 |
|
283 files = list(docsPath.glob("*.qch")) |
|
284 if not files: |
|
285 engine.setCustomValue(versionKey, "|") |
|
286 return False |
|
287 |
|
288 for f in files: |
|
289 if f.name == "source.qch": |
|
290 namespace = QHelpEngineCore.namespaceName(str(f.resolve())) |
|
291 if not namespace: |
|
292 continue |
|
293 |
|
294 if ( |
|
295 dt is not None |
|
296 and namespace in engine.registeredDocumentations() |
|
297 and (datetime.datetime.fromtimestamp(f.stat().st_mtime) == dt) |
|
298 and qchFile == str(f.resolve()) |
|
299 ): |
|
300 return False |
|
301 |
|
302 if namespace in engine.registeredDocumentations(): |
|
303 engine.unregisterDocumentation(namespace) |
|
304 |
|
305 if not engine.registerDocumentation(str(f.resolve())): |
|
306 self.errorMessage.emit( |
|
307 self.tr( |
|
308 """<p>The file <b>{0}</b> could not be""" |
|
309 """ registered. <br/>Reason: {1}</p>""" |
|
310 ).format(f, engine.error()) |
|
311 ) |
|
312 return False |
|
313 |
|
314 engine.setCustomValue( |
|
315 versionKey, |
|
316 datetime.datetime.fromtimestamp(f.stat().st_mtime).isoformat() |
|
317 + "|" |
|
318 + str(f.resolve()), |
|
319 ) |
|
320 return True |
|
321 |
|
322 return False |