1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a thread class populating and updating the QtHelp 8documentation database. 9""" 10 11import os 12 13from PyQt5.QtCore import ( 14 pyqtSignal, QThread, Qt, QMutex, QDateTime, QDir, QLibraryInfo, QFileInfo 15) 16from PyQt5.QtHelp import QHelpEngineCore 17 18from eric6config import getConfig 19 20from Globals import qVersionTuple 21 22 23class HelpDocsInstaller(QThread): 24 """ 25 Class implementing the worker thread populating and updating the QtHelp 26 documentation database. 27 28 @signal errorMessage(str) emitted, if an error occurred during 29 the installation of the documentation 30 @signal docsInstalled(bool) emitted after the installation has finished 31 """ 32 errorMessage = pyqtSignal(str) 33 docsInstalled = pyqtSignal(bool) 34 35 def __init__(self, collection): 36 """ 37 Constructor 38 39 @param collection full pathname of the collection file (string) 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", "qdoc", "qmake", "qt3d", "qt3drenderer", 74 "qtandroidextras", "qtassistant", "qtbluetooth", "qtcanvas3d", 75 "qtcharts", "qtconcurrent", "qtcore", "qtdatavisualization", 76 "qtdbus", "qtdesigner", "qtdistancefieldgenerator", "qtdoc", 77 "qtenginio", "qtenginiooverview", "qtenginoqml", "qtgamepad", 78 "qtgraphicaleffects", "qtgui", "qthelp", "qtimageformats", 79 "qtlabscalendar", "qtlabsplatform", "qtlabscontrols", "qtlinguist", 80 "qtlocation", "qtlottieanimation", "qtmaxextras", "qtmultimedia", 81 "qtmultimediawidgets", "qtnetwork", "qtnetworkauth", "qtnfc", 82 "qtopengl", "qtplatformheaders", "qtpositioning", "qtprintsupport", 83 "qtpurchasing", "qtqml", "qtqmltest", "qtquick", "qtquickcontrols", 84 "qtquickcontrols1", "qtquickdialogs", "qtquickextras", 85 "qtquicklayouts", "qtremoteobjects", "qtscript", "qtscripttools", 86 "qtscxml", "qtsensors", "qtserialbus", "qtserialport", "qtspeech", 87 "qtsql", "qtsvg", "qttest", "qttestlib", "qtuitools", 88 "qtvirtualkeyboard", "qtwaylandcompositor", "qtwebchannel", 89 "qtwebengine", "qtwebenginewidgets", "qtwebkit", 90 "qtwebkitexamples", "qtwebsockets", "qtwebview", "qtwidgets", 91 "qtwinextras", "qtx11extras", "qtxml", "qtxmlpatterns"] 92 for qtDocs, version in [(qt5Docs, 5)]: 93 for doc in qtDocs: 94 changes |= self.__installQtDoc(doc, version, engine) 95 self.__mutex.lock() 96 if self.__abort: 97 engine = None 98 self.__mutex.unlock() 99 return 100 self.__mutex.unlock() 101 102 changes |= self.__installEric6Doc(engine) 103 engine = None 104 del engine 105 self.docsInstalled.emit(changes) 106 107 def __installQtDoc(self, name, version, engine): 108 """ 109 Private method to install/update a Qt help document. 110 111 @param name name of the Qt help document (string) 112 @param version Qt version of the help documens (integer) 113 @param engine reference to the help engine (QHelpEngineCore) 114 @return flag indicating success (boolean) 115 """ 116 versionKey = "qt_version_{0}@@{1}".format(version, name) 117 info = engine.customValue(versionKey, "") 118 lst = info.split('|') 119 120 dt = QDateTime() 121 if len(lst) and lst[0]: 122 dt = QDateTime.fromString(lst[0], Qt.DateFormat.ISODate) 123 124 qchFile = "" 125 if len(lst) == 2: 126 qchFile = lst[1] 127 128 if version == 4: 129 docsPath = QDir( 130 QLibraryInfo.location( 131 QLibraryInfo.LibraryLocation.DocumentationPath) + 132 QDir.separator() + "qch") 133 elif version == 5: 134 docsPath = QLibraryInfo.location( 135 QLibraryInfo.LibraryLocation.DocumentationPath) 136 if ( 137 not os.path.isdir(docsPath) or 138 len(QDir(docsPath).entryList(["*.qch"])) == 0 139 ): 140 docsPathList = QDir.fromNativeSeparators(docsPath).split("/") 141 docsPath = os.sep.join( 142 docsPathList[:-3] + 143 ["Docs", "Qt-{0}.{1}".format(*qVersionTuple())]) 144 docsPath = QDir(docsPath) 145 else: 146 # unsupported Qt version 147 return False 148 149 files = docsPath.entryList(["*.qch"]) 150 if not files: 151 engine.setCustomValue( 152 versionKey, 153 QDateTime().toString(Qt.DateFormat.ISODate) + '|') 154 return False 155 156 for f in files: 157 if f.startswith(name + "."): 158 fi = QFileInfo(docsPath.absolutePath() + QDir.separator() + f) 159 namespace = QHelpEngineCore.namespaceName( 160 fi.absoluteFilePath()) 161 if not namespace: 162 continue 163 164 if ( 165 dt.isValid() and 166 namespace in engine.registeredDocumentations() and 167 (fi.lastModified().toString(Qt.DateFormat.ISODate) == 168 dt.toString(Qt.DateFormat.ISODate)) and 169 qchFile == fi.absoluteFilePath() 170 ): 171 return False 172 173 if namespace in engine.registeredDocumentations(): 174 engine.unregisterDocumentation(namespace) 175 176 if not engine.registerDocumentation(fi.absoluteFilePath()): 177 self.errorMessage.emit( 178 self.tr( 179 """<p>The file <b>{0}</b> could not be""" 180 """ registered. <br/>Reason: {1}</p>""") 181 .format(fi.absoluteFilePath, engine.error()) 182 ) 183 return False 184 185 engine.setCustomValue( 186 versionKey, 187 fi.lastModified().toString(Qt.DateFormat.ISODate) + '|' + 188 fi.absoluteFilePath()) 189 return True 190 191 return False 192 193 def __installEric6Doc(self, engine): 194 """ 195 Private method to install/update the eric help documentation. 196 197 @param engine reference to the help engine (QHelpEngineCore) 198 @return flag indicating success (boolean) 199 """ 200 versionKey = "eric6_ide" 201 info = engine.customValue(versionKey, "") 202 lst = info.split('|') 203 204 dt = QDateTime() 205 if len(lst) and lst[0]: 206 dt = QDateTime.fromString(lst[0], Qt.DateFormat.ISODate) 207 208 qchFile = "" 209 if len(lst) == 2: 210 qchFile = lst[1] 211 212 docsPath = QDir(getConfig("ericDocDir") + QDir.separator() + "Help") 213 214 files = docsPath.entryList(["*.qch"]) 215 if not files: 216 engine.setCustomValue( 217 versionKey, QDateTime().toString(Qt.DateFormat.ISODate) + '|') 218 return False 219 220 for f in files: 221 if f == "source.qch": 222 fi = QFileInfo(docsPath.absolutePath() + QDir.separator() + f) 223 namespace = QHelpEngineCore.namespaceName( 224 fi.absoluteFilePath()) 225 if not namespace: 226 continue 227 228 if ( 229 dt.isValid() and 230 namespace in engine.registeredDocumentations() and 231 (fi.lastModified().toString(Qt.DateFormat.ISODate) == 232 dt.toString(Qt.DateFormat.ISODate)) and 233 qchFile == fi.absoluteFilePath() 234 ): 235 return False 236 237 if namespace in engine.registeredDocumentations(): 238 engine.unregisterDocumentation(namespace) 239 240 if not engine.registerDocumentation(fi.absoluteFilePath()): 241 self.errorMessage.emit( 242 self.tr( 243 """<p>The file <b>{0}</b> could not be""" 244 """ registered. <br/>Reason: {1}</p>""") 245 .format(fi.absoluteFilePath, engine.error()) 246 ) 247 return False 248 249 engine.setCustomValue( 250 versionKey, 251 fi.lastModified().toString(Qt.DateFormat.ISODate) + '|' + 252 fi.absoluteFilePath()) 253 return True 254 255 return False 256