diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9027c7495..979b70140 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Requirements -- Python 3.7 +- Python 3.11 - Git CLI installed - Python, pip and git are all available as command-line commands (add to the path if needed) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 28f4a6eb2..44bfc5c49 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -61,10 +61,11 @@ from gui.targetProfileEditor import TargetProfileEditor from gui.updateDialog import UpdateDialog from gui.utils.clipboard import fromClipboard +from gui.utils.progressHelper import ProgressHelper from service.character import Character from service.esi import Esi from service.fit import Fit -from service.port import IPortUser, Port +from service.port import Port from service.price import Price from service.settings import HTMLExportSettings, SettingsProvider from service.update import Update @@ -130,7 +131,6 @@ def stop(self): self.running = False -# todo: include IPortUser again class MainFrame(wx.Frame): __instance = None @@ -845,14 +845,15 @@ def fileImportDialog(self, event): style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE ) as dlg: if dlg.ShowModal() == wx.ID_OK: - self.progressDialog = wx.ProgressDialog( - _t("Importing fits"), - " " * 100, # set some arbitrary spacing to create width in window - parent=self, - style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL - ) - Port.importFitsThreaded(dlg.GetPaths(), self) - self.progressDialog.ShowModal() + # set some arbitrary spacing to create width in window + progress = ProgressHelper(message=" " * 100, callback=self._openAfterImport) + call = (Port.importFitsThreaded, [dlg.GetPaths(), progress], {}) + self.handleProgress( + title=_t("Importing fits"), + style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE, + call=call, + progress=progress, + errMsgLbl=_t("Import Error")) def backupToXml(self, event): """ Back up all fits to EVE XML file """ @@ -863,32 +864,30 @@ def backupToXml(self, event): _t("Save Backup As..."), wildcard=_t("EVE XML fitting file") + " (*.xml)|*.xml", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, - defaultFile=defaultFile, - ) as dlg: - if dlg.ShowModal() == wx.ID_OK: - filePath = dlg.GetPath() + defaultFile=defaultFile) as fileDlg: + if fileDlg.ShowModal() == wx.ID_OK: + filePath = fileDlg.GetPath() if '.' not in os.path.basename(filePath): filePath += ".xml" - sFit = Fit.getInstance() - max_ = sFit.countAllFits() - - self.progressDialog = wx.ProgressDialog( - _t("Backup fits"), - _t("Backing up {} fits to: {}").format(max_, filePath), - maximum=max_, - parent=self, - style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL - ) - Port.backupFits(filePath, self) - self.progressDialog.ShowModal() + fitAmount = Fit.getInstance().countAllFits() + progress = ProgressHelper( + message=_t("Backing up {} fits to: {}").format(fitAmount, filePath), + maximum=fitAmount + 1) + call = (Port.backupFits, [filePath, progress], {}) + self.handleProgress( + title=_t("Backup fits"), + style=wx.PD_CAN_ABORT | wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE, + call=call, + progress=progress, + errMsgLbl=_t("Export Error")) def exportHtml(self, event): from gui.utils.exportHtml import exportHtml + sFit = Fit.getInstance() settings = HTMLExportSettings.getInstance() - max_ = sFit.countAllFits() path = settings.getPath() if not os.path.isdir(os.path.dirname(path)): @@ -903,82 +902,44 @@ def exportHtml(self, event): ) as dlg: if dlg.ShowModal() == wx.ID_OK: return - - self.progressDialog = wx.ProgressDialog( - _t("Backup fits"), - _t("Generating HTML file at: {}").format(path), - maximum=max_, parent=self, - style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME) - - exportHtml.getInstance().refreshFittingHtml(True, self.backupCallback) - self.progressDialog.ShowModal() - - def backupCallback(self, info): - if info == -1: - self.closeProgressDialog() - else: - self.progressDialog.Update(info) - - def on_port_process_start(self): - # flag for progress dialog. - self.__progress_flag = True - - def on_port_processing(self, action, data=None): - # 2017/03/29 NOTE: implementation like interface - wx.CallAfter( - self._on_port_processing, action, data - ) - - return self.__progress_flag - - def _on_port_processing(self, action, data): - """ - While importing fits from file, the logic calls back to this function to - update progress bar to show activity. XML files can contain multiple - ships with multiple fits, whereas EFT cfg files contain many fits of - a single ship. When iterating through the files, we update the message - when we start a new file, and then Pulse the progress bar with every fit - that is processed. - - action : a flag that lets us know how to deal with :data - None: Pulse the progress bar - 1: Replace message with data - other: Close dialog and handle based on :action (-1 open fits, -2 display error) - """ - _message = None - if action & IPortUser.ID_ERROR: - self.closeProgressDialog() - _message = _t("Import Error") if action & IPortUser.PROCESS_IMPORT else _t("Export Error") + progress = ProgressHelper( + message=_t("Generating HTML file at: {}").format(path), + maximum=sFit.countAllFits() + 1) + call = (exportHtml.getInstance().refreshFittingHtml, [True, progress], {}) + self.handleProgress( + title=_t("Backup fits"), + style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME, + call=call, + progress=progress) + + def handleProgress(self, title, style, call, progress, errMsgLbl=None): + extraArgs = {} + if progress.maximum is not None: + extraArgs['maximum'] = progress.maximum + with wx.ProgressDialog( + parent=self, + title=title, + message=progress.message, + style=style, + **extraArgs + ) as dlg: + func, args, kwargs = call + func(*args, **kwargs) + while progress.working: + wx.MilliSleep(250) + wx.Yield() + (progress.dlgWorking, skip) = dlg.Update(progress.current, progress.message) + if progress.error and errMsgLbl: with wx.MessageDialog( self, _t("The following error was generated") + - f"\n\n{data}\n\n" + + f"\n\n{progress.error}\n\n" + _t("Be aware that already processed fits were not saved"), - _message, wx.OK | wx.ICON_ERROR + errMsgLbl, wx.OK | wx.ICON_ERROR ) as dlg: dlg.ShowModal() - return - - # data is str - if action & IPortUser.PROCESS_IMPORT: - if action & IPortUser.ID_PULSE: - _message = () - # update message - elif action & IPortUser.ID_UPDATE: # and data != self.progressDialog.message: - _message = data - - if _message is not None: - self.__progress_flag, _unuse = self.progressDialog.Pulse(_message) - else: - self.closeProgressDialog() - if action & IPortUser.ID_DONE: - self._openAfterImport(data) - # data is tuple(int, str) - elif action & IPortUser.PROCESS_EXPORT: - if action & IPortUser.ID_DONE: - self.closeProgressDialog() - else: - self.__progress_flag, _unuse = self.progressDialog.Update(data[0], data[1]) + elif progress.callback: + progress.callback(*progress.cbArgs) def _openAfterImport(self, fits): if len(fits) > 0: @@ -988,6 +949,8 @@ def _openAfterImport(self, fits): wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True)) else: fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name)) + # Show 100 fits max + fits = fits[:100] results = [] for fit in fits: results.append(( @@ -999,15 +962,6 @@ def _openAfterImport(self, fits): )) wx.PostEvent(self.shipBrowser, ImportSelected(fits=results, back=True)) - def closeProgressDialog(self): - # Windows apparently handles ProgressDialogs differently. We can - # simply Destroy it here, but for other platforms we must Close it - if 'wxMSW' in wx.PlatformInfo: - self.progressDialog.Destroy() - else: - self.progressDialog.EndModal(wx.ID_OK) - self.progressDialog.Close() - def importCharacter(self, event): """ Imports character XML file from EVE API """ with wx.FileDialog( diff --git a/gui/utils/exportHtml.py b/gui/utils/exportHtml.py index 6b4451460..95c27cfc6 100644 --- a/gui/utils/exportHtml.py +++ b/gui/utils/exportHtml.py @@ -26,20 +26,20 @@ def getInstance(cls): def __init__(self): self.thread = exportHtmlThread() - def refreshFittingHtml(self, force=False, callback=False): + def refreshFittingHtml(self, force=False, progress=None): settings = HTMLExportSettings.getInstance() if force or settings.getEnabled(): self.thread.stop() - self.thread = exportHtmlThread(callback) + self.thread = exportHtmlThread(progress) self.thread.start() class exportHtmlThread(threading.Thread): - def __init__(self, callback=False): + def __init__(self, progress=False): threading.Thread.__init__(self) self.name = "HTMLExport" - self.callback = callback + self.progress = progress self.stopRunning = False def stop(self): @@ -72,11 +72,13 @@ def run(self): pass except (KeyboardInterrupt, SystemExit): raise - except Exception as ex: - pass - - if self.callback: - wx.CallAfter(self.callback, -1) + except Exception as e: + if self.progress: + self.progress.error = f'{e}' + finally: + if self.progress: + self.progress.current += 1 + self.progress.workerWorking = False def generateFullHTML(self, sMkt, sFit, dnaUrl): """ Generate the complete HTML with styling and javascript """ @@ -234,8 +236,8 @@ def generateFullHTML(self, sMkt, sFit, dnaUrl): pyfalog.warning("Failed to export line") continue finally: - if self.callback: - wx.CallAfter(self.callback, count) + if self.progress: + self.progress.current = count count += 1 HTMLgroup += HTMLship + (' \n' ' \n') @@ -291,7 +293,7 @@ def generateMinimalHTML(self, sMkt, sFit, dnaUrl): pyfalog.error("Failed to export line") continue finally: - if self.callback: - wx.CallAfter(self.callback, count) + if self.progress: + self.progress.current = count count += 1 return HTML diff --git a/gui/utils/progressHelper.py b/gui/utils/progressHelper.py new file mode 100644 index 000000000..cda25b55d --- /dev/null +++ b/gui/utils/progressHelper.py @@ -0,0 +1,19 @@ +class ProgressHelper: + + def __init__(self, message, maximum=None, callback=None): + self.message = message + self.current = 0 + self.maximum = maximum + self.workerWorking = True + self.dlgWorking = True + self.error = None + self.callback = callback + self.cbArgs = [] + + @property + def working(self): + return self.workerWorking and self.dlgWorking and not self.error + + @property + def userCancelled(self): + return not self.dlgWorking diff --git a/service/port/__init__.py b/service/port/__init__.py index 2e884d214..e7b0967a1 100644 --- a/service/port/__init__.py +++ b/service/port/__init__.py @@ -1,2 +1,2 @@ from .efs import EfsPort -from .port import Port, IPortUser +from .port import Port diff --git a/service/port/eft.py b/service/port/eft.py index c0b75382e..68f63609b 100644 --- a/service/port/eft.py +++ b/service/port/eft.py @@ -38,7 +38,7 @@ from service.fit import Fit as svcFit from service.market import Market from service.port.muta import parseMutant, renderMutant -from service.port.shared import IPortUser, fetchItem, processing_notify +from service.port.shared import fetchItem pyfalog = Logger(__name__) @@ -365,7 +365,7 @@ def importEft(lines): return fit -def importEftCfg(shipname, lines, iportuser): +def importEftCfg(shipname, lines, progress): """Handle import from EFT config store file""" # Check if we have such ship in database, bail if we don't @@ -388,6 +388,8 @@ def importEftCfg(shipname, lines, iportuser): fitIndices.append(startPos) for i, startPos in enumerate(fitIndices): + if progress and progress.userCancelled: + return [] # End position is last file line if we're trying to get it for last fit, # or start position of next fit minus 1 endPos = len(lines) if i == len(fitIndices) - 1 else fitIndices[i + 1] @@ -558,11 +560,8 @@ def importEftCfg(shipname, lines, iportuser): # Append fit to list of fits fits.append(fitobj) - if iportuser: # NOTE: Send current processing status - processing_notify( - iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, - "%s:\n%s" % (fitobj.ship.name, fitobj.name) - ) + if progress: + progress.message = "%s:\n%s" % (fitobj.ship.name, fitobj.name) except (KeyboardInterrupt, SystemExit): raise diff --git a/service/port/port.py b/service/port/port.py index 9014ef1ce..6c175e2ab 100644 --- a/service/port/port.py +++ b/service/port/port.py @@ -38,7 +38,6 @@ isValidImplantImport, isValidBoosterImport) from service.port.esi import exportESI, importESI from service.port.multibuy import exportMultiBuy -from service.port.shared import IPortUser, UserCancelException, processing_notify from service.port.shipstats import exportFitStats from service.port.xml import importXml, exportXml from service.port.muta import parseMutant, parseDynamicItemString, fetchDynamicItem @@ -73,53 +72,48 @@ def is_tag_replace(cls): return cls.__tag_replace_flag @staticmethod - def backupFits(path, iportuser): + def backupFits(path, progress): pyfalog.debug("Starting backup fits thread.") - def backupFitsWorkerFunc(path, iportuser): - success = True + def backupFitsWorkerFunc(path, progress): try: - iportuser.on_port_process_start() - backedUpFits = Port.exportXml(svcFit.getInstance().getAllFits(), iportuser) - backupFile = open(path, "w", encoding="utf-8") - backupFile.write(backedUpFits) - backupFile.close() - except UserCancelException: - success = False - # Send done signal to GUI - # wx.CallAfter(callback, -1, "Done.") - flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE - iportuser.on_port_processing(IPortUser.PROCESS_EXPORT | flag, - "User canceled or some error occurrence." if not success else "Done.") + backedUpFits = Port.exportXml(svcFit.getInstance().getAllFits(), progress) + if backedUpFits: + progress.message = f'writing {path}' + backupFile = open(path, "w", encoding="utf-8") + backupFile.write(backedUpFits) + backupFile.close() + except (KeyboardInterrupt, SystemExit): + raise + except Exception as e: + progress.error = f'{e}' + finally: + progress.current += 1 + progress.workerWorking = False threading.Thread( target=backupFitsWorkerFunc, - args=(path, iportuser) + args=(path, progress) ).start() @staticmethod - def importFitsThreaded(paths, iportuser): - # type: (tuple, IPortUser) -> None + def importFitsThreaded(paths, progress): """ :param paths: fits data file path list. - :param iportuser: IPortUser implemented class. :rtype: None """ pyfalog.debug("Starting import fits thread.") - def importFitsFromFileWorkerFunc(paths, iportuser): - iportuser.on_port_process_start() - success, result = Port.importFitFromFiles(paths, iportuser) - flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE - iportuser.on_port_processing(IPortUser.PROCESS_IMPORT | flag, result) + def importFitsFromFileWorkerFunc(paths, progress): + Port.importFitFromFiles(paths, progress) threading.Thread( target=importFitsFromFileWorkerFunc, - args=(paths, iportuser) + args=(paths, progress) ).start() @staticmethod - def importFitFromFiles(paths, iportuser=None): + def importFitFromFiles(paths, progress=None): """ Imports fits from file(s). First processes all provided paths and stores assembled fits into a list. This allows us to call back to the GUI as @@ -132,11 +126,13 @@ def importFitFromFiles(paths, iportuser=None): fit_list = [] try: for path in paths: - if iportuser: # Pulse + if progress: + if progress and progress.userCancelled: + progress.workerWorking = False + return False, "Cancelled by user" msg = "Processing file:\n%s" % path + progress.message = msg pyfalog.debug(msg) - processing_notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg) - # wx.CallAfter(callback, 1, msg) with open(path, "rb") as file_: srcString = file_.read() @@ -148,15 +144,21 @@ def importFitFromFiles(paths, iportuser=None): continue try: - importType, makesNewFits, fitsImport = Port.importAuto(srcString, path, iportuser=iportuser) + importType, makesNewFits, fitsImport = Port.importAuto(srcString, path, progress=progress) fit_list += fitsImport except xml.parsers.expat.ExpatError: pyfalog.warning("Malformed XML in:\n{0}", path) - return False, "Malformed XML in %s" % path + msg = "Malformed XML in %s" % path + if progress: + progress.error = msg + progress.workerWorking = False + return False, msg - # IDs = [] # NOTE: what use for IDs? numFits = len(fit_list) for idx, fit in enumerate(fit_list): + if progress and progress.userCancelled: + progress.workerWorking = False + return False, "Cancelled by user" # Set some more fit attributes and save fit.character = sFit.character fit.damagePattern = sFit.pattern @@ -168,25 +170,23 @@ def importFitFromFiles(paths, iportuser=None): fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT db.save(fit) # IDs.append(fit.ID) - if iportuser: # Pulse + if progress: pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits) - processing_notify( - iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, - "Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name) - ) - - except UserCancelException: - return False, "Processing has been canceled.\n" + progress.message = "Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name) except (KeyboardInterrupt, SystemExit): raise except Exception as e: - pyfalog.critical("Unknown exception processing: {0}", path) + pyfalog.critical("Unknown exception processing: {0}", paths) pyfalog.critical(e) - # TypeError: not all arguments converted during string formatting -# return False, "Unknown Error while processing {0}" % path + if progress: + progress.error = f'{e}' + progress.workerWorking = False return False, "Unknown error while processing {}\n\n Error: {} {}".format( - path, type(e).__name__, getattr(e, 'message', '')) + paths, type(e).__name__, getattr(e, 'message', '')) + if progress: + progress.cbArgs.append(fit_list[:]) + progress.workerWorking = False return True, fit_list @staticmethod @@ -211,8 +211,7 @@ def importFitFromBuffer(bufferStr, activeFit=None): return importType, importData @classmethod - def importAuto(cls, string, path=None, activeFit=None, iportuser=None): - # type: (Port, str, str, object, IPortUser) -> object + def importAuto(cls, string, path=None, activeFit=None, progress=None): lines = string.splitlines() # Get first line and strip space symbols of it to avoid possible detection errors firstLine = '' @@ -224,7 +223,7 @@ def importAuto(cls, string, path=None, activeFit=None, iportuser=None): # If XML-style start of tag encountered, detect as XML if re.search(RE_XML_START, firstLine): - return "XML", True, cls.importXml(string, iportuser) + return "XML", True, cls.importXml(string, progress) # If JSON-style start, parse as CREST/JSON if firstLine[0] == '{': @@ -235,7 +234,7 @@ def importAuto(cls, string, path=None, activeFit=None, iportuser=None): if re.match("^\s*\[.*\]", firstLine) and path is not None: filename = os.path.split(path)[1] shipName = filename.rsplit('.')[0] - return "EFT Config", True, cls.importEftCfg(shipName, lines, iportuser) + return "EFT Config", True, cls.importEftCfg(shipName, lines, progress) # If no file is specified and there's comma between brackets, # consider that we have [ship, setup name] and detect like eft export format @@ -297,8 +296,8 @@ def importEft(lines): return importEft(lines) @staticmethod - def importEftCfg(shipname, lines, iportuser=None): - return importEftCfg(shipname, lines, iportuser) + def importEftCfg(shipname, lines, progress=None): + return importEftCfg(shipname, lines, progress) @classmethod def exportEft(cls, fit, options, callback=None): @@ -328,12 +327,12 @@ def exportESI(fit, exportCharges, exportImplants, exportBoosters, callback=None) # XML-related methods @staticmethod - def importXml(text, iportuser=None): - return importXml(text, iportuser) + def importXml(text, progress=None): + return importXml(text, progress) @staticmethod - def exportXml(fits, iportuser=None, callback=None): - return exportXml(fits, iportuser, callback=callback) + def exportXml(fits, progress=None, callback=None): + return exportXml(fits, progress, callback=callback) # Multibuy-related methods @staticmethod diff --git a/service/port/shared.py b/service/port/shared.py index 5f3988723..21022cb83 100644 --- a/service/port/shared.py +++ b/service/port/shared.py @@ -18,8 +18,6 @@ # ============================================================================= -from abc import ABCMeta, abstractmethod - from logbook import Logger from service.market import Market @@ -28,55 +26,6 @@ pyfalog = Logger(__name__) -class UserCancelException(Exception): - """when user cancel on port processing.""" - pass - - -class IPortUser(metaclass=ABCMeta): - - ID_PULSE = 1 - # Pulse the progress bar - ID_UPDATE = ID_PULSE << 1 - # Replace message with data: update messate - ID_DONE = ID_PULSE << 2 - # open fits: import process done - ID_ERROR = ID_PULSE << 3 - # display error: raise some error - - PROCESS_IMPORT = ID_PULSE << 4 - # means import process. - PROCESS_EXPORT = ID_PULSE << 5 - # means import process. - - @abstractmethod - def on_port_processing(self, action, data=None): - """ - While importing fits from file, the logic calls back to this function to - update progress bar to show activity. XML files can contain multiple - ships with multiple fits, whereas EFT cfg files contain many fits of - a single ship. When iterating through the files, we update the message - when we start a new file, and then Pulse the progress bar with every fit - that is processed. - - action : a flag that lets us know how to deal with :data - None: Pulse the progress bar - 1: Replace message with data - other: Close dialog and handle based on :action (-1 open fits, -2 display error) - """ - - """return: True is continue process, False is cancel.""" - pass - - def on_port_process_start(self): - pass - - -def processing_notify(iportuser, flag, data): - if not iportuser.on_port_processing(flag, data): - raise UserCancelException - - def fetchItem(typeName, eagerCat=False): sMkt = Market.getInstance() eager = 'group.category' if eagerCat else None diff --git a/service/port/xml.py b/service/port/xml.py index e2203a3b1..4fbb161e2 100644 --- a/service/port/xml.py +++ b/service/port/xml.py @@ -36,7 +36,7 @@ from service.fit import Fit as svcFit from service.market import Market from service.port.muta import renderMutantAttrs, parseMutantAttrs -from service.port.shared import IPortUser, processing_notify, fetchItem +from service.port.shared import fetchItem from utils.strfunctions import replace_ltgt, sequential_rep @@ -154,9 +154,8 @@ def _resolve_module(hardware, sMkt, b_localized): return item, mutaplasmidItem, mutatedAttrs -def importXml(text, iportuser): +def importXml(text, progress): from .port import Port - # type: (str, IPortUser) -> list[eos.saveddata.fit.Fit] sMkt = Market.getInstance() doc = xml.dom.minidom.parseString(text) # NOTE: @@ -169,6 +168,9 @@ def importXml(text, iportuser): failed = 0 for fitting in fittings: + if progress and progress.userCancelled: + return [] + try: fitobj = _resolve_ship(fitting, sMkt, b_localized) except (KeyboardInterrupt, SystemExit): @@ -272,16 +274,13 @@ def importXml(text, iportuser): fitobj.modules.append(module) fit_list.append(fitobj) - if iportuser: # NOTE: Send current processing status - processing_notify( - iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, - "Processing %s\n%s" % (fitobj.ship.name, fitobj.name) - ) + if progress: + progress.message = "Processing %s\n%s" % (fitobj.ship.name, fitobj.name) return fit_list -def exportXml(fits, iportuser, callback): +def exportXml(fits, progress, callback): doc = xml.dom.minidom.Document() fittings = doc.createElement("fittings") # fit count @@ -295,6 +294,12 @@ def addMutantAttributes(node, mutant): node.setAttribute("mutated_attrs", renderMutantAttrs(mutant)) for i, fit in enumerate(fits): + if progress: + if progress.userCancelled: + return None + processedFits = i + 1 + progress.current = processedFits + progress.message = "converting to xml (%s/%s) %s" % (processedFits, fit_count, fit.ship.name) try: fitting = doc.createElement("fitting") fitting.setAttribute("name", fit.name) @@ -387,12 +392,6 @@ def addMutantAttributes(node, mutant): except Exception as e: pyfalog.error("Failed on fitID: %d, message: %s" % e.message) continue - finally: - if iportuser: - processing_notify( - iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE, - (i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name)) - ) text = doc.toprettyxml() if callback: