diff --git a/Components.py b/Components.py index f5ee7169..67dc5fd1 100644 --- a/Components.py +++ b/Components.py @@ -44,12 +44,7 @@ if builtins.__dict__.get('GUI_FLAG',True): import wx - if wx.VERSION_STRING < '2.9': - from wx.lib.pubsub import Publisher - elif wx.VERSION_STRING < '4.0': - from wx.lib.pubsub import pub as Publisher - else: - from pubsub import pub as Publisher + from pubsub import pub as Publisher import Editor diff --git a/Container.py b/Container.py index f590025c..c99a0e46 100644 --- a/Container.py +++ b/Container.py @@ -28,17 +28,11 @@ import wx import wx.lib.dragscroller import wx.lib.dialogs + from wx.lib.newevent import NewEvent _ = wx.GetTranslation - if wx.VERSION_STRING < '2.9': - from wx.lib.pubsub import Publisher - elif wx.VERSION_STRING < '4.0': - from wx.lib.pubsub import pub as Publisher - else: - from pubsub import pub as Publisher - - from wx.lib.newevent import NewEvent + from pubsub import pub as Publisher AttrUpdateEvent, EVT_ATTR_UPDATE = NewEvent() diff --git a/Decorators.py b/Decorators.py index 3018ea7d..df95154f 100644 --- a/Decorators.py +++ b/Decorators.py @@ -42,6 +42,8 @@ import wx.lib.agw.aui.framemanager AuiFloatingFrame = wx.lib.agw.aui.framemanager.AuiFloatingFrame + from pubsub import pub + def cond_decorator(flag, dec): def decorate(fn): return dec(fn) if flag else fn @@ -143,21 +145,27 @@ def wrapper(*args): return wrapper class ThreadWithReturnValue(threading.Thread): + """ https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/ + """ def __init__(self, *args, **kwargs): super(ThreadWithReturnValue, self).__init__(*args, **kwargs) - self._return = None + #self._return = None self.killed = False - + self._log = "" + self._status = "" + pub.subscribe(self.my_listener, "to_progress_diag") + def start(self): self.__run_backup = self.run self.run = self.__run - threading.Thread.start(self) + threading.Thread.start(self) + self._status = 'alive' def __run(self): sys.settrace(self.globaltrace) self._return = self.__run_backup() self.run = self.__run_backup - + def globaltrace(self, frame, event, arg): if event == 'call': return self.localtrace @@ -170,6 +178,22 @@ def localtrace(self, frame, event, arg): raise SystemExit() return self.localtrace + def my_listener(self, message, arg2=None): + """ + Listener function + """ + self._log = message + if arg2 == 'stop': + self.kill() + elif arg2 is not None: + self._status = arg2 + + def getStatus(self): + return self._status + + def getLog(self): + return self._log + def kill(self): self.killed = True @@ -201,13 +225,13 @@ def wrapper(*args): thread = ThreadWithReturnValue(target = f, args = args) thread.start() - while thread.isAlive(): + while thread.isAlive(): + if progress_dlg.WasCancelled() or progress_dlg.WasSkipped(): thread.kill() - break else: wx.MilliSleep(300) - progress_dlg.Pulse() + progress_dlg.Pulse(thread.getLog()) wx.SafeYield() progress_dlg.Destroy() diff --git a/DiagramNotebook.py b/DiagramNotebook.py index 02a4dcd1..00696462 100644 --- a/DiagramNotebook.py +++ b/DiagramNotebook.py @@ -25,12 +25,7 @@ import sys import builtins -# to send event -if wx.VERSION_STRING < '2.9': - from wx.lib.pubsub import Publisher as pub -else: - #from wx.lib.pubsub import pub - from pubsub import pub +from pubsub import pub import Container import Menu diff --git a/Menu.py b/Menu.py index 0a34a03c..dc2d0097 100644 --- a/Menu.py +++ b/Menu.py @@ -97,7 +97,8 @@ ID_HELP = wx.ID_HELP ID_API_HELP = wx.NewIdRef() ID_UPDATE_PIP_PACKAGE = wx.NewIdRef() -ID_UPDATE_FROM_GIT = wx.NewIdRef() +ID_UPDATE_FROM_GIT_ARCHIVE = wx.NewIdRef() +ID_UPDATE_FROM_GIT_REPO = wx.NewIdRef() ID_CONTACT = wx.NewIdRef() ID_ABOUT = wx.ID_ABOUT @@ -478,16 +479,20 @@ def __init__(self, parent): parent = parent.GetParent() + update_subMenu = wx.Menu() + helpModel = wx.MenuItem(self, ID_HELP, _('&DEVSimPy Help\tF1'), _("Help for DEVSimPy user")) - apiModel = wx.MenuItem(self, ID_API_HELP, _('&DEVSimPy API\tF2'), _("API for DEVSimPy user")) - updatePipPackage = wx.MenuItem(self, ID_UPDATE_PIP_PACKAGE, _('Update PIP Packages\tF3'), _("Update of dependant pip packages")) - updateFromGit = wx.MenuItem(self, ID_UPDATE_FROM_GIT, _('Update From Git'), _("Update of DEVSimPy from Git archive")) + apiModel = wx.MenuItem(self, ID_API_HELP, _('&DEVSimPy API\tF2'), _("API for DEVSimPy user")) + updatePipPackage = wx.MenuItem(self, ID_UPDATE_PIP_PACKAGE, _('Dependencies (PIP Packages)\tF3'), _("Update of dependant pip packages")) + updateFromGitArchive = wx.MenuItem(self, ID_UPDATE_FROM_GIT_ARCHIVE, _('From Git Archive (zip)'), _("Update of DEVSimPy from Git archive")) + updateFromGitRepo = wx.MenuItem(self, ID_UPDATE_FROM_GIT_REPO, _('From Git Repository (Pull)'), _("Update of DEVSimPy from its Git repo")) contactModel = wx.MenuItem(self, ID_CONTACT, _('Contact the Author...'), _("Send mail to the author")) aboutModel = wx.MenuItem(self, ID_ABOUT, _('About DEVSimPy...'), _("About DEVSimPy")) helpModel.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'search.png'))) updatePipPackage.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'update.png'))) - updateFromGit.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'update.png'))) + updateFromGitArchive.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'update.png'))) + updateFromGitRepo.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'update.png'))) apiModel.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'api.png'))) contactModel.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'mail.png'))) aboutModel.SetBitmap(wx.Bitmap(os.path.join(ICON_PATH,'info.png'))) @@ -497,8 +502,11 @@ def __init__(self, parent): AppendItem(helpModel) AppendItem(apiModel) self.AppendSeparator() - AppendItem(updatePipPackage) - AppendItem(updateFromGit) + Update_menu = AppendMenu(self, -1, _("Update"), update_subMenu) + Update_SubMenu0 = update_subMenu.Append(updatePipPackage) + update_subMenu.AppendSeparator() + Update_SubMenu1 = update_subMenu.Append(updateFromGitArchive) + Update_SubMenu2 = update_subMenu.Append(updateFromGitRepo) self.AppendSeparator() AppendItem(aboutModel) AppendItem(contactModel) @@ -506,7 +514,8 @@ def __init__(self, parent): parent.Bind(wx.EVT_MENU, parent.OnHelp, id=ID_HELP) parent.Bind(wx.EVT_MENU, parent.OnAPI, id=ID_API_HELP) parent.Bind(wx.EVT_MENU, parent.OnUpdatPiPPackage, id=ID_UPDATE_PIP_PACKAGE) - parent.Bind(wx.EVT_MENU, parent.OnUpdatFromGit, id=ID_UPDATE_FROM_GIT) + parent.Bind(wx.EVT_MENU, parent.OnUpdatFromGitRepo, id=ID_UPDATE_FROM_GIT_REPO) + parent.Bind(wx.EVT_MENU, parent.OnUpdatFromGitArchive, id=ID_UPDATE_FROM_GIT_ARCHIVE) parent.Bind(wx.EVT_MENU, parent.OnAbout, id=ID_ABOUT) parent.Bind(wx.EVT_MENU, parent.OnContact, id=ID_CONTACT) diff --git a/SimulationGUI.py b/SimulationGUI.py index 33e2711e..1b6c862a 100644 --- a/SimulationGUI.py +++ b/SimulationGUI.py @@ -27,12 +27,7 @@ import threading # to send event -if wx.VERSION_STRING < '2.9': - from wx.lib.pubsub import Publisher as pub -elif wx.VERSION_STRING < '4.0': - from wx.lib.pubsub import pub -else: - from pubsub import pub +from pubsub import pub from tempfile import gettempdir diff --git a/SpreadSheet.py b/SpreadSheet.py index 11f96a04..e1dfe4ca 100644 --- a/SpreadSheet.py +++ b/SpreadSheet.py @@ -26,12 +26,7 @@ from wx.lib import sheet # to send event -if wx.VERSION_STRING < '2.9': - from wx.lib.pubsub import Publisher -elif wx.VERSION_STRING < '4.0': - from wx.lib.pubsub import pub as Publisher -else: - from pubsub import pub as Publisher +from pubsub import pub as Publisher #from Container import * from PlotGUI import * @@ -90,7 +85,7 @@ def Populate(self, data): try: ### inform Frame that table us full for graph icon enabling Publisher.sendMessage("isfull", msg=self._full_flag) - except wx.lib.pubsub.core.topicargspecimpl.SenderMissingReqdMsgDataError as info: + except pubsub.pub.SenderMissingReqdMsgDataError as info: pass self.AutoSize() diff --git a/Utilities.py b/Utilities.py index 1db5b49e..ec873f54 100644 --- a/Utilities.py +++ b/Utilities.py @@ -35,13 +35,16 @@ import linecache import imp import tempfile +import pathlib +import shlex from copy import deepcopy +from datetime import datetime import gettext _ = gettext.gettext from itertools import combinations -from zipfile import ZipFile +from zipfile import ZipFile, ZIP_DEFLATED from io import StringIO if builtins.__dict__.get('GUI_FLAG',True): @@ -58,6 +61,8 @@ except ImportError: # if it's not there locally, try the wxPython lib. import wx.lib.agw.pybusyinfo as PBI + from pubsub import pub + ### for replaceAll import fileinput @@ -69,7 +74,7 @@ import pip import importlib -from subprocess import call, check_output, check_call, CalledProcessError +from subprocess import call, check_output, check_call, Popen, PIPE # Used for smooth (spectrum) try: @@ -194,7 +199,8 @@ def updatePiP(): if check_internet(): try: - check_call("python -m pip install --upgrade pip", shell=True) + command = "python -m pip install --upgrade pip" + run_command(command, "to_progress_diag") except Exception as ee: print(ee.output) return False @@ -210,6 +216,7 @@ def downloadFromURL(url): try: # downloading with request # download the file contents in binary format + pub.sendMessage("to_progress_diag", message=_(f"Download git archive from:\n{url}")) r = urllib.request.urlopen(url) except Exception as e: print(e) @@ -217,50 +224,125 @@ def downloadFromURL(url): else: if r.getcode() == 200: # 200 means a successful request - tempdir = tempfile.gettempdir() fn = os.path.join(tempdir, "DEVSimPy.zip") - # downloading with urllib + # downloading with urllib # Copy a network object to a local file + pub.sendMessage("to_progress_diag", message=_(f"Copy a network object to:\n{fn}")) urlretrieve(url, fn) - + pub.sendMessage("to_progress_diag", message=_(f"Copy done!")) return fn else: return None -def updateFromGit(): +def zipdir(path, ziph): + # ziph is zipfile handle + lenDirPath = len(path) + for root, dirs, files in os.walk(path): + for file in files: + filePath = os.path.join(root, file) + ziph.write(filePath , filePath[lenDirPath:]) + +def copy_dir(src, dst): + dst.mkdir(parents=True, exist_ok=True) + for item in os.listdir(src): + s = src / item + d = dst / item + if s.is_dir(): + copy_dir(s, d) + else: + shutil.copy2(str(s), str(d)) + +def updateFromGitRepo(): """ Updated DEVSimPy from Git with a zip (not with git command) """ - + import git + + try: + pub.sendMessage("to_progress_diag", message=_("Pull...")) + repo = git.Repo() + o = repo.remotes.origin + o.pull() + except Exception as e: + print(e) + return False + else: + pub.sendMessage("to_progress_diag", message=_("Done!")) + return True + +def updateFromGitArchive(): + """ Updated DEVSimPy from Git with a zip (not with git command) + """ + # specifying the zip file name fn = downloadFromURL("https://github.com/capocchi/DEVSimPy/archive/master.zip") if fn: - # opening the zip file in READ mode - with ZipFile(fn, 'r') as zip: - # printing all the contents of the zip file - #dlg = wx.RichMessageDialog(None, "Do you realy want to update DEVSimPy?\nAll files will be relaced and you cannot go backwards.", style=wx.YES_NO|wx.CENTER) - #txt = 'Name / Size / Date\n' - #txt +=' \n'.join([str(elem.filename)+'/'+str(elem.file_size)+'/'+str(elem.date_time) for elem in zip.infolist()]) - #dlg.ShowDetailedText(txt) - #if dlg.ShowModal() not in (wx.ID_NO, wx.ID_CANCEL): - - # extracting all the files - print('Extracting all the files now...') - #zip.extractall() - print('Done!') - #dlg.Destroy() - return True - #else: - # dlg.Destroy() - # return False + tempdir = tempfile.gettempdir() + now = datetime.now() # current date and time + + try: + ### make a backup of DEVSimPy sources to temp directory with the file DEVSimPy-backup-m_d_y + pub.sendMessage("to_progress_diag", message=_(f"Backup DEVSimPy in {tempdir} directory...")) + + zipf = ZipFile(os.path.join(tempdir,''.join(['DEVSimPy-backup-',now.strftime("%m_%d_%Y"),'.zip'])), 'w', ZIP_DEFLATED) + zipdir(os.getcwd(), zipf) + zipf.close() + except: + return False + else: + pub.sendMessage("to_progress_diag", message=_(f"Done!")) + + # opening the downloaded zip file in READ mode + with ZipFile(fn, 'a') as zip: + + # extracting all the files (simulate in order to wait if the user want to stop the process) + pub.sendMessage("to_progress_diag", message=_("Extracting all the files...")) + for elem in zip.infolist(): + time.sleep(0.1) + + p = pathlib.PurePosixPath(elem.filename) + pub.sendMessage("to_progress_diag", message=_(f"Extract...\n{p.relative_to('DEVSimPy-master')}")) + + ### effective extraction in temp directory + zip.extractall(tempdir) + + ### Copy the extracted files into the DEVSimPy folder. + pub.sendMessage("to_progress_diag", message=_(f"Copy...\n{p.relative_to('DEVSimPy-master')}")) + if platform.python_version() >= '3.8': + shutil.copytree(os.path.join(tempdir, 'DEVSimPy-master'), os.path.join(tempdir, 'test'), dirs_exist_ok=True) + else: + src = pathlib.Path(os.path.join(tempdir, 'DEVSimPy-master')) + dest = pathlib.Path(os.path.join(tempdir, 'test')) + copy_dir(src, dest) + + pub.sendMessage("to_progress_diag", message=_("Done!")) + + ### delete temporary zip file + os.remove(fn) + + return True + else: return False -def updatePackageWithPiP(): - """ Update all installed package using pip +def run_command(command, message=None): + """ run command and send a message for each output of the process using pubsub + """ + ### dynamic output of the process to progress diag using pubsub! + process = Popen(shlex.split(command), stdout=PIPE, stderr = PIPE, shell=True, encoding='utf-8') + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output and message: + pub.sendMessage(message, message=output.strip()) + process.poll() + +def updatePiPPackages(): + """ Update all pip packages that DEVSimPy depends. """ if updatePiP(): @@ -272,7 +354,9 @@ def updatePackageWithPiP(): command = "pip install --user --upgrade " + ' '.join(packages) try: - check_call(command, shell=True) + run_command(command, "to_progress_diag") + #check_call(command, shell=True) + except Exception as ee: print(ee.output) return False @@ -281,25 +365,34 @@ def updatePackageWithPiP(): else: return False -def install_and_import(package): +def install_and_import(package_to_install, package_to_import=None): """ Install and import the package """ - installed = install(package) - if installed and package not in sys.modules: globals()[package] = importlib.import_module(package) + ### if package to import is different to the package to install + if not package_to_import: + package_to_import = package_to_install + + installed = install(package_to_install, package_to_import) + if installed and package_to_import not in sys.modules: globals()[package_to_import] = importlib.import_module(package_to_import) return installed -def install(package): +def install(package_to_install, package_to_import=None): """ Install the package """ + + ### if package to import is different to the package to install + if not package_to_import: + package_to_import = package_to_install + try: - importlib.import_module(package) + importlib.import_module(package_to_import) installed = True except ImportError: - if pip.main(['search', package]) != 23: - dial = wx.MessageDialog(None, _('We find that the package %s is missing. \n\n Do you want to install him using pip?'%(package)), _('Install Package'), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + if pip.main(['search', package_to_install]) != 23: + dial = wx.MessageDialog(None, _('We find that the package %s is missing. \n\n Do you want to install him using pip?'%(package_to_install)), _('Package Manager'), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) if dial.ShowModal() == wx.ID_YES: - installed = not pip.main(['install', '--user', package]) + installed = not pip.main(['install', '--user', package_to_install]) dial.Destroy() else: installed = False diff --git a/devsimpy.py b/devsimpy.py index ad1e2bad..675ae4bd 100644 --- a/devsimpy.py +++ b/devsimpy.py @@ -102,16 +102,11 @@ old = True # to send event -if wx.VERSION_STRING < '2.9': - from wx.lib.pubsub import Publisher as pub -elif wx.VERSION_STRING < '4.0': - from wx.lib.pubsub import pub -else: - try: - from pubsub import pub - except Exception: - sys.stdout.write('Last version for Python2 is PyPubSub 3.3.0 \n pip install PyPubSub==3.3.0') - sys.exit() +try: + from pubsub import pub +except Exception: + sys.stdout.write('Last version for Python2 is PyPubSub 3.3.0 \n pip install PyPubSub==3.3.0') + sys.exit() if wx.VERSION_STRING >= '4.0': @@ -189,7 +184,7 @@ from PreferencesGUI import PreferencesGUI from pluginmanager import load_plugins, enable_plugin from which import which -from Utilities import GetUserConfigDir, install, install_and_import, updatePackageWithPiP, updateFromGit, NotificationMessage +from Utilities import GetUserConfigDir, install, install_and_import, updatePiPPackages, updateFromGitRepo, updateFromGitArchive, NotificationMessage from Decorators import redirectStdout, BuzyCursorNotification, ProgressNotification, cond_decorator from DetachedFrame import DetachedFrame from LibraryTree import LibraryTree @@ -2039,24 +2034,65 @@ def OnHelp(self, event): else: self.help.Display(os.path.join('html','toc.html')) - @cond_decorator(builtins.__dict__.get('GUI_FLAG',True), ProgressNotification(_("Update of dependant pip packages."))) def OnUpdatPiPPackage(self, event): - if updatePackageWithPiP(): - args = (_('Information'), _('All pip packages that DEVSimPy depends have been updated!')) + msg = _("Do you really want to update all pip packages that DEVSimPy depends?") + #info = "" + dlg = wx.RichMessageDialog(self, msg, _("Update Manager"), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + #dlg.ShowDetailedText(info) + if dlg.ShowModal() not in [wx.ID_NO, wx.ID_CANCEL]: + self.DoUpdatPiPPackage() + dlg.Destroy() + + @cond_decorator(builtins.__dict__.get('GUI_FLAG',True), ProgressNotification(_("Update of dependant pip packages"))) + def DoUpdatPiPPackage(self): + if updatePiPPackages(): + args = (_('Information'), _('All pip packages that DEVSimPy depends have been updated! \nYou need to restart DEVSimPy to take effect')) kwargs = {'parent':self, 'timeout':5} else: - args = (_('Error'), _('Pip packages update failed!\n Check the trace in background for more informations.')) + args = (_('Error'), _('Pip packages update failed! \nCheck the trace in background for more informations.')) kwargs = {'parent':self, 'flag':wx.ICON_ERROR, 'timeout':5} NotificationMessage(*args, **kwargs) + ### + def OnUpdatFromGitRepo(self, event): + msg = _("Do you really want to update DEVSimPy with a Pull Git request?") + #info = "" + dlg = wx.RichMessageDialog(self, msg, _("Update Manager"), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + #dlg.ShowDetailedText(info) + if dlg.ShowModal() not in [wx.ID_NO, wx.ID_CANCEL]: + if install_and_import('gitpython', 'git'): + self.DoUpdatFromGitRepo() + dlg.Destroy() + + @cond_decorator(builtins.__dict__.get('GUI_FLAG',True), ProgressNotification(_("DEVSimPy Update from git repo"))) + def DoUpdatFromGitRepo(self): + if updateFromGitRepo(): + args = (_('Information'), _('Update of DEVSimPy from git done! \nYou need to restart DEVSimPy to take effect.')) + kwargs = {'parent':self, 'timeout':5} + else: + args = (_('Error'), _('DEVSimPy update from git failed! \nCheck the trace in background for more informations.')) + kwargs = {'parent':self, 'flag':wx.ICON_ERROR, 'timeout':5} + + NotificationMessage(*args, **kwargs) + + ### + def OnUpdatFromGitArchive(self, event): + msg = _("Do you really want to update DEVSimPy from the last master archive? \nAll files will be replaced and you cannot go backwards.") + #info = "" + dlg = wx.RichMessageDialog(self, msg, _("Update Manager"), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + #dlg.ShowDetailedText(info) + if dlg.ShowModal() not in [wx.ID_NO, wx.ID_CANCEL]: + self.DoUpdatFromGitArchive() + dlg.Destroy() + @cond_decorator(builtins.__dict__.get('GUI_FLAG',True), ProgressNotification(_("DEVSimPy Update from git."))) - def OnUpdatFromGit(self, event): - if updateFromGit(): - args = (_('Information'), _('Update of DEVSimPy from git done!')) + def DoUpdatFromGitArchive(self): + if updateFromGitArchive(): + args = (_('Information'), _('Update of DEVSimPy from git archive done! \nYou need to restart DEVSimPy to take effect.')) kwargs = {'parent':self, 'timeout':5} else: - args = (_('Error'), _('DEVSimPy update from git failed!\n Check the trace in background for more informations.')) + args = (_('Error'), _('DEVSimPy update from git archive failed! \nCheck the trace in background for more informations.')) kwargs = {'parent':self, 'flag':wx.ICON_ERROR, 'timeout':5} NotificationMessage(*args, **kwargs)