diff --git a/eos/saveddata/ship.py b/eos/saveddata/ship.py index 1e3fd41f0..10b047c71 100644 --- a/eos/saveddata/ship.py +++ b/eos/saveddata/ship.py @@ -71,6 +71,11 @@ class Ship(ItemAttrShortcut, HandledItem): def item(self): return self.__item + @property + def name(self): + # NOTE: add name property + return self.__item.name + @property def itemModifiedAttributes(self): return self.__itemModifiedAttributes diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 7af37d875..60e2b7f26 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -72,7 +72,7 @@ from service.update import Update from eos.modifiedAttributeDict import ModifiedAttributeDict from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues from eos.db.saveddata.queries import getFit as db_getFit -from service.port import Port +from service.port import Port, IPortUser from service.settings import HTMLExportSettings from time import gmtime, strftime @@ -137,7 +137,7 @@ class OpenFitsThread(threading.Thread): wx.CallAfter(self.callback) -class MainFrame(wx.Frame): +class MainFrame(wx.Frame, IPortUser): __instance = None @classmethod @@ -419,8 +419,7 @@ class MainFrame(wx.Frame): format_ = dlg.GetFilterIndex() path = dlg.GetPath() if format_ == 0: - sPort = Port.getInstance() - output = sPort.exportXml(None, fit) + output = Port.exportXml(None, fit) if '.' not in os.path.basename(path): path += ".xml" else: @@ -790,7 +789,6 @@ class MainFrame(wx.Frame): def fileImportDialog(self, event): """Handles importing single/multiple EVE XML / EFT cfg fit files""" - sPort = Port.getInstance() dlg = wx.FileDialog( self, "Open One Or More Fitting Files", @@ -804,10 +802,13 @@ class MainFrame(wx.Frame): "Importing fits", " " * 100, # set some arbitrary spacing to create width in window parent=self, - style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME + style=wx.PD_CAN_ABORT + | wx.PD_SMOOTH + | wx.PD_ELAPSED_TIME + | wx.PD_APP_MODAL ) - self.progressDialog.message = None - sPort.importFitsThreaded(dlg.GetPaths(), self.fileImportCallback) + # self.progressDialog.message = None + Port.importFitsThreaded(dlg.GetPaths(), self) self.progressDialog.ShowModal() try: dlg.Destroy() @@ -839,9 +840,12 @@ class MainFrame(wx.Frame): "Backing up %d fits to: %s" % (max_, filePath), maximum=max_, parent=self, - style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME, + style=wx.PD_CAN_ABORT + | wx.PD_SMOOTH + | wx.PD_ELAPSED_TIME + | wx.PD_APP_MODAL ) - Port().backupFits(filePath, self.backupCallback) + Port.backupFits(filePath, self) self.progressDialog.ShowModal() def exportHtml(self, event): @@ -879,7 +883,17 @@ class MainFrame(wx.Frame): else: self.progressDialog.Update(info) - def fileImportCallback(self, action, data=None): + def onPortProcessStart(self): + # flag for progress dialog. + self.__progress_flag = True + # 2017/03/29 NOTE: implementation like interface + def onPortProcessing(self, action, data=None): + wx.CallAfter( + self._onPortProcessing, action, data + ) + return self.__progress_flag + + def _onPortProcessing(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 @@ -893,22 +907,39 @@ class MainFrame(wx.Frame): 1: Replace message with data other: Close dialog and handle based on :action (-1 open fits, -2 display error) """ - - if action is None: - self.progressDialog.Pulse() - elif action == 1 and data != self.progressDialog.message: - self.progressDialog.message = data - self.progressDialog.Pulse(data) - else: + _message = None + if action & IPortUser.ID_ERROR: self.closeProgressDialog() - if action == -1: - self._openAfterImport(data) - elif action == -2: - dlg = wx.MessageDialog(self, - "The following error was generated\n\n%s\n\nBe aware that already processed fits were not saved" % data, - "Import Error", wx.OK | wx.ICON_ERROR) - if dlg.ShowModal() == wx.ID_OK: - return + _message = "Import Error" if action & IPortUser.PROCESS_IMPORT else "Export Error" + dlg = wx.MessageDialog(self, + "The following error was generated\n\n%s\n\nBe aware that already processed fits were not saved" % data, + _message, wx.OK | wx.ICON_ERROR) +# if dlg.ShowModal() == wx.ID_OK: +# return + 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]) + def _openAfterImport(self, fits): if len(fits) > 0: diff --git a/gui/notesView.py b/gui/notesView.py index d4f8703cc..031e64941 100644 --- a/gui/notesView.py +++ b/gui/notesView.py @@ -40,8 +40,8 @@ class NotesView(wx.Panel): self.saveTimer.Start(1000, True) def delayedSave(self, event): - sFit = Fit.getInstance() - fit = sFit.getFit(self.lastFitId) - newNotes = self.editNotes.GetValue() - fit.notes = newNotes + fit = Fit.getInstance().getFit(self.lastFitId) + # NOTE: encounter a situation where fit is None + if fit is not None: + fit.notes = self.editNotes.GetValue() wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID)) diff --git a/service/port.py b/service/port.py index 3e923ed0f..a63dafd7e 100644 --- a/service/port.py +++ b/service/port.py @@ -1,3 +1,4 @@ +# coding: utf-8 # ============================================================================= # Copyright (C) 2014 Ryan Holmes # @@ -71,7 +72,123 @@ INV_FLAG_DRONEBAY = 87 INV_FLAG_FIGHTER = 158 +from utils.strfunctions import sequential_rep, replaceLTGT +# -- 170327 Ignored description -- +localized_pattern = re.compile(r'([^\*]+)\*') + + +def _resolveShip(fitting, sMkt): + fitobj = Fit() + # 2017/03/29 NOTE: + # if fit name contained "<" or ">" then reprace to named html entity by EVE client + fitobj.name = replaceLTGT(fitting.getAttribute("name")) + # Maelstrom + shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value") + emergency = None + matches = localized_pattern.match(shipType) + if matches: + # emergency cache + emergency = matches.group(2) + # expect an official name + shipType = matches.group(1) + limit = 2 + while True: + must_retry = False + try: + try: + fitobj.ship = Ship(sMkt.getItem(shipType)) + except ValueError: + fitobj.ship = Citadel(sMkt.getItem(shipType)) + except Exception as e: + pyfalog.warning("Caught exception on _resolveShip") + pyfalog.error(e) + shipType = emergency + must_retry = True + limit -= 1 + if not must_retry or limit is 0: + break + # True means localized + return matches is not None, fitobj + +def _resolveModule(hardware, sMkt, b_localized): + moduleName = hardware.getAttribute("type") + emergency = None + if b_localized: + emergency = localized_pattern.sub("\g<2>", moduleName) + # expect an official name + moduleName = localized_pattern.sub("\g<1>", moduleName) + item = None + limit = 2 + while True: + must_retry = False + try: + item = sMkt.getItem(moduleName, eager="group.category") + except Exception as e: + pyfalog.warning("Caught exception on _resolveModule") + pyfalog.error(e) + moduleName = emergency + must_retry = True + limit -= 1 + if not must_retry or limit is 0: + break + return item + + +class UserCancelException(Exception): + """when user cancel on port processing.""" + pass + + +from abc import ABCMeta, abstractmethod + + +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 onPortProcessing(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 onPortProcessStart(self): + pass + + class Port(object): + """ + 2017/03/31 NOTE: About change + 1. want to keep the description recorded in fit + 2. i think should not write wx.CallAfter in here + """ instance = None @classmethod @@ -82,19 +199,28 @@ class Port(object): return cls.instance @staticmethod - def backupFits(path, callback): + def backupFits(path, iportuser): pyfalog.debug("Starting backup fits thread.") - thread = FitBackupThread(path, callback) - thread.start() +# thread = FitBackupThread(path, callback) +# thread.start() + threading.Thread( + target=PortProcessing.backupFits, + args=(path, iportuser) + ).start() @staticmethod - def importFitsThreaded(paths, callback): + def importFitsThreaded(paths, iportuser): + """param iportuser: IPortUser implemented class""" pyfalog.debug("Starting import fits thread.") - thread = FitImportThread(paths, callback) - thread.start() +# thread = FitImportThread(paths, iportuser) +# thread.start() + threading.Thread( + target=PortProcessing.importFitsFromFile, + args=(paths, iportuser) + ).start() @staticmethod - def importFitFromFiles(paths, callback=None): + def importFitFromFiles(paths, iportuser=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 @@ -104,96 +230,104 @@ class Port(object): defcodepage = locale.getpreferredencoding() sFit = svcFit.getInstance() - fits = [] - for path in paths: - if callback: # Pulse - pyfalog.debug("Processing file:\n{0}", path) - wx.CallAfter(callback, 1, "Processing file:\n%s" % path) + fit_list = [] + try: + for path in paths: + if iportuser: # Pulse + msg = "Processing file:\n%s" % path + pyfalog.debug(msg) + PortProcessing.notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg) + #wx.CallAfter(callback, 1, msg) - file_ = open(path, "r") - srcString = file_.read() + file_ = open(path, "r") + srcString = file_.read() - if len(srcString) == 0: # ignore blank files - pyfalog.debug("File is blank.") - continue + if len(srcString) == 0: # ignore blank files + pyfalog.debug("File is blank.") + continue - codec_found = None - # If file had ANSI encoding, decode it to unicode using detection - # of BOM header or if there is no header try default - # codepage then fallback to utf-16, cp1252 + codec_found = None + # If file had ANSI encoding, decode it to unicode using detection + # of BOM header or if there is no header try default + # codepage then fallback to utf-16, cp1252 - if isinstance(srcString, str): - savebom = None + if isinstance(srcString, str): + savebom = None - encoding_map = ( - ('\xef\xbb\xbf', 'utf-8'), - ('\xff\xfe\0\0', 'utf-32'), - ('\0\0\xfe\xff', 'UTF-32BE'), - ('\xff\xfe', 'utf-16'), - ('\xfe\xff', 'UTF-16BE')) + encoding_map = ( + ('\xef\xbb\xbf', 'utf-8'), + ('\xff\xfe\0\0', 'utf-32'), + ('\0\0\xfe\xff', 'UTF-32BE'), + ('\xff\xfe', 'utf-16'), + ('\xfe\xff', 'UTF-16BE')) - for bom, encoding in encoding_map: - if srcString.startswith(bom): - codec_found = encoding - savebom = bom + for bom, encoding in encoding_map: + if srcString.startswith(bom): + codec_found = encoding + savebom = bom + + if codec_found is None: + pyfalog.info("Unicode BOM not found in file {0}.", path) + attempt_codecs = (defcodepage, "utf-8", "utf-16", "cp1252") + + for page in attempt_codecs: + try: + pyfalog.info("Attempting to decode file {0} using {1} page.", path, page) + srcString = unicode(srcString, page) + codec_found = page + pyfalog.info("File {0} decoded using {1} page.", path, page) + except UnicodeDecodeError: + pyfalog.info("Error unicode decoding {0} from page {1}, trying next codec", path, page) + else: + break + else: + pyfalog.info("Unicode BOM detected in {0}, using {1} page.", path, codec_found) + srcString = unicode(srcString[len(savebom):], codec_found) + + else: + # nasty hack to detect other transparent utf-16 loading + if srcString[0] == '<' and 'utf-16' in srcString[:128].lower(): + codec_found = "utf-16" + else: + codec_found = "utf-8" if codec_found is None: - pyfalog.info("Unicode BOM not found in file {0}.", path) - attempt_codecs = (defcodepage, "utf-8", "utf-16", "cp1252") + return False, "Proper codec could not be established for %s" % path - for page in attempt_codecs: - try: - pyfalog.info("Attempting to decode file {0} using {1} page.", path, page) - srcString = unicode(srcString, page) - codec_found = page - pyfalog.info("File {0} decoded using {1} page.", path, page) - except UnicodeDecodeError: - pyfalog.info("Error unicode decoding {0} from page {1}, trying next codec", path, page) - else: - break - else: - pyfalog.info("Unicode BOM detected in {0}, using {1} page.", path, codec_found) - srcString = unicode(srcString[len(savebom):], codec_found) + try: + _, fitsImport = Port.importAuto(srcString, path, iportuser=iportuser, encoding=codec_found) + fit_list += fitsImport + except xml.parsers.expat.ExpatError: + pyfalog.warning("Malformed XML in:\n{0}", path) + return False, "Malformed XML in %s" % path - else: - # nasty hack to detect other transparent utf-16 loading - if srcString[0] == '<' and 'utf-16' in srcString[:128].lower(): - codec_found = "utf-16" - else: - codec_found = "utf-8" + # IDs = [] # NOTE: what use for IDs? + numFits = len(fit_list) + for idx, fit in enumerate(fit_list): + # Set some more fit attributes and save + fit.character = sFit.character + fit.damagePattern = sFit.pattern + fit.targetResists = sFit.targetResists + db.save(fit) + # IDs.append(fit.ID) + if iportuser: # Pulse + pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits) + PortProcessing.notify( + iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, + "Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name) + ) - if codec_found is None: - return False, "Proper codec could not be established for %s" % path + except UserCancelException: + return False, "Processing has been canceled.\n" + except Exception as e: + pyfalog.critical("Unknown exception processing: {0}", path) + pyfalog.critical(e) + # TypeError: not all arguments converted during string formatting +# return False, "Unknown Error while processing {0}" % path + return False, "Unknown Error while processing %s\n\n message: %s" % (path, e.message) - try: - _, fitsImport = Port.importAuto(srcString, path, callback=callback, encoding=codec_found) - fits += fitsImport - except xml.parsers.expat.ExpatError: - pyfalog.warning("Malformed XML in:\n{0}", path) - return False, "Malformed XML in %s" % path - except Exception as e: - pyfalog.critical("Unknown exception processing: {0}", path) - pyfalog.critical(e) - return False, "Unknown Error while processing {0}" % path + return True, fit_list - IDs = [] - numFits = len(fits) - for i, fit in enumerate(fits): - # Set some more fit attributes and save - fit.character = sFit.character - fit.damagePattern = sFit.pattern - fit.targetResists = sFit.targetResists - db.save(fit) - IDs.append(fit.ID) - if callback: # Pulse - pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", i + 1, numFits) - wx.CallAfter( - callback, 1, - "Processing complete, saving fits to database\n(%d/%d)" % - (i + 1, numFits) - ) - - return True, fits @staticmethod def importFitFromBuffer(bufferStr, activeFit=None): @@ -228,7 +362,9 @@ class Port(object): fit['ship']['id'] = ofit.ship.item.ID fit['ship']['name'] = '' - fit['description'] = "" % ofit.ID + # 2017/03/29 NOTE: "<" or "<" is Ignored + # fit['description'] = "" % ofit.ID + fit['description'] = ofit.notes if ofit.notes is not None else "" fit['items'] = [] slotNum = {} @@ -302,7 +438,7 @@ class Port(object): return json.dumps(fit) @classmethod - def importAuto(cls, string, path=None, activeFit=None, callback=None, encoding=None): + def importAuto(cls, string, path=None, activeFit=None, iportuser=None, encoding=None): # Get first line and strip space symbols of it to avoid possible detection errors firstLine = re.split("[\n\r]+", string.strip(), maxsplit=1)[0] firstLine = firstLine.strip() @@ -310,9 +446,9 @@ class Port(object): # If XML-style start of tag encountered, detect as XML if re.match("<", firstLine): if encoding: - return "XML", cls.importXml(string, callback, encoding) + return "XML", cls.importXml(string, iportuser, encoding) else: - return "XML", cls.importXml(string, callback) + return "XML", cls.importXml(string, iportuser) # If JSON-style start, parse as CREST/JSON if firstLine[0] == '{': @@ -323,7 +459,7 @@ class Port(object): if re.match("\[.*\]", firstLine) and path is not None: filename = os.path.split(path)[1] shipName = filename.rsplit('.')[0] - return "EFT Config", cls.importEftCfg(shipName, string, callback) + return "EFT Config", cls.importEftCfg(shipName, string, iportuser) # If no file is specified and there's comma between brackets, # consider that we have [ship, setup name] and detect like eft export format @@ -335,22 +471,26 @@ class Port(object): @staticmethod def importCrest(str_): - fit = json.loads(str_) - sMkt = Market.getInstance() - f = Fit() - f.name = fit['name'] + sMkt = Market.getInstance() + fitobj = Fit() + refobj = json.loads(str_) + items = refobj['items'] + fitobj.name = refobj['name'] + # 2017/03/29: read description + # "<" and ">" is replace to "<", ">" by EVE client + fitobj.notes = refobj['description'] try: + refobj = refobj['ship']['id'] try: - f.ship = Ship(sMkt.getItem(fit['ship']['id'])) + fitobj.ship = Ship(sMkt.getItem(refobj)) except ValueError: - f.ship = Citadel(sMkt.getItem(fit['ship']['id'])) + fitobj.ship = Citadel(sMkt.getItem(refobj)) except: pyfalog.warning("Caught exception in importCrest") return None - items = fit['items'] items.sort(key=lambda k: k['flag']) moduleList = [] @@ -360,14 +500,14 @@ class Port(object): if module['flag'] == INV_FLAG_DRONEBAY: d = Drone(item) d.amount = module['quantity'] - f.drones.append(d) + fitobj.drones.append(d) elif module['flag'] == INV_FLAG_CARGOBAY: c = Cargo(item) c.amount = module['quantity'] - f.cargo.append(c) + fitobj.cargo.append(c) elif module['flag'] == INV_FLAG_FIGHTER: fighter = Fighter(item) - f.fighters.append(fighter) + fitobj.fighters.append(fighter) else: try: m = Module(item) @@ -377,8 +517,8 @@ class Port(object): continue # Add subsystems before modules to make sure T3 cruisers have subsystems installed if item.category.name == "Subsystem": - if m.fits(f): - f.modules.append(m) + if m.fits(fitobj): + fitobj.modules.append(m) else: if m.isValidState(State.ACTIVE): m.state = State.ACTIVE @@ -390,13 +530,13 @@ class Port(object): continue # Recalc to get slot numbers correct for T3 cruisers - svcFit.getInstance().recalc(f) + svcFit.getInstance().recalc(fitobj) for module in moduleList: - if module.fits(f): - f.modules.append(module) + if module.fits(fitobj): + fitobj.modules.append(module) - return f + return fitobj @staticmethod def importDna(string): @@ -635,7 +775,7 @@ class Port(object): return fit @staticmethod - def importEftCfg(shipname, contents, callback=None): + def importEftCfg(shipname, contents, iportuser=None): """Handle import from EFT config store file""" # Check if we have such ship in database, bail if we don't @@ -648,7 +788,7 @@ class Port(object): # If client didn't take care of encoding file contents into Unicode, # do it using fallback encoding ourselves if isinstance(contents, str): - contents = unicode(contents, "cp1252") + contents = unicode(contents, locale.getpreferredencoding()) fits = [] # List for fits fitIndices = [] # List for starting line numbers for each fit @@ -671,14 +811,14 @@ class Port(object): try: # Create fit object - f = Fit() + fitobj = Fit() # Strip square brackets and pull out a fit name - f.name = fitLines[0][1:-1] + fitobj.name = fitLines[0][1:-1] # Assign ship to fitting try: - f.ship = Ship(sMkt.getItem(shipname)) + fitobj.ship = Ship(sMkt.getItem(shipname)) except ValueError: - f.ship = Citadel(sMkt.getItem(shipname)) + fitobj.ship = Citadel(sMkt.getItem(shipname)) moduleList = [] for x in range(1, len(fitLines)): @@ -689,6 +829,8 @@ class Port(object): # Parse line into some data we will need misc = re.match("(Drones|Implant|Booster)_(Active|Inactive)=(.+)", line) cargo = re.match("Cargohold=(.+)", line) + # 2017/03/27 NOTE: store description from EFT + description = re.match("Description=(.+)", line) if misc: entityType = misc.group(1) @@ -713,11 +855,11 @@ class Port(object): d.amountActive = droneAmount elif entityState == "Inactive": d.amountActive = 0 - f.drones.append(d) + fitobj.drones.append(d) elif droneItem.category.name == "Fighter": # EFT saves fighter as drones ft = Fighter(droneItem) ft.amount = int(droneAmount) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize - f.fighters.append(ft) + fitobj.fighters.append(ft) else: continue elif entityType == "Implant": @@ -735,7 +877,7 @@ class Port(object): imp.active = True elif entityState == "Inactive": imp.active = False - f.implants.append(imp) + fitobj.implants.append(imp) elif entityType == "Booster": # Bail if we can't get item or it's not from implant category try: @@ -752,7 +894,7 @@ class Port(object): b.active = True elif entityState == "Inactive": b.active = False - f.boosters.append(b) + fitobj.boosters.append(b) # If we don't have any prefixes, then it's a module elif cargo: cargoData = re.match("(.+),([0-9]+)", cargo.group(1)) @@ -767,7 +909,10 @@ class Port(object): # Add Cargo to the fitting c = Cargo(item) c.amount = cargoAmount - f.cargo.append(c) + fitobj.cargo.append(c) + # 2017/03/27 NOTE: store description from EFT + elif description: + fitobj.notes = description.group(1).replace("|", "\n") else: withCharge = re.match("(.+),(.+)", line) modName = withCharge.group(1) if withCharge else line @@ -784,10 +929,10 @@ class Port(object): # Add subsystems before modules to make sure T3 cruisers have subsystems installed if modItem.category.name == "Subsystem": - if m.fits(f): - f.modules.append(m) + if m.fits(fitobj): + fitobj.modules.append(m) else: - m.owner = f + m.owner = fitobj # Activate mod if it is activable if m.isValidState(State.ACTIVE): m.state = State.ACTIVE @@ -804,17 +949,21 @@ class Port(object): moduleList.append(m) # Recalc to get slot numbers correct for T3 cruisers - svcFit.getInstance().recalc(f) + svcFit.getInstance().recalc(fitobj) for module in moduleList: - if module.fits(f): - f.modules.append(module) + if module.fits(fitobj): + fitobj.modules.append(module) # Append fit to list of fits - fits.append(f) + fits.append(fitobj) + + if iportuser: # NOTE: Send current processing status + PortProcessing.notify( + iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, + "%s:\n%s" % (fitobj.ship.name, fitobj.name) + ) - if callback: - wx.CallAfter(callback, None) # Skip fit silently if we get an exception except Exception as e: pyfalog.error("Caught exception on fit.") @@ -824,93 +973,95 @@ class Port(object): return fits @staticmethod - def importXml(text, callback=None, encoding="utf-8"): - sMkt = Market.getInstance() + def importXml(text, iportuser=None, encoding="utf-8"): + sMkt = Market.getInstance() doc = xml.dom.minidom.parseString(text.encode(encoding)) fittings = doc.getElementsByTagName("fittings").item(0) fittings = fittings.getElementsByTagName("fitting") - fits = [] + fit_list = [] + + for fitting in (fittings): + b_localized, fitobj = _resolveShip(fitting, sMkt) + # -- 170327 Ignored description -- + # read description from exported xml. (EVE client, EFT) + description = fitting.getElementsByTagName("description").item(0).getAttribute("value") + if description is None: + description = "" + elif len(description): + # convert
to "\n" and remove html tags. + description = sequential_rep(description, r"<(br|BR)>", "\n",r"<[^<>]+>", "") +# description = re.sub(r"<(br|BR)>", "\n", description) +# description = re.sub(r"<[^<>]+>", "", description) + fitobj.notes = description - for i, fitting in enumerate(fittings): - f = Fit() - f.name = fitting.getAttribute("name") - # Maelstrom - shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value") - try: - try: - f.ship = Ship(sMkt.getItem(shipType)) - except ValueError: - f.ship = Citadel(sMkt.getItem(shipType)) - except Exception as e: - pyfalog.warning("Caught exception on importXml") - pyfalog.error(e) - continue hardwares = fitting.getElementsByTagName("hardware") moduleList = [] for hardware in hardwares: try: - moduleName = hardware.getAttribute("type") - try: - item = sMkt.getItem(moduleName, eager="group.category") - except Exception as e: - pyfalog.warning("Caught exception on importXml") - pyfalog.error(e) + item = _resolveModule(hardware, sMkt, b_localized) + if not item: continue - if item: - if item.category.name == "Drone": - d = Drone(item) - d.amount = int(hardware.getAttribute("qty")) - f.drones.append(d) - elif item.category.name == "Fighter": - ft = Fighter(item) - ft.amount = int(hardware.getAttribute("qty")) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize - f.fighters.append(ft) - elif hardware.getAttribute("slot").lower() == "cargo": - # although the eve client only support charges in cargo, third-party programs - # may support items or "refits" in cargo. Support these by blindly adding all - # cargo, not just charges - c = Cargo(item) - c.amount = int(hardware.getAttribute("qty")) - f.cargo.append(c) - else: - try: - m = Module(item) - # When item can't be added to any slot (unknown item or just charge), ignore it - except ValueError: - pyfalog.warning("item can't be added to any slot (unknown item or just charge), ignore it") - continue - # Add subsystems before modules to make sure T3 cruisers have subsystems installed - if item.category.name == "Subsystem": - if m.fits(f): - m.owner = f - f.modules.append(m) - else: - if m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - moduleList.append(m) + if item.category.name == "Drone": + d = Drone(item) + d.amount = int(hardware.getAttribute("qty")) + fitobj.drones.append(d) + elif item.category.name == "Fighter": + ft = Fighter(item) + ft.amount = int(hardware.getAttribute("qty")) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize + fitobj.fighters.append(ft) + elif hardware.getAttribute("slot").lower() == "cargo": + # although the eve client only support charges in cargo, third-party programs + # may support items or "refits" in cargo. Support these by blindly adding all + # cargo, not just charges + c = Cargo(item) + c.amount = int(hardware.getAttribute("qty")) + fitobj.cargo.append(c) + else: + try: + m = Module(item) + # When item can't be added to any slot (unknown item or just charge), ignore it + except ValueError: + pyfalog.warning("item can't be added to any slot (unknown item or just charge), ignore it") + continue + # Add subsystems before modules to make sure T3 cruisers have subsystems installed + if item.category.name == "Subsystem": + if m.fits(fitobj): + m.owner = fitobj + fitobj.modules.append(m) + else: + if m.isValidState(State.ACTIVE): + m.state = State.ACTIVE + + moduleList.append(m) except KeyboardInterrupt: pyfalog.warning("Keyboard Interrupt") continue # Recalc to get slot numbers correct for T3 cruisers - svcFit.getInstance().recalc(f) + svcFit.getInstance().recalc(fitobj) for module in moduleList: - if module.fits(f): - module.owner = f - f.modules.append(module) + if module.fits(fitobj): + module.owner = fitobj + fitobj.modules.append(module) - fits.append(f) - if callback: - wx.CallAfter(callback, None) + fit_list.append(fitobj) + if iportuser: # NOTE: Send current processing status + PortProcessing.notify( + iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, + "analysis :%s\n%s" % (fitobj.ship.name, fitobj.name) + ) - return fits + return fit_list @staticmethod def _exportEftBase(fit): + """Basically EFT format does not require blank lines + also, it's OK to arrange modules randomly? + """ offineSuffix = " /OFFLINE" export = "[%s, %s]\n" % (fit.ship.item.name, fit.name) stuff = {} @@ -1038,10 +1189,13 @@ class Port(object): return dna + "::" - @classmethod - def exportXml(cls, callback=None, *fits): + @staticmethod + def exportXml(iportuser=None, *fits): doc = xml.dom.minidom.Document() fittings = doc.createElement("fittings") + # fit count + fit_count = len(fits) + fittings.setAttribute("count", "%s" % fit_count) doc.appendChild(fittings) sFit = svcFit.getInstance() @@ -1051,10 +1205,18 @@ class Port(object): fitting.setAttribute("name", fit.name) fittings.appendChild(fitting) description = doc.createElement("description") - description.setAttribute("value", "") + # -- 170327 Ignored description -- + try: + notes = fit.notes # unicode + description.setAttribute( + "value", re.sub("[\r\n]+", "
", notes) if notes is not None else "" + ) + except Exception as e: + pyfalog.warning("read description is failed, msg=%s\n" % e.args) + fitting.appendChild(description) shipType = doc.createElement("shipType") - shipType.setAttribute("value", fit.ship.item.name) + shipType.setAttribute("value", fit.ship.name) fitting.appendChild(shipType) charges = {} @@ -1113,12 +1275,17 @@ class Port(object): hardware.setAttribute("slot", "cargo") hardware.setAttribute("type", name) fitting.appendChild(hardware) - except: - print("Failed on fitID: %d" % fit.ID) + except Exception as e: +# print("Failed on fitID: %d" % fit.ID) + pyfalog.error("Failed on fitID: %d, message: %s" % e.message) continue finally: - if callback: - wx.CallAfter(callback, i) + if iportuser: + PortProcessing.notify( + iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE, + (i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name)) + ) +# wx.CallAfter(callback, i, "(%s/%s) %s" % (i, fit_count, fit.ship.name)) return doc.toprettyxml() @@ -1169,37 +1336,34 @@ class Port(object): return export -class FitBackupThread(threading.Thread): - def __init__(self, path, callback): - threading.Thread.__init__(self) - self.path = path - self.callback = callback - - def run(self): - path = self.path - sFit = svcFit.getInstance() - sPort = Port.getInstance() - backedUpFits = sPort.exportXml(self.callback, *sFit.getAllFits()) - backupFile = open(path, "w", encoding="utf-8") - backupFile.write(backedUpFits) - backupFile.close() - +class PortProcessing(object): + """Port Processing class """ + @staticmethod + def backupFits(path, iportuser): + success = True + try: + iportuser.onPortProcessStart() + backedUpFits = Port.exportXml(iportuser, *svcFit.getInstance().getAllFits()) + backupFile = open(path, "w", encoding="utf-8") + backupFile.write(backedUpFits) + backupFile.close() + except UserCancelException: + success = False # Send done signal to GUI - wx.CallAfter(self.callback, -1) +# wx.CallAfter(callback, -1, "Done.") + flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE + iportuser.onPortProcessing(IPortUser.PROCESS_EXPORT | flag, + "User canceled or some error occurrence." if not success else "Done.") + @staticmethod + def importFitsFromFile(paths, iportuser): + iportuser.onPortProcessStart() + success, result = Port.importFitFromFiles(paths, iportuser) + flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE + iportuser.onPortProcessing(IPortUser.PROCESS_IMPORT | flag, result) -class FitImportThread(threading.Thread): - def __init__(self, paths, callback): - threading.Thread.__init__(self) - self.paths = paths - self.callback = callback + @staticmethod + def notify(iportuser, flag, data): + if not iportuser.onPortProcessing(flag, data): + raise UserCancelException - def run(self): - sPort = Port.getInstance() - success, result = sPort.importFitFromFiles(self.paths, self.callback) - - if not success: # there was an error during processing - pyfalog.error("Error while processing file import: {0}", result) - wx.CallAfter(self.callback, -2, result) - else: # Send done signal to GUI - wx.CallAfter(self.callback, -1, result) diff --git a/utils/strfunctions.py b/utils/strfunctions.py new file mode 100644 index 000000000..a1decbd1c --- /dev/null +++ b/utils/strfunctions.py @@ -0,0 +1,34 @@ +''' + string manipulation module +''' +import re + + +def sequential_rep(text_, *args): + """ + params + text_: string content + args : , , , , ... + + return + empty string when text_ length was zero or invalid. + """ + + if not text_ or not len(text_): + return "" + + arg_len = len(args) + i = 0 + while i < arg_len: + text_ = re.sub(args[i], args[i + 1], text_) + i += 2 + + return text_ + +def replaceLTGT(text_): + """if fit name contained "<" or ">" then reprace to named html entity by EVE client. + + for fit name. + """ + return text_.replace("<", "<").replace(">", ">") if isinstance(text_, unicode) else text_ +