7 Module implementing the APIsManager. |
7 Module implementing the APIsManager. |
8 """ |
8 """ |
9 |
9 |
10 import os |
10 import os |
11 |
11 |
12 from PyQt4.QtCore import QTimer, QThread, QFileInfo, pyqtSignal, QCoreApplication, \ |
12 from PyQt4.QtCore import QTimer, QThread, QFileInfo, pyqtSignal, QDateTime, QObject, Qt |
13 QEvent, QDateTime, QObject, Qt |
|
14 try: |
13 try: |
15 from PyQt4.QtSql import QSqlDatabase, QSqlQuery |
14 from PyQt4.QtSql import QSqlDatabase, QSqlQuery |
16 except ImportError: |
15 except ImportError: |
17 # just ignore it because the main plug-in file will deal with it |
16 # just ignore it because the main plug-in file will deal with it |
18 pass |
17 pass |
23 |
22 |
24 import Utilities.ModuleParser |
23 import Utilities.ModuleParser |
25 import Utilities |
24 import Utilities |
26 import Preferences |
25 import Preferences |
27 |
26 |
28 WorkerStarted = QEvent.User + 2001 |
27 WorkerStatusStarted = 2001 |
29 WorkerFinished = QEvent.User + 2002 |
28 WorkerStatusFinished = 2002 |
30 WorkerAborted = QEvent.User + 2003 |
29 WorkerStatusAborted = 2003 |
|
30 WorkerStatusFile = 2004 |
31 |
31 |
32 ApisNameProject = "__Project__" |
32 ApisNameProject = "__Project__" |
33 |
33 |
34 |
34 |
35 class DbAPIsWorker(QThread): |
35 class DbAPIsWorker(QThread): |
36 """ |
36 """ |
37 Class implementing a worker thread to prepare the API database. |
37 Class implementing a worker thread to prepare the API database. |
38 """ |
38 |
|
39 @signal processing(status, file) emitted to indicate the processing status |
|
40 (one of WorkerStatus..., string) |
|
41 """ |
|
42 processing = pyqtSignal(int, str) |
|
43 |
39 populate_api_stmt = """ |
44 populate_api_stmt = """ |
40 INSERT INTO api (acWord, context, fullContext, signature, fileId, pictureId) |
45 INSERT INTO api (acWord, context, fullContext, signature, fileId, pictureId) |
41 VALUES (:acWord, :context, :fullContext, :signature, :fileId, :pictureId) |
46 VALUES (:acWord, :context, :fullContext, :signature, :fileId, :pictureId) |
42 """ |
47 """ |
43 populate_del_api_stmt = """ |
48 populate_del_api_stmt = """ |
142 if os.path.exists(basesFile): |
147 if os.path.exists(basesFile): |
143 modTimeBases = QFileInfo(basesFile).lastModified() |
148 modTimeBases = QFileInfo(basesFile).lastModified() |
144 if modTimeBases > modTime: |
149 if modTimeBases > modTime: |
145 modTime = modTimeBases |
150 modTime = modTimeBases |
146 if loadTime < modTime: |
151 if loadTime < modTime: |
|
152 self.processing.emit(WorkerStatusFile, apiFile) |
147 self.__loadApiFile(apiFile) |
153 self.__loadApiFile(apiFile) |
148 |
154 |
149 def __classesAttributesApi(self, module): |
155 def __classesAttributesApi(self, module): |
150 """ |
156 """ |
151 Private method to generate class api section for class attributes. |
157 Private method to generate class api section for class attributes. |
219 pass |
225 pass |
220 language = None |
226 language = None |
221 |
227 |
222 if len(apis) > 0: |
228 if len(apis) > 0: |
223 self.__storeApis(apis, bases, apiFile, language) |
229 self.__storeApis(apis, bases, apiFile, language) |
|
230 else: |
|
231 # just store file info to avoid rereading it every time |
|
232 self.__storeFileInfoOnly(apiFile) |
|
233 |
|
234 def __storeFileInfoOnly(self, apiFile): |
|
235 """ |
|
236 Private method to store file info only. |
|
237 |
|
238 Doing this avoids rereading the file whenever the API is initialized in case |
|
239 the given file doesn't contain API data. |
|
240 """ |
|
241 db = QSqlDatabase.database(self.__language) |
|
242 db.transaction() |
|
243 try: |
|
244 query = QSqlQuery(db) |
|
245 # step 1: create entry in file table |
|
246 query.prepare(self.populate_file_stmt) |
|
247 query.bindValue(":file", apiFile) |
|
248 query.exec_() |
|
249 |
|
250 # step 2: update the file entry |
|
251 query.prepare(self.update_file_stmt) |
|
252 query.bindValue(":lastRead", QDateTime.currentDateTime()) |
|
253 query.bindValue(":file", apiFile) |
|
254 query.exec_() |
|
255 finally: |
|
256 del query |
|
257 if self.__aborted: |
|
258 db.rollback() |
|
259 else: |
|
260 db.commit() |
224 |
261 |
225 def __storeApis(self, apis, bases, apiFile, language): |
262 def __storeApis(self, apis, bases, apiFile, language): |
226 """ |
263 """ |
227 Private method to put the API entries into the database. |
264 Private method to put the API entries into the database. |
228 |
265 |
339 query.bindValue(":baseClasses", baseClasses) |
376 query.bindValue(":baseClasses", baseClasses) |
340 query.bindValue(":fileId", id) |
377 query.bindValue(":fileId", id) |
341 query.exec_() |
378 query.exec_() |
342 |
379 |
343 if not self.__aborted: |
380 if not self.__aborted: |
344 # step 4: update the file entry |
381 # step 5: update the file entry |
345 query.prepare(self.update_file_stmt) |
382 query.prepare(self.update_file_stmt) |
346 query.bindValue(":lastRead", QDateTime.currentDateTime()) |
383 query.bindValue(":lastRead", QDateTime.currentDateTime()) |
347 query.bindValue(":file", apiFile) |
384 query.bindValue(":file", apiFile) |
348 query.exec_() |
385 query.exec_() |
349 finally: |
386 finally: |
391 |
428 |
392 def run(self): |
429 def run(self): |
393 """ |
430 """ |
394 Public method to perform the threads work. |
431 Public method to perform the threads work. |
395 """ |
432 """ |
396 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerStarted))) |
433 self.processing.emit(WorkerStatusStarted, "") |
397 |
434 |
398 db = QSqlDatabase.database(self.__language) |
435 db = QSqlDatabase.database(self.__language) |
399 if db.isValid() and db.isOpen(): |
436 if db.isValid() and db.isOpen(): |
400 # step 1: remove API files not wanted any longer |
437 # step 1: remove API files not wanted any longer |
401 if not self.__refresh: |
438 if not self.__refresh: |
408 for apiFile in self.__apiFiles: |
445 for apiFile in self.__apiFiles: |
409 if not self.__aborted: |
446 if not self.__aborted: |
410 self.__loadApiFileIfNewer(apiFile) |
447 self.__loadApiFileIfNewer(apiFile) |
411 |
448 |
412 if self.__aborted: |
449 if self.__aborted: |
413 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerAborted))) |
450 self.processing.emit(WorkerStatusAborted, "") |
414 else: |
451 else: |
415 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerFinished))) |
452 self.processing.emit(WorkerStatusFinished, "") |
416 |
453 |
417 |
454 |
418 class DbAPIs(QObject): |
455 class DbAPIs(QObject): |
419 """ |
456 """ |
420 Class implementing an API storage entity. |
457 Class implementing an API storage entity. |
421 |
458 |
422 @signal apiPreparationFinished(language) emitted after the API preparation has finished |
459 @signal apiPreparationStatus(language, status, file) emitted to indicate the |
423 @signal apiPreparationStarted(language) emitted after the API preparation has started |
460 API preparation status for a language |
424 @signal apiPreparationCancelled(language) emitted after the API preparation has been |
461 """ |
425 cancelled |
462 apiPreparationStatus = pyqtSignal(str, int, str) |
426 """ |
|
427 apiPreparationFinished = pyqtSignal(str) |
|
428 apiPreparationStarted = pyqtSignal(str) |
|
429 apiPreparationCancelled = pyqtSignal(str) |
|
430 |
463 |
431 DB_VERSION = 4 |
464 DB_VERSION = 4 |
432 |
465 |
433 create_mgmt_stmt = """ |
466 create_mgmt_stmt = """ |
434 CREATE TABLE mgmt |
467 CREATE TABLE mgmt |
908 [f for f in self.__getProjectFormSources() if f not in apiFiles]) |
941 [f for f in self.__getProjectFormSources() if f not in apiFiles]) |
909 projectPath = self.__project.getProjectPath() |
942 projectPath = self.__project.getProjectPath() |
910 else: |
943 else: |
911 apiFiles = Preferences.getEditorAPI(self.__language) |
944 apiFiles = Preferences.getEditorAPI(self.__language) |
912 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath) |
945 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath) |
|
946 self.__worker.processing.connect(self.__processingStatus, Qt.QueuedConnection) |
913 self.__worker.start() |
947 self.__worker.start() |
914 |
948 |
915 def __processQueue(self): |
949 def __processQueue(self): |
916 """ |
950 """ |
917 Private slot to process the queue of files to load. |
951 Private slot to process the queue of files to load. |
918 """ |
952 """ |
919 if self.__worker is not None and self.__worker.isFinished(): |
953 if self.__worker is not None and self.__worker.isFinished(): |
|
954 self.__worker.deleteLater() |
920 self.__worker = None |
955 self.__worker = None |
921 |
956 |
922 if self.__worker is None and len(self.__workerQueue) > 0: |
957 if self.__worker is None and len(self.__workerQueue) > 0: |
923 apiFiles = [self.__workerQueue.pop(0)] |
958 apiFiles = [self.__workerQueue.pop(0)] |
924 if self.__language == ApisNameProject: |
959 if self.__language == ApisNameProject: |
926 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")] |
961 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")] |
927 else: |
962 else: |
928 projectPath = "" |
963 projectPath = "" |
929 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath, |
964 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath, |
930 refresh=True) |
965 refresh=True) |
|
966 self.__worker.processing.connect(self.__processingStatus, Qt.QueuedConnection) |
931 self.__worker.start() |
967 self.__worker.start() |
932 |
968 |
933 def getLexer(self): |
969 def getLexer(self): |
934 """ |
970 """ |
935 Public method to return a reference to our lexer object. |
971 Public method to return a reference to our lexer object. |
946 """ |
982 """ |
947 if self.__lexer: |
983 if self.__lexer: |
948 return self.__lexer.autoCompletionWordSeparators() |
984 return self.__lexer.autoCompletionWordSeparators() |
949 return None |
985 return None |
950 |
986 |
951 def event(self, evt): |
987 def __processingStatus(self, status, filename): |
952 """ |
988 """ |
953 Protected method to handle events from the worker thread. |
989 Private slot handling the processing signal of the API preparation |
954 |
990 thread. |
955 @param evt reference to the event object (QEvent) |
991 |
956 @return flag indicating, if the event was handled (boolean) |
992 @param status preparation status (integer, one of WorkerStatus...) |
957 """ |
993 @param filename name of the file being processed (string) |
958 if evt.type() == WorkerStarted: |
994 """ |
|
995 if status == WorkerStatusStarted: |
959 self.__inPreparation = True |
996 self.__inPreparation = True |
960 self.apiPreparationStarted.emit(self.__language) |
997 self.apiPreparationStatus.emit(self.__language, WorkerStatusStarted, "") |
961 return True |
998 elif status == WorkerStatusFinished: |
962 |
|
963 elif evt.type() == WorkerFinished: |
|
964 self.__inPreparation = False |
999 self.__inPreparation = False |
965 self.apiPreparationFinished.emit(self.__language) |
1000 self.apiPreparationStatus.emit(self.__language, WorkerStatusFinished, "") |
966 QTimer.singleShot(0, self.__processQueue) |
1001 QTimer.singleShot(0, self.__processQueue) |
967 return True |
1002 elif status == WorkerStatusAborted: |
968 |
|
969 elif evt.type() == WorkerAborted: |
|
970 self.__inPreparation = False |
1003 self.__inPreparation = False |
971 self.apiPreparationCancelled.emit(self.__language) |
1004 self.apiPreparationStatus.emit(self.__language, WorkerStatusAborted, "") |
972 QTimer.singleShot(0, self.__processQueue) |
1005 QTimer.singleShot(0, self.__processQueue) |
973 return True |
1006 elif status == WorkerStatusFile: |
974 |
1007 self.apiPreparationStatus.emit(self.__language, WorkerStatusFile, filename) |
975 else: |
|
976 return QObject.event(self, evt) |
|
977 |
1008 |
978 ######################################################## |
1009 ######################################################## |
979 ## project related stuff below |
1010 ## project related stuff below |
980 ######################################################## |
1011 ######################################################## |
981 |
1012 |
1058 except KeyError: |
1089 except KeyError: |
1059 if language in QScintilla.Lexers.getSupportedLanguages() or \ |
1090 if language in QScintilla.Lexers.getSupportedLanguages() or \ |
1060 language == ApisNameProject: |
1091 language == ApisNameProject: |
1061 # create the api object |
1092 # create the api object |
1062 api = DbAPIs(language) |
1093 api = DbAPIs(language) |
1063 api.apiPreparationFinished.connect(self.__apiPreparationFinished) |
1094 api.apiPreparationStatus.connect(self.__apiPreparationStatus) |
1064 api.apiPreparationStarted.connect(self.__apiPreparationStarted) |
|
1065 api.apiPreparationCancelled.connect(self.__apiPreparationCancelled) |
|
1066 self.__apis[language] = api |
1095 self.__apis[language] = api |
1067 return self.__apis[language] |
1096 return self.__apis[language] |
1068 else: |
1097 else: |
1069 return None |
1098 return None |
1070 |
1099 |
1081 """ |
1110 """ |
1082 Private message to show a message in the main windows status bar. |
1111 Private message to show a message in the main windows status bar. |
1083 |
1112 |
1084 @param msg message to be shown (string) |
1113 @param msg message to be shown (string) |
1085 """ |
1114 """ |
1086 self.__mw.statusBar().showMessage(msg, 2000) |
1115 if msg: |
|
1116 self.__mw.statusBar().showMessage(msg, 2000) |
1087 |
1117 |
1088 def __apiPreparationFinished(self, language): |
1118 def __apiPreparationFinished(self, language): |
1089 """ |
1119 """ |
1090 Private slot handling the preparation finished signal of an API object. |
1120 Private slot handling the preparation finished signal of an API object. |
1091 |
1121 |
1115 """ |
1145 """ |
1116 if language == ApisNameProject: |
1146 if language == ApisNameProject: |
1117 language = self.trUtf8("Project") |
1147 language = self.trUtf8("Project") |
1118 self.__showMessage(self.trUtf8("Preparation of '{0}' APIs cancelled.") |
1148 self.__showMessage(self.trUtf8("Preparation of '{0}' APIs cancelled.") |
1119 .format(language)) |
1149 .format(language)) |
|
1150 |
|
1151 def __apiPreparationStatus(self, language, status, filename): |
|
1152 """ |
|
1153 Private slot handling the preparation status signal of an API object. |
|
1154 |
|
1155 @param language language of the API (string) |
|
1156 @param status preparation status (integer, one of WorkerStatus...) |
|
1157 @param filename name of the file being processed (string) |
|
1158 """ |
|
1159 if language == ApisNameProject: |
|
1160 language = self.trUtf8("Project") |
|
1161 |
|
1162 if status == WorkerStatusStarted: |
|
1163 self.__showMessage(self.trUtf8("Preparation of '{0}' APIs started.").format( |
|
1164 language)) |
|
1165 elif status == WorkerStatusFile: |
|
1166 self.__showMessage(self.trUtf8("'{0}' APIs: Processing '{1}'").format( |
|
1167 language, os.path.basename(filename))) |
|
1168 elif status == WorkerStatusFinished: |
|
1169 self.__showMessage(self.trUtf8("Preparation of '{0}' APIs finished.").format( |
|
1170 language)) |
|
1171 elif status == WorkerStatusAborted: |
|
1172 self.__showMessage(self.trUtf8("Preparation of '{0}' APIs cancelled.").format( |
|
1173 language)) |