1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the APIsManager.
8"""
9
10import os
11
12from PyQt5.QtCore import QDir, QFileInfo, pyqtSignal, QObject
13from PyQt5.Qsci import QsciAPIs
14
15from . import Lexers
16import Preferences
17import Globals
18
19
20class APIs(QObject):
21    """
22    Class implementing an API storage entity.
23
24    @signal apiPreparationFinished() emitted after the API preparation has
25        finished
26    @signal apiPreparationCancelled() emitted after the API preparation has
27        been cancelled
28    @signal apiPreparationStarted() emitted after the API preparation has
29        started
30    """
31    apiPreparationFinished = pyqtSignal()
32    apiPreparationCancelled = pyqtSignal()
33    apiPreparationStarted = pyqtSignal()
34
35    def __init__(self, language, projectType="", forPreparation=False,
36                 parent=None):
37        """
38        Constructor
39
40        @param language language of the APIs object
41        @type str
42        @param projectType type of the project
43        @type str
44        @param forPreparation flag indicating this object is just needed
45            for a preparation process
46        @type bool
47        @param parent reference to the parent object
48        @type QObject
49        """
50        super().__init__(parent)
51        if projectType:
52            self.setObjectName("APIs_{0}_{1}".format(language, projectType))
53        else:
54            self.setObjectName("APIs_{0}".format(language))
55
56        self.__inPreparation = False
57        self.__language = language
58        self.__projectType = projectType
59        self.__forPreparation = forPreparation
60        self.__lexer = Lexers.getLexer(self.__language)
61        self.__apifiles = Preferences.getEditorAPI(self.__language,
62                                                   self.__projectType)
63        self.__apifiles.sort()
64        if self.__lexer is None:
65            self.__apis = None
66        else:
67            self.__apis = QsciAPIs(self.__lexer)
68            self.__apis.apiPreparationFinished.connect(
69                self.__apiPreparationFinished)
70            self.__apis.apiPreparationCancelled.connect(
71                self.__apiPreparationCancelled)
72            self.__apis.apiPreparationStarted.connect(
73                self.__apiPreparationStarted)
74            self.__loadAPIs()
75
76    def __loadAPIs(self):
77        """
78        Private method to load the APIs.
79        """
80        if self.__apis.isPrepared():
81            # load a prepared API file
82            if (
83                not self.__forPreparation and
84                Preferences.getEditor("AutoPrepareAPIs")
85            ):
86                self.prepareAPIs()
87            self.__apis.loadPrepared(self.__preparedName())
88        else:
89            # load the raw files and prepare the API file
90            if (
91                not self.__forPreparation and
92                Preferences.getEditor("AutoPrepareAPIs")
93            ):
94                self.prepareAPIs(ondemand=True)
95
96    def reloadAPIs(self):
97        """
98        Public method to reload the API information.
99        """
100        if (
101            not self.__forPreparation and
102            Preferences.getEditor("AutoPrepareAPIs")
103        ):
104            self.prepareAPIs()
105        self.__loadAPIs()
106
107    def getQsciAPIs(self):
108        """
109        Public method to get a reference to QsciAPIs object.
110
111        @return reference to the QsciAPIs object (QsciAPIs)
112        """
113        if (
114            not self.__forPreparation and
115            Preferences.getEditor("AutoPrepareAPIs")
116        ):
117            self.prepareAPIs()
118        return self.__apis
119
120    def isEmpty(self):
121        """
122        Public method to check, if the object has API files configured.
123
124        @return flag indicating no API files have been configured (boolean)
125        """
126        return len(self.__apifiles) == 0
127
128    def __apiPreparationFinished(self):
129        """
130        Private method called to save an API, after it has been prepared.
131        """
132        self.__apis.savePrepared(self.__preparedName())
133        self.__inPreparation = False
134        self.apiPreparationFinished.emit()
135
136    def __apiPreparationCancelled(self):
137        """
138        Private method called, after the API preparation process has been
139        cancelled.
140        """
141        self.__inPreparation = False
142        self.apiPreparationCancelled.emit()
143
144    def __apiPreparationStarted(self):
145        """
146        Private method called, when the API preparation process started.
147        """
148        self.__inPreparation = True
149        self.apiPreparationStarted.emit()
150
151    def prepareAPIs(self, ondemand=False, rawList=None):
152        """
153        Public method to prepare the APIs if necessary.
154
155        @param ondemand flag indicating a requested preparation (boolean)
156        @param rawList list of raw API files (list of strings)
157        """
158        if self.__apis is None or self.__inPreparation:
159            return
160
161        needsPreparation = False
162        if ondemand:
163            needsPreparation = True
164        else:
165            # check, if a new preparation is necessary
166            preparedAPIs = self.__preparedName()
167            if preparedAPIs:
168                preparedAPIsInfo = QFileInfo(preparedAPIs)
169                if not preparedAPIsInfo.exists():
170                    needsPreparation = True
171                else:
172                    preparedAPIsTime = preparedAPIsInfo.lastModified()
173                    apifiles = sorted(Preferences.getEditorAPI(
174                        self.__language, self.__projectType))
175                    if self.__apifiles != apifiles:
176                        needsPreparation = True
177                    for apifile in apifiles:
178                        if (
179                            QFileInfo(apifile).lastModified() >
180                            preparedAPIsTime
181                        ):
182                            needsPreparation = True
183                            break
184
185        if needsPreparation:
186            # do the preparation
187            self.__apis.clear()
188            if rawList:
189                apifiles = rawList
190            else:
191                apifiles = Preferences.getEditorAPI(
192                    self.__language, self.__projectType)
193            for apifile in apifiles:
194                self.__apis.load(apifile)
195            self.__apis.prepare()
196            self.__apifiles = apifiles
197
198    def cancelPreparation(self):
199        """
200        Public slot to cancel the APIs preparation.
201        """
202        self.__apis and self.__apis.cancelPreparation()
203
204    def installedAPIFiles(self):
205        """
206        Public method to get a list of installed API files.
207
208        @return list of installed API files (list of strings)
209        """
210        if self.__apis is not None:
211            if Globals.isWindowsPlatform():
212                qsciPath = os.path.join(
213                    Globals.getPyQt5ModulesDirectory(), "qsci")
214                if os.path.exists(qsciPath):
215                    # it's the installer
216                    if self.__lexer.lexerName() is not None:
217                        apidir = os.path.join(qsciPath, "api",
218                                              self.__lexer.lexerName())
219                        fnames = []
220                        filist = QDir(apidir).entryInfoList(
221                            ["*.api"], QDir.Filter.Files,
222                            QDir.SortFlag.IgnoreCase)
223                        for fi in filist:
224                            fnames.append(fi.absoluteFilePath())
225                        return fnames
226                    else:
227                        return []
228
229            return self.__apis.installedAPIFiles()
230        else:
231            return []
232
233    def __preparedName(self):
234        """
235        Private method returning the default name of a prepared API file.
236
237        @return complete filename for the Prepared APIs file (string)
238        """
239        apisDir = os.path.join(Globals.getConfigDir(), "APIs")
240        if self.__apis is not None:
241            if self.__projectType:
242                filename = "{0}_{1}.pap".format(self.__language,
243                                                self.__projectType)
244            else:
245                filename = "{0}.pap".format(self.__language)
246            return os.path.join(apisDir, filename)
247        else:
248            return ""
249
250
251class APIsManager(QObject):
252    """
253    Class implementing the APIsManager class, which is the central store for
254    API information used by autocompletion and calltips.
255    """
256    def __init__(self, parent=None):
257        """
258        Constructor
259
260        @param parent reference to the parent object (QObject)
261        """
262        super().__init__(parent)
263        self.setObjectName("APIsManager")
264
265        self.__apis = {}
266
267    def reloadAPIs(self):
268        """
269        Public slot to reload the api information.
270        """
271        for api in list(self.__apis.values()):
272            api and api.reloadAPIs()
273
274    def getAPIs(self, language, projectType="", forPreparation=False):
275        """
276        Public method to get an APIs object for autocompletion/calltips.
277
278        This method creates and loads an APIs object dynamically upon request.
279        This saves memory for languages, that might not be needed at the
280        moment.
281
282        @param language language of the requested APIs object
283        @type str
284        @param projectType type of the project
285        @type str
286        @param forPreparation flag indicating the requested APIs object is just
287            needed for a preparation process
288        @type bool
289        @return reference to the APIs object
290        @rtype APIs
291        """
292        if forPreparation:
293            return APIs(language, projectType=projectType,
294                        forPreparation=forPreparation)
295        else:
296            try:
297                return self.__apis[(language, projectType)]
298            except KeyError:
299                if language in Lexers.getSupportedApiLanguages():
300                    # create the api object
301                    self.__apis[(language, projectType)] = APIs(
302                        language, projectType=projectType)
303                    return self.__apis[(language, projectType)]
304                else:
305                    return None
306