3
3
# Launcher for depthai_demo.py which provides updating capabilities
4
4
5
5
# Standard imports
6
- import os , sys , subprocess , time , threading
6
+ import os , sys , subprocess , time , threading , argparse
7
+ # Import splash screen
8
+ import pyqt5_splash_screen
9
+ # Import version parser
10
+ from packaging import version
11
+ # PyQt5
12
+ from PyQt5 import QtCore , QtGui , QtWidgets
7
13
8
14
# Constants
9
15
SCRIPT_DIRECTORY = os .path .abspath (os .path .dirname (__file__ ))
10
16
DEPTHAI_DEMO_SCRIPT = 'depthai_demo.py'
11
17
DEPTHAI_INSTALL_REQUIREMENTS_SCRIPT = 'install_requirements.py'
18
+ DEFAULT_GIT_PATH = 'git'
19
+ DEPTHAI_REPOSITORY_NAME = 'depthai'
20
+ DEPTHAI_REMOTE_REPOSITORY_URL = 'https://github.com/luxonis/depthai.git'
21
+
22
+ # Parse arguments
23
+ parser = argparse .ArgumentParser ()
24
+ parser .add_argument ('-r' , '--repo' , help = 'Path to DepthAI Git repository' , default = f'{ SCRIPT_DIRECTORY } /{ DEPTHAI_REPOSITORY_NAME } ' )
25
+ parser .add_argument ('-g' , '--git' , help = 'Path to Git executable. Default \' git\' ' , default = DEFAULT_GIT_PATH )
26
+ args = parser .parse_args ()
27
+
28
+ pathToDepthaiRepository = args .repo
29
+ gitExecutable = args .git
12
30
13
31
# Create a logger
14
32
class Logger (object ):
@@ -28,137 +46,216 @@ def flush(self):
28
46
sys .stdout = logger
29
47
sys .stderr = logger
30
48
31
- # PyQt5
32
- from PyQt5 import QtCore , QtGui , QtWidgets
33
-
34
49
qApp = QtWidgets .QApplication (['DepthAI Launcher' ])
50
+ # Set style
51
+ #print(PyQt5.QtWidgets.QStyleFactory.keys())
52
+ #qApp.setStyle('Fusion')
35
53
36
- # Import splash screen
37
- import pyqt5_splash_screen
38
54
39
55
splashScreen = pyqt5_splash_screen .SplashScreen ('splash2.png' )
40
56
57
+ def closeSplash ():
58
+ splashScreen .hide ()
41
59
42
- def workingThread ():
43
-
44
- def closeSplash ():
45
- splashScreen .hide ()
46
-
47
- # # Create splash screen with splash2.png image
48
- # splashProcess = subprocess.Popen([sys.executable, f'{SCRIPT_DIRECTORY}/splash_screen.py', 'splash2.png', '0'], stdin=subprocess.PIPE, text=True)
49
- # splashClosed = False
50
- # def closeSplash():
51
- # global splashClosed
52
- # if not splashClosed:
53
- # splashClosed = True
54
- # splashProcess.terminate()
55
- # #splashProcess.communicate(input='a', timeout=1.0)
56
-
57
- # Defaults
58
- depthaiRepositoryName = 'depthai'
59
- pathToDepthaiRemoteRepository = 'https://github.com/luxonis/depthai.git'
60
- pathToDepthaiRepository = f'{ SCRIPT_DIRECTORY } /{ depthaiRepositoryName } '
60
+ class Worker (QtCore .QThread ):
61
+ signalUpdateQuestion = QtCore .pyqtSignal (str , str )
62
+ sigInfo = QtCore .pyqtSignal (str , str )
63
+ # Should update if a new version is available?
61
64
shouldUpdate = True
62
65
63
- # If path to repository is specified, use that instead
64
- # TODO(themarpe) - Expand possible arguments
65
- if len (sys .argv ) > 1 :
66
- pathToDepthaiRepository = sys .argv [1 ]
67
-
68
- # Check if repository exists
69
- from dulwich .repo import Repo
70
- from dulwich import porcelain
71
- import dulwich
72
-
73
- depthaiRepo = None
74
- try :
75
- depthaiRepo = Repo (pathToDepthaiRepository )
76
- except dulwich .errors .NotGitRepository as ex :
77
- launcher .updateSplashMessage ('DepthAI repository' )
78
- launcher .enableHeartbeat (True )
79
-
80
- # Repository doesn't exists, clone first
81
- depthaiRepo = porcelain .clone (pathToDepthaiRemoteRepository , depthaiRepositoryName )
82
-
83
- # # Check if an update is available
84
- # # TODO(themarpe)
85
- # from dulwich.repo import Repo
86
- # import dulwich.porcelain
87
- #
88
- # r = Repo(pathToDepthaiRepository)
89
- # # Fetch tags first
90
- # dulwich.porcelain.fetch(r)
91
- # # Get current tag
92
- # currentTag = dulwich.porcelain.describe(r)
93
- #
94
- # tags = r.refs.as_dict("refs/tags".encode())
95
- #
96
- # print(f'Current tag: {dulwich.porcelain.describe(r)}')
97
- #
98
- # ver = semver.VersionInfo.parse(currentTag)
99
- # print(ver)
100
- # print(tags)
101
- newVersionAvailable = True
102
-
103
- # If a new version is available, ask to update
104
- if newVersionAvailable == True :
105
- # Try asking user whether to update
106
- # import semver
107
- # Update by default
108
- print ("Message Box in Console" )
109
- ret = QtWidgets .QMessageBox .question (None , "Update Available" , "Version 2.3.1 is available.\n Current version is 2.2.0.\n Update?" , QtWidgets .QMessageBox .Yes | QtWidgets .QMessageBox .No , QtWidgets .QMessageBox .Yes )
110
- print (f'Should update? { shouldUpdate } ' )
66
+ @QtCore .pyqtSlot (str ,str )
67
+ def updateQuestion (self , title , message ):
68
+ ret = QtWidgets .QMessageBox .question (splashScreen , title , message , QtWidgets .QMessageBox .Yes | QtWidgets .QMessageBox .No , QtWidgets .QMessageBox .Yes )
111
69
if ret == QtWidgets .QMessageBox .Yes :
112
- shouldUpdate = True
70
+ self .shouldUpdate = True
71
+ return True
72
+ else :
73
+ self .shouldUpdate = False
74
+ return False
113
75
114
- if shouldUpdate == True :
76
+ @QtCore .pyqtSlot (str ,str )
77
+ def showInformation (self , title , message ):
78
+ QtWidgets .QMessageBox .information (splashScreen , title , message )
79
+
80
+ def __init__ (self , parent = None ):
81
+ QtCore .QThread .__init__ (self , parent )
82
+ self .signalUpdateQuestion [str , str ].connect (self .updateQuestion , QtCore .Qt .BlockingQueuedConnection )
83
+ self .sigInfo [str , str ].connect (self .showInformation , QtCore .Qt .BlockingQueuedConnection )
84
+ def __del__ (self ):
85
+ self .exiting = True
86
+ try :
87
+ self .wait ()
88
+ except :
115
89
pass
116
- #TODO (themarpe) - update by fetch & checkout of depthai repo
117
90
118
- # Set to quit splash screen a little after subprocess is ran
119
- skipSplashQuitFirstTime = False
120
- def removeSplash ():
121
- time .sleep (2.5 )
122
- if not skipSplashQuitFirstTime :
123
- closeSplash ()
124
- quitThread = threading .Thread (target = removeSplash )
125
- quitThread .start ()
91
+ def run (self ):
92
+
93
+ try :
94
+
95
+ # New version available?
96
+ newVersionAvailable = False
97
+ # Current version name
98
+ currentVersion = 'Unknown'
99
+ newVersion = 'Unknown'
100
+ newVersionTag = 'vUnknown'
126
101
127
- # All ready, run the depthai_demo.py as a separate process
128
- ret = subprocess . run ([ sys . executable , f' { pathToDepthaiRepository } / { DEPTHAI_DEMO_SCRIPT } ' ], cwd = pathToDepthaiRepository , stderr = subprocess . PIPE )
102
+ # Check if repository exists
103
+ try :
129
104
130
- # Print out stderr first
131
- sys .stderr .write (ret .stderr .decode ())
105
+ subprocess .run ([gitExecutable , 'status' ], cwd = pathToDepthaiRepository )
132
106
133
- # Retry if failed by an ModuleNotFoundError, by installing the requirements
134
- if ret .returncode != 0 and 'ModuleNotFoundError' in str (ret .stderr ):
135
- skipSplashQuitFirstTime = True
136
- print (f'ModuleNotFoundError raised. Retrying by installing requirements first and restarting demo.' )
107
+ if os .path .isdir (pathToDepthaiRepository ) and subprocess .run ([gitExecutable , 'status' ], cwd = pathToDepthaiRepository ).returncode == 0 :
108
+ pass
109
+ else :
110
+ # DepthAI repo not available, clone first
111
+ splashScreen .updateSplashMessage ('Loading DepthAI Repository ...' )
112
+ splashScreen .enableHeartbeat (True )
113
+ # Repository doesn't exists, clone first
114
+ subprocess .check_call ([gitExecutable , 'clone' , DEPTHAI_REMOTE_REPOSITORY_URL , DEPTHAI_REPOSITORY_NAME ], cwd = SCRIPT_DIRECTORY )
137
115
138
- # present message of installing dependencies
139
- splashScreen .updateSplashMessage ('Loading DepthAI Dependencies ...' )
140
- splashScreen .enableHeartbeat (True )
116
+ # Fetch changes
117
+ subprocess .check_call ([gitExecutable , 'fetch' ], cwd = pathToDepthaiRepository )
141
118
142
- # Install requirements for depthai_demo.py
143
- subprocess .run ([sys .executable , f'{ pathToDepthaiRepository } /{ DEPTHAI_INSTALL_REQUIREMENTS_SCRIPT } ' ], cwd = pathToDepthaiRepository )
119
+ # Get all available versions
120
+ availableDepthAIVersions = []
121
+ proc = subprocess .Popen ([gitExecutable , 'tag' , '-l' ], cwd = pathToDepthaiRepository , stdout = subprocess .PIPE )
122
+ while True :
123
+ line = proc .stdout .readline ()
124
+ if not line :
125
+ break
126
+ # Check that the tag refers to DepthAI demo and not SDK
127
+ tag = line .rstrip ().decode ()
128
+ # Check that tag is actually a version
129
+ if type (version .parse (tag )) is version .Version :
130
+ availableDepthAIVersions .append (tag )
131
+ print (f'Available DepthAI versions: { availableDepthAIVersions } ' )
144
132
145
- # Remove message and animation
146
- splashScreen .updateSplashMessage ('' )
147
- splashScreen .enableHeartbeat (False )
133
+ # If any available versions
134
+ if len (availableDepthAIVersions ) > 0 :
135
+ # Get latest version
136
+ newVersionTag = availableDepthAIVersions [0 ]
137
+ newVersion = str (version .parse (newVersionTag ))
138
+ for ver in availableDepthAIVersions :
139
+ if version .parse (ver ) > version .parse (newVersionTag ):
140
+ newVersionTag = ver
141
+ newVersion = str (version .parse (ver ))
148
142
149
- quitThread .join ()
150
- skipSplashQuitFirstTime = False
151
- quitThread = threading .Thread (target = removeSplash )
152
- quitThread .start ()
143
+ # Check current tag
144
+ ret = subprocess .run ([gitExecutable , 'describe' , '--tags' ], cwd = pathToDepthaiRepository , stdout = subprocess .PIPE , check = True )
145
+ tag = ret .stdout .decode ()
146
+ # See if its DepthAI version (if not, then suggest to update)
147
+ if len (tag .split ('-sdk' )) == 1 :
148
+ splitTag = tag .split ('-' )[0 ]
149
+ splitTag = splitTag .split ('v' )
150
+ currentTag = splitTag [len (splitTag ) - 1 ]
151
+ currentVersion = 'Unknown'
152
+ if type (version .parse (currentTag )) is version .Version :
153
+ print (f'Current tag: { currentTag } , ver: { str (version .parse (currentTag ))} ' )
154
+ currentVersion = str (version .parse (currentTag ))
153
155
154
- # All ready, run the depthai_demo.py as a separate process
155
- subprocess .run ([sys .executable , f'{ pathToDepthaiRepository } /{ DEPTHAI_DEMO_SCRIPT } ' ], cwd = pathToDepthaiRepository )
156
+ # Check if latest version is newer than current
157
+ if version .parse (newVersionTag ) > version .parse (currentTag ):
158
+ newVersionAvailable = True
159
+ else :
160
+ newVersionAvailable = False
156
161
157
- # At the end quit anyway
158
- closeSplash ()
159
- quitThread .join ()
160
- splashScreen .close ()
161
- qApp .exit ()
162
+ else :
163
+ newVersionAvailable = True
164
+ else :
165
+ newVersionAvailable = True
166
+
167
+ # If a new version is available, ask to update
168
+ if newVersionAvailable == True :
169
+ # Ask user whether to update
170
+ # Update by default
171
+ title = 'Update Available'
172
+ message = f'Version { newVersion } is available.\n Current version is { currentVersion } \n Update?'
173
+ print (f'Message Box ({ title } ): { message } ' )
174
+ self .signalUpdateQuestion .emit (title , message )
175
+
176
+ print (f'Should update? { self .shouldUpdate } ' )
177
+
178
+ if self .shouldUpdate == True :
179
+ # DepthAI repo not available, clone first
180
+ splashScreen .updateSplashMessage ('Updating DepthAI Repository ...' )
181
+ splashScreen .enableHeartbeat (True )
182
+ subprocess .run ([gitExecutable , 'checkout' , newVersionTag ], cwd = pathToDepthaiRepository , check = True )
183
+
184
+ # present message of installing dependencies
185
+ splashScreen .updateSplashMessage ('Loading DepthAI Dependencies ...' )
186
+ splashScreen .enableHeartbeat (True )
187
+
188
+ # Install requirements for depthai_demo.py
189
+ subprocess .run ([sys .executable , f'{ pathToDepthaiRepository } /{ DEPTHAI_INSTALL_REQUIREMENTS_SCRIPT } ' ], cwd = pathToDepthaiRepository )
190
+
191
+ except subprocess .CalledProcessError as ex :
192
+ # TODO(themarpe) - issue information box that Git isn't available
193
+ title = 'Git Error'
194
+ message = f'Git produced the following error: { ex } '
195
+ print (f'Message Box ({ title } ): { message } ' )
196
+ self .sigInfo .emit (title , message )
197
+ raise Exception ('Git Error' )
198
+ except FileNotFoundError as ex :
199
+ # TODO(themarpe) - issue information box that Git isn't available
200
+ title = 'No Git Available'
201
+ message = 'Git cannot be found in the path. Make sure Git is installed and added to the path, then try again'
202
+ print (f'Message Box ({ title } ): { message } ' )
203
+ self .sigInfo .emit (title , message )
204
+ raise Exception ('No Git Found' )
205
+
206
+ try :
207
+ # Set to quit splash screen a little after subprocess is ran
208
+ skipSplashQuitFirstTime = False
209
+ def removeSplash ():
210
+ time .sleep (2.5 )
211
+ if not skipSplashQuitFirstTime :
212
+ closeSplash ()
213
+ quitThread = threading .Thread (target = removeSplash )
214
+ quitThread .start ()
215
+
216
+ # All ready, run the depthai_demo.py as a separate process
217
+ ret = subprocess .run ([sys .executable , f'{ pathToDepthaiRepository } /{ DEPTHAI_DEMO_SCRIPT } ' ], cwd = pathToDepthaiRepository , stderr = subprocess .PIPE )
218
+
219
+ # Print out stderr first
220
+ sys .stderr .write (ret .stderr .decode ())
221
+
222
+ # Retry if failed by an ModuleNotFoundError, by installing the requirements
223
+ if ret .returncode != 0 and ('ModuleNotFoundError' in str (ret .stderr ) or 'Version mismatch' in str (ret .stderr )):
224
+ skipSplashQuitFirstTime = True
225
+ print (f'ModuleNotFoundError raised. Retrying by installing requirements first and restarting demo.' )
226
+
227
+ # present message of installing dependencies
228
+ splashScreen .updateSplashMessage ('Loading DepthAI Dependencies ...' )
229
+ splashScreen .enableHeartbeat (True )
230
+
231
+ # Install requirements for depthai_demo.py
232
+ subprocess .run ([sys .executable , f'{ pathToDepthaiRepository } /{ DEPTHAI_INSTALL_REQUIREMENTS_SCRIPT } ' ], cwd = pathToDepthaiRepository )
233
+
234
+ # Remove message and animation
235
+ splashScreen .updateSplashMessage ('' )
236
+ splashScreen .enableHeartbeat (False )
237
+
238
+ quitThread .join ()
239
+ skipSplashQuitFirstTime = False
240
+ quitThread = threading .Thread (target = removeSplash )
241
+ quitThread .start ()
242
+
243
+ # All ready, run the depthai_demo.py as a separate process
244
+ subprocess .run ([sys .executable , f'{ pathToDepthaiRepository } /{ DEPTHAI_DEMO_SCRIPT } ' ], cwd = pathToDepthaiRepository )
245
+ except :
246
+ pass
247
+ finally :
248
+ quitThread .join ()
249
+
250
+ except Exception as ex :
251
+ # Catch all for any kind of an error
252
+ print (f'Unknown error occured ({ ex } ), exiting...' )
253
+ finally :
254
+ # At the end quit anyway
255
+ closeSplash ()
256
+ splashScreen .close ()
257
+ qApp .exit ()
162
258
163
- threading .Thread (target = workingThread ).start ()
259
+ qApp .worker = Worker ()
260
+ qApp .worker .start ()
164
261
sys .exit (qApp .exec_ ())
0 commit comments