Skip to content

Commit

Permalink
Merge pull request #308 from austinmatherne-wk/247-arellegui-fails-to…
Browse files Browse the repository at this point in the history
…-download-update

Fix Updater module
  • Loading branch information
stevenbronson-wk committed Sep 1, 2022
2 parents 8e84fac + 94e5c75 commit d9fe631
Show file tree
Hide file tree
Showing 6 changed files with 486 additions and 89 deletions.
9 changes: 5 additions & 4 deletions arelle/Cntlr.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class Cntlr:
True if a system platform clipboard is implemented on current platform
.. attribute:: updateURL
URL string of application download file (on arelle.org server). Usually redirected to latest released application installable module.
URL string of application download file (on arelle.org) or None if update installs not supported by platform.
"""
__version__ = "1.6.0"
Expand All @@ -109,6 +109,7 @@ def __init__(self, hasGui=False, logFileName=None, logFileMode=None, logFileEnco
self.isCGI = False
self.systemWordSize = int(round(math.log(sys.maxsize, 2)) + 1) # e.g., 32 or 64
self.uiLangDir = "ltr"
self.updateURL = None

# sys.setrecursionlimit(10000) # 1000 default exceeded in some inline documents

Expand Down Expand Up @@ -166,7 +167,7 @@ def __init__(self, hasGui=False, logFileName=None, logFileMode=None, logFileEnco
# note that cache is in ~/Library/Caches/Arelle
self.contextMenuClick = "<Button-2>"
self.hasClipboard = hasGui # clipboard always only if Gui (not command line mode)
self.updateURL = "http://arelle.org/download/1005"
self.updateURL = "https://arelle.org/download/1005"
elif sys.platform.startswith("win"):
self.isMac = False
self.isMSW = True
Expand All @@ -192,9 +193,9 @@ def __init__(self, hasGui=False, logFileName=None, logFileMode=None, logFileEnco
self.hasClipboard = False
self.contextMenuClick = "<Button-3>"
if "64 bit" in sys.version:
self.updateURL = "http://arelle.org/download/1008"
self.updateURL = "https://arelle.org/download/1008"
else: # 32 bit
self.updateURL = "http://arelle.org/download/1011"
self.updateURL = "https://arelle.org/download/1011"
else: # Unix/Linux
self.isMac = False
self.isMSW = False
Expand Down
4 changes: 3 additions & 1 deletion arelle/CntlrWinMain.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
@author: Mark V Systems Limited
(c) Copyright 2010 Mark V Systems Limited, All rights reserved.
'''
from __future__ import annotations

from arelle import PythonUtil # define 2.x or 3.x string types
import os, sys, subprocess, pickle, time, locale, re, fnmatch, platform

Expand Down Expand Up @@ -1424,7 +1426,7 @@ def uiReloadViews(self, modelXbrl):
view.view()

# worker threads showStatus
def showStatus(self, message, clearAfter=None):
def showStatus(self, message: str, clearAfter: int | None = None) -> None:
self.uiThreadQueue.put((self.uiShowStatus, [message, clearAfter]))

# ui thread showStatus
Expand Down
223 changes: 148 additions & 75 deletions arelle/Updater.py
Original file line number Diff line number Diff line change
@@ -1,86 +1,159 @@
'''
"""
Created on May 30, 2010
@author: Mark V Systems Limited
(c) Copyright 2011 Mark V Systems Limited, All rights reserved.
'''
import tkinter.messagebox, webbrowser, os, threading

def checkForUpdates(cntlr):
if not cntlr.webCache.workOffline:
# check for updates in background
import threading
thread = threading.Thread(target=lambda c=cntlr: backgroundCheckForUpdates(c))
thread.daemon = True
thread.start()

def backgroundCheckForUpdates(cntlr):
actualUrl = None
"""
from __future__ import annotations

import gettext
import os
import subprocess
import sys
import threading
import tkinter.messagebox
import typing
from datetime import date

import regex

from arelle import Version

if typing.TYPE_CHECKING:
from arelle.CntlrWinMain import CntlrWinMain

_ = gettext.gettext

_MESSAGE_HEADER = "arelle\u2122 - Updater"
_ISO_DATE_PATTERN = regex.compile(
r"(?P<date>(?P<year>[0-9]{4})-(?P<month>0[1-9]|1[0-2])-(?P<day>0[1-9]|[12][0-9]|3[01]))"
)


def checkForUpdates(cntlr: CntlrWinMain) -> None:
thread = threading.Thread(daemon=True, target=lambda c=cntlr: _checkForUpdates(c))
thread.start()


def _checkForUpdates(cntlr: CntlrWinMain) -> None:
if cntlr.updateURL is None:
_showInfo(
cntlr,
_(
"""
Operating system not supported by update checker.
Please go to arelle.org to check for updates.
"""
),
)
return
if cntlr.webCache.workOffline:
_showInfo(cntlr, _("Disable offline mode to check for updates."))
return
cntlr.showStatus(_("Checking for updates to Arelle"))
try:
attachmentFileName = cntlr.webCache.getAttachmentFilename(cntlr.updateURL)
if attachmentFileName:
cntlr.showStatus("") # clear web loading status entry
cntlr.uiThreadQueue.put((checkUpdateUrl, [cntlr, attachmentFileName]))
except:
pass
cntlr.showStatus("") # clear web loading status entry

def checkUpdateUrl(cntlr, attachmentFileName):
# get latest header file
except RuntimeError as e:
_showWarning(
cntlr,
_("Failed to check for updates. URL {0}, {1}").format(cntlr.updateURL, e),
)
return
finally:
cntlr.showStatus("") # clear web loading status entry
cntlr.uiThreadQueue.put((_checkUpdateUrl, [cntlr, attachmentFileName]))


def _checkUpdateUrl(cntlr: CntlrWinMain, attachmentFileName: str) -> None:
filename = os.path.basename(attachmentFileName)
try:
from arelle import WebCache, Version
filename = os.path.basename(attachmentFileName)
if filename and "-20" in filename:
i = filename.index("-20") + 1
filenameDate = filename[i:i+10]
versionDate = Version.version[0:10]
if filenameDate > versionDate:
# newer
reply = tkinter.messagebox.askyesnocancel(
_("arelle\u2122 - Updater"),
_("Update {0} is available, running version is {1}. \n\nDownload now? \n\n(Arelle will exit before installing.)").format(
filenameDate, versionDate),
parent=cntlr.parent)
if reply is None:
return False
if reply:
thread = threading.Thread(target=lambda u=actualUrl: backgroundDownload(cntlr, u))
thread.daemon = True
thread.start()
else:
if filenameDate < versionDate:
msg = _("Arelle running version, {0}, is newer than the downloadable version, {1}.").format(
versionDate, filenameDate)
else:
msg = _("Arelle running version, {0}, is the same as the downloadable version.").format(
versionDate)
tkinter.messagebox.showwarning(_("arelle\u2122 - Updater"), msg, parent=cntlr.parent)

except:
pass

return

def backgroundDownload(cntlr, url):
filepathtmp = cntlr.webCache.getfilename(cntlr.updateURL, reload=True)
cntlr.modelManager.showStatus(_("Download ompleted"), 5000)
filepath = os.path.join(os.path.dirname(filepathtmp), os.path.basename(url))
os.rename(filepathtmp, filepath)
cntlr.uiThreadQueue.put((install, [cntlr,filepath]))

def install(cntlr,filepath):
import sys
if sys.platform.startswith("win"):
os.startfile(filepath)
currentVersion = _parseVersion(Version.version)
except ValueError:
_showWarning(cntlr, _("Unable to determine current version of Arelle."))
return
try:
updateVersion = _parseVersion(filename)
except ValueError:
_showWarning(cntlr, _("Unable to determine version of Arelle update."))
return
if updateVersion > currentVersion:
reply = tkinter.messagebox.askokcancel(
_(_MESSAGE_HEADER),
_(
"""
Update {0} is available, current version is {1}.
Download now?
(Arelle will exit before installing.)
"""
).format(updateVersion, currentVersion),
parent=cntlr.parent,
)
if reply:
_backgroundDownload(cntlr, attachmentFileName)
elif updateVersion < currentVersion:
_showInfo(
cntlr,
_("Current Arelle version {0} is newer than update {1}.").format(
currentVersion, updateVersion
),
)
else:
if sys.platform in ("darwin", "macos"):
command = 'open'
else: # linux/unix
command = 'xdg-open'
_showInfo(
cntlr,
_("Arelle is already running the latest version {0}.").format(
currentVersion
),
)


def _parseVersion(versionStr: str) -> date:
versionDateMatch = _ISO_DATE_PATTERN.search(versionStr)
if versionDateMatch is None:
raise ValueError(f"Unable to parse version date from {versionStr}")
return date.fromisoformat(versionDateMatch.group("date"))


def _backgroundDownload(cntlr: CntlrWinMain, attachmentFileName: str) -> None:
thread = threading.Thread(
daemon=True, target=lambda u=attachmentFileName: _download(cntlr, u)
)
thread.start()


def _download(cntlr: CntlrWinMain, url: str) -> None:
filepathTmp = cntlr.webCache.getfilename(cntlr.updateURL, reload=True)
if not filepathTmp:
_showWarning(cntlr, _("Failed to download update."))
return
cntlr.showStatus(_("Download completed"), 5000)
filepath = os.path.join(os.path.dirname(filepathTmp), os.path.basename(url))
try:
os.rename(filepathTmp, filepath)
except OSError:
_showWarning(cntlr, _("Failed to process update."))
return
cntlr.uiThreadQueue.put((_install, [cntlr, filepath]))


def _install(cntlr: CntlrWinMain, filepath: str) -> None:
if sys.platform == "win32":
os.startfile(filepath)
elif sys.platform == "darwin":
try:
import subprocess
subprocess.Popen([command,filepath])
except:
pass
subprocess.Popen(["open", filepath])
except (OSError, subprocess.SubprocessError):
_showWarning(cntlr, _("Failed to start updated Arelle instance."))
return
else:
raise RuntimeError("Tried to install update on unsupported platform.")
cntlr.uiThreadQueue.put((cntlr.quit, []))


def _showInfo(cntlr: CntlrWinMain, msg: str) -> None:
tkinter.messagebox.showinfo(_(_MESSAGE_HEADER), msg, parent=cntlr.parent)


def _showWarning(cntlr: CntlrWinMain, msg: str) -> None:
tkinter.messagebox.showwarning(_(_MESSAGE_HEADER), msg, parent=cntlr.parent)
21 changes: 12 additions & 9 deletions arelle/WebCache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
e.g., User-Agent: Sample Company Name AdminContact@<sample company domain>.com
'''
from __future__ import annotations

import os, posixpath, sys, re, shutil, time, calendar, io, json, logging, shutil, cgi, zlib
if sys.version[0] >= '3':
from urllib.parse import quote, unquote
Expand Down Expand Up @@ -328,7 +330,10 @@ def cacheFilepathToUrl(self, cacheFilepath):
.sub(lambda c: chr( int(c.group(0)[1:]) ), # remove ^nnn encoding
urlpart) for urlpart in urlparts)

def getfilename(self, url, base=None, reload=False, checkModifiedTime=False, normalize=False, filenameOnly=False):
def getfilename(
self, url: str | None, base: str | None = None,
reload: bool = False, checkModifiedTime: bool = False,
normalize: bool = False, filenameOnly: bool = False) -> str | None:
if url is None:
return url
if base is not None or normalize:
Expand Down Expand Up @@ -641,14 +646,12 @@ def geturl(self, url): # get the url that the argument url redirects or resolve
pass
return None

def getAttachmentFilename(self, url): # get the filename attachment from the header
if url and isHttpUrl(url):
try:
fp = self.opener.open(url, timeout=self.timeout)
return cgi.parse_header(fp.headers.get("Content-Disposition"))[1]["filename"]
except Exception:
pass
return None
def getAttachmentFilename(self, url: str) -> str: # get the filename attachment from the header
try:
fp = self.opener.open(url, timeout=self.timeout)
return cgi.parse_header(fp.headers.get("Content-Disposition"))[1]["filename"]
except (URLError, IndexError, KeyError) as e:
raise RuntimeError(f"Unable to to retrieve filename from headers for {url}") from e

def retrieve(self, url, filename=None, filestream=None, reporthook=None, data=None):
# return filename, headers (in dict), initial file bytes (to detect logon requests)
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ ignore_errors = true
[[tool.mypy.overrides]]
module = [
'arelle.plugin.validate.ESEF.*',
'arelle.Updater',
]
ignore_errors = false
strict = true

0 comments on commit d9fe631

Please sign in to comment.