Skip to content

Commit 3c1c91b

Browse files
committed
Added git integration and updating dialog
1 parent dfb8db0 commit 3c1c91b

File tree

3 files changed

+234
-124
lines changed

3 files changed

+234
-124
lines changed

launcher/launcher.py

+209-112
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,30 @@
33
# Launcher for depthai_demo.py which provides updating capabilities
44

55
# 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
713

814
# Constants
915
SCRIPT_DIRECTORY=os.path.abspath(os.path.dirname(__file__))
1016
DEPTHAI_DEMO_SCRIPT='depthai_demo.py'
1117
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
1230

1331
# Create a logger
1432
class Logger(object):
@@ -28,137 +46,216 @@ def flush(self):
2846
sys.stdout = logger
2947
sys.stderr = logger
3048

31-
# PyQt5
32-
from PyQt5 import QtCore, QtGui, QtWidgets
33-
3449
qApp = QtWidgets.QApplication(['DepthAI Launcher'])
50+
# Set style
51+
#print(PyQt5.QtWidgets.QStyleFactory.keys())
52+
#qApp.setStyle('Fusion')
3553

36-
# Import splash screen
37-
import pyqt5_splash_screen
3854

3955
splashScreen = pyqt5_splash_screen.SplashScreen('splash2.png')
4056

57+
def closeSplash():
58+
splashScreen.hide()
4159

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?
6164
shouldUpdate = True
6265

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.\nCurrent version is 2.2.0.\nUpdate?", 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)
11169
if ret == QtWidgets.QMessageBox.Yes:
112-
shouldUpdate = True
70+
self.shouldUpdate = True
71+
return True
72+
else:
73+
self.shouldUpdate = False
74+
return False
11375

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:
11589
pass
116-
#TODO (themarpe) - update by fetch & checkout of depthai repo
11790

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'
126101

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:
129104

130-
# Print out stderr first
131-
sys.stderr.write(ret.stderr.decode())
105+
subprocess.run([gitExecutable, 'status'], cwd=pathToDepthaiRepository)
132106

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)
137115

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)
141118

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}')
144132

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))
148142

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))
153155

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
156161

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.\nCurrent version is {currentVersion}\nUpdate?'
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()
162258

163-
threading.Thread(target=workingThread).start()
259+
qApp.worker = Worker()
260+
qApp.worker.start()
164261
sys.exit(qApp.exec_())

0 commit comments

Comments
 (0)