1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2006 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the VCS status monitor thread base class.
8"""
9
10import contextlib
11
12from PyQt5.QtCore import (
13    QThread, QMutex, QWaitCondition, pyqtSignal, QCoreApplication
14)
15
16
17class VcsStatusMonitorThread(QThread):
18    """
19    Class implementing the VCS status monitor thread base class.
20
21    @signal vcsStatusMonitorData(list of str) emitted to update the VCS status
22    @signal vcsStatusMonitorStatus(str, str) emitted to signal the status of
23        the monitoring thread (ok, nok, op) and a status message
24    @signal vcsStatusMonitorInfo(str) emitted to signal some info of the
25        monitoring thread
26    """
27    vcsStatusMonitorData = pyqtSignal(list)
28    vcsStatusMonitorStatus = pyqtSignal(str, str)
29    vcsStatusMonitorInfo = pyqtSignal(str)
30
31    def __init__(self, interval, project, vcs, parent=None):
32        """
33        Constructor
34
35        @param interval new interval in seconds (integer)
36        @param project reference to the project object (Project)
37        @param vcs reference to the version control object
38        @param parent reference to the parent object (QObject)
39        """
40        super().__init__(parent)
41        self.setObjectName("VcsStatusMonitorThread")
42
43        self.setTerminationEnabled(True)
44
45        self.projectDir = project.getProjectPath()
46        self.project = project
47        self.vcs = vcs
48
49        self.interval = interval
50        self.autoUpdate = False
51
52        self.statusList = []
53        self.reportedStates = {}
54        self.shouldUpdate = False
55
56        self.monitorMutex = QMutex()
57        self.monitorCondition = QWaitCondition()
58        self.__stopIt = False
59
60    def run(self):
61        """
62        Public method implementing the tasks action.
63        """
64        while not self.__stopIt:
65            # perform the checking task
66            self.statusList = []
67            self.vcsStatusMonitorStatus.emit(
68                "wait", QCoreApplication.translate(
69                    "VcsStatusMonitorThread", "Waiting for lock"))
70            try:
71                locked = self.vcs.vcsExecutionMutex.tryLock(5000)
72            except TypeError:
73                locked = self.vcs.vcsExecutionMutex.tryLock()
74            if locked:
75                try:
76                    self.vcsStatusMonitorStatus.emit(
77                        "op", QCoreApplication.translate(
78                            "VcsStatusMonitorThread",
79                            "Checking repository status"))
80                    res, statusMsg = self._performMonitor()
81                    infoMsg = self._getInfo()
82                finally:
83                    self.vcs.vcsExecutionMutex.unlock()
84                if res:
85                    status = "ok"
86                else:
87                    status = "nok"
88                self.vcsStatusMonitorStatus.emit(
89                    "send", QCoreApplication.translate(
90                        "VcsStatusMonitorThread", "Sending data"))
91                self.vcsStatusMonitorData.emit(self.statusList)
92                self.vcsStatusMonitorStatus.emit(status, statusMsg)
93                self.vcsStatusMonitorInfo.emit(infoMsg)
94            else:
95                self.vcsStatusMonitorStatus.emit(
96                    "timeout", QCoreApplication.translate(
97                        "VcsStatusMonitorThread",
98                        "Timed out waiting for lock"))
99                self.vcsStatusMonitorInfo.emit("")
100
101            if self.autoUpdate and self.shouldUpdate:
102                self.vcs.vcsUpdate(self.projectDir, True)
103                continue    # check again
104                self.shouldUpdate = False
105
106            # wait until interval has expired checking for a stop condition
107            self.monitorMutex.lock()
108            if not self.__stopIt:
109                self.monitorCondition.wait(
110                    self.monitorMutex, self.interval * 1000)
111            self.monitorMutex.unlock()
112
113        self._shutdown()
114        self.exit()
115
116    def setInterval(self, interval):
117        """
118        Public method to change the monitor interval.
119
120        @param interval new interval in seconds (integer)
121        """
122        locked = self.monitorMutex.tryLock()
123        self.interval = interval
124        self.monitorCondition.wakeAll()
125        if locked:
126            self.monitorMutex.unlock()
127
128    def getInterval(self):
129        """
130        Public method to get the monitor interval.
131
132        @return interval in seconds (integer)
133        """
134        return self.interval
135
136    def setAutoUpdate(self, auto):
137        """
138        Public method to enable the auto update function.
139
140        @param auto status of the auto update function (boolean)
141        """
142        self.autoUpdate = auto
143
144    def getAutoUpdate(self):
145        """
146        Public method to retrieve the status of the auto update function.
147
148        @return status of the auto update function (boolean)
149        """
150        return self.autoUpdate
151
152    def checkStatus(self):
153        """
154        Public method to wake up the status monitor thread.
155        """
156        locked = self.monitorMutex.tryLock()
157        self.monitorCondition.wakeAll()
158        if locked:
159            self.monitorMutex.unlock()
160
161    def stop(self):
162        """
163        Public method to stop the monitor thread.
164        """
165        locked = self.monitorMutex.tryLock()
166        self.__stopIt = True
167        self.monitorCondition.wakeAll()
168        if locked:
169            self.monitorMutex.unlock()
170
171    def clearCachedState(self, name):
172        """
173        Public method to clear the cached VCS state of a file/directory.
174
175        @param name name of the entry to be cleared (string)
176        """
177        key = self.project.getRelativePath(name)
178        with contextlib.suppress(KeyError):
179            del self.reportedStates[key]
180
181    def _performMonitor(self):
182        """
183        Protected method implementing the real monitoring action.
184
185        This method must be overridden and populate the statusList member
186        variable with a list of strings giving the status in the first column
187        and the path relative to the project directory starting with the
188        third column. The allowed status flags are:
189        <ul>
190            <li>"A" path was added but not yet comitted</li>
191            <li>"M" path has local changes</li>
192            <li>"O" path was removed</li>
193            <li>"R" path was deleted and then re-added</li>
194            <li>"U" path needs an update</li>
195            <li>"Z" path contains a conflict</li>
196            <li>" " path is back at normal</li>
197        </ul>
198
199        @return tuple of flag indicating successful operation (boolean) and
200            a status message in case of non successful operation (string)
201        @exception RuntimeError to indicate that this method must be
202            implemented by a subclass
203        """
204        raise RuntimeError('Not implemented')
205
206        return ()
207
208    def _getInfo(self):
209        """
210        Protected method implementing the real info action.
211
212        This method should be overridden and create a short info message to be
213        shown in the main window status bar right next to the status indicator.
214
215        @return short info message
216        @rtype str
217        """
218        return ""
219
220    def _shutdown(self):
221        """
222        Protected method performing shutdown actions.
223
224        The default implementation does nothing.
225        """
226        pass
227