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