diff --git a/requirements.txt b/requirements.txt index 82557f7..5da8e63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,6 @@ pyinstaller==3.5.0 wxpython==4.0.6 pyxform==0.15.0 +requests==2.22.0 +packaging==19.1 +markdown2==2.3.8 \ No newline at end of file diff --git a/src/main.py b/src/main.py index fd6dd75..5c5a255 100755 --- a/src/main.py +++ b/src/main.py @@ -10,10 +10,14 @@ import wx import worker +import update_checker + +import requests +from packaging import version +import threading # TODO pull out all strings # TODO why is the first button selected - TITLE = 'ODK XLSForm Offline' VERSION = 'v1.11.0' @@ -24,6 +28,8 @@ MAIN_WINDOW_HEIGHT = 620 ABOUT_WINDOW_WIDTH = 360 ABOUT_WINDOW_HEIGHT = 365 +UPDATE_WINDOW_WIDTH = 360 +UPDATE_WINDOW_HEIGHT = 365 MAX_PATH_LENGTH = 45 HEADER_SPACER = 6 CHOOSE_BORDER = 5 @@ -35,6 +41,8 @@ MAIN_WINDOW_HEIGHT = 750 ABOUT_WINDOW_WIDTH = 360 ABOUT_WINDOW_HEIGHT = 315 + UPDATE_WINDOW_WIDTH = 360 + UPDATE_WINDOW_HEIGHT = 315 MAX_PATH_LENGTH = 40 HEADER_SPACER = 0 CHOOSE_BORDER = 1 @@ -46,6 +54,29 @@ WORKER_PROGRESS_SLEEP = .05 +class UpdateAvailableFrame(wx.Frame): + def __init__(self, parent, update_info): + wx.Frame.__init__(self, parent, wx.ID_ANY, title='Update ' + update_info['latest_version'] + ' available', + size=(UPDATE_WINDOW_WIDTH, UPDATE_WINDOW_HEIGHT), + style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX) + html = HtmlWindow(self) + html.SetStandardFonts() + update_html_path = '' + if getattr(sys, 'frozen', False): + update_html_path = os.path.join(sys._MEIPASS, 'res', 'update.html') + else: + update_html_path = os.path.join('src', 'res', 'update.html') + + update_html_text = '' + with open(update_html_path, 'r') as file: + update_html_text = file.read() + + filled_update_html_text = update_html_text.replace('@desc', update_info['update_desc']).replace( + '@download_url', update_info['download_url']).replace('@download_name', update_info['download_name']) + + html.SetPage(filled_update_html_text) + + class AboutFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, wx.ID_ANY, title='About ' + TITLE, @@ -53,7 +84,7 @@ def __init__(self, parent): style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX) html = HtmlWindow(self) html.SetStandardFonts() - about = os.path.join('res','about.html') + about = os.path.join('res', 'about.html') if getattr(sys, 'frozen', False): html.LoadPage(os.path.join(sys._MEIPASS, about)) else: @@ -82,6 +113,7 @@ def __init__(self, parent, title): self.progress_thread = None self.about_window = None + self.update_window = None self.menu_bar = wx.MenuBar() self.file_menu = wx.Menu() @@ -223,6 +255,9 @@ def __init__(self, parent, title): self.Centre() self.Show() + update_checker.evt_update_check_done(self, self.check_update_and_show) + update_checker.UpdateChecker(self, VERSION).start() + @staticmethod def shorten_string(string, max_length): if len(string) >= max_length: @@ -291,6 +326,8 @@ def on_quit(self, e): self.Destroy() if self.about_window: self.about_window.Close() + if self.update_window: + self.update_window.Close() def on_about(self, e): if self.about_window: @@ -315,6 +352,15 @@ def on_progress(self, event): if self.result_thread is not None and self.result_thread.isAlive(): self.status_gauge.Pulse() + def check_update_and_show(self, event): + if self.update_window: + self.update_window.Close() + + if event.data['update_available']: + self.update_window = UpdateAvailableFrame(None, event.data) + self.update_window.Centre() + self.update_window.Show() + @staticmethod def is_java_installed(): @@ -335,7 +381,6 @@ def is_java_installed(): return java_version and java_regex.match(java_version) - def enable_ui(self, enable): # Turns UI elements on and off self.choose_file_button.Enable(enable) @@ -349,4 +394,4 @@ def enable_ui(self, enable): app = wx.App() app.SetAppName(TITLE) MainFrame(None, title=TITLE) - app.MainLoop() \ No newline at end of file + app.MainLoop() diff --git a/src/res/update.html b/src/res/update.html new file mode 100644 index 0000000..06f7de7 --- /dev/null +++ b/src/res/update.html @@ -0,0 +1,19 @@ + + + + + + + + +

+ @desc +
+
+
+ Download
+ @download_name +

+ + + \ No newline at end of file diff --git a/src/update_checker.py b/src/update_checker.py new file mode 100644 index 0000000..da06feb --- /dev/null +++ b/src/update_checker.py @@ -0,0 +1,68 @@ +import wx +import sys + +import requests +from packaging import version +import threading +import markdown2 + +GITHUB_RELEASES_API = "https://api.github.com/repos/opendatakit/xlsform-offline/releases/latest" + +OS_MAP = { + 'win32': 'windows', + 'darwin': 'macos' +} + +EVT_UPDATE_CHECKER = wx.NewId() + + +def evt_update_check_done(win, func): + '''Define Update Check Done Event.''' + win.Connect(-1, -1, EVT_UPDATE_CHECKER, func) + + +class UpdateCheckDoneEvent(wx.PyEvent): + '''Simple event to check for updates.''' + + def __init__(self, data): + '''Init Update Check Done Event.''' + wx.PyEvent.__init__(self) + self.SetEventType(EVT_UPDATE_CHECKER) + self.data = data + + +class UpdateChecker(threading.Thread): + def __init__(self, parent, current_version): + threading.Thread.__init__(self) + self._parent = parent + self._current_version = current_version + + def run(self): + try: + response = requests.get(GITHUB_RELEASES_API, timeout=30) + if response.status_code == 200: + json_response = response.json() + latest_version = json_response["tag_name"] + if version.parse(latest_version[1:]) > version.parse(self._current_version[1:]): + download_url = '' + download_name = '' + for asset in json_response['assets']: + if OS_MAP[sys.platform] in asset['name'].lower(): + download_url = asset['browser_download_url'] + download_name = asset['name'] + break + + wx.PostEvent(self._parent, UpdateCheckDoneEvent({ + 'update_available': True, + 'latest_version': latest_version, + 'download_url': download_url, + 'download_name': download_name, + 'update_desc': markdown2.markdown(json_response["body"].replace('\n', '
')) + })) + else: + wx.PostEvent(self._parent, UpdateCheckDoneEvent({ + 'update_available': False + })) + except Exception as ex: + print ex + pass