diff --git a/gui/mainFrame.py b/gui/mainFrame.py index fcc901b3b..777c608b5 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -877,19 +877,19 @@ class MainFrame(wx.Frame, IPortUser): else: self.progressDialog.Update(info) - def onPortProcessStart(self): + def on_port_process_start(self): # flag for progress dialog. self.__progress_flag = True - def onPortProcessing(self, action, data=None): + def on_port_processing(self, action, data=None): # 2017/03/29 NOTE: implementation like interface wx.CallAfter( - self._onPortProcessing, action, data + self._on_port_processing, action, data ) return self.__progress_flag - def _onPortProcessing(self, action, data): + 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 diff --git a/service/port.py b/service/port.py index b2b7163c1..b7bdd95ac 100644 --- a/service/port.py +++ b/service/port.py @@ -47,7 +47,7 @@ from eos.saveddata.ship import Ship from eos.saveddata.citadel import Citadel from eos.saveddata.fit import Fit from service.market import Market -from utils.strfunctions import sequential_rep, replaceLTGT +from utils.strfunctions import sequential_rep, replace_ltgt from abc import ABCMeta, abstractmethod if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): @@ -73,25 +73,39 @@ INV_FLAG_CARGOBAY = 5 INV_FLAG_DRONEBAY = 87 INV_FLAG_FIGHTER = 158 +# 2017/04/05 NOTE: simple validation, for xml file +RE_XML_START = r'<\?xml\s+version="1.0"\s+\?>' # -- 170327 Ignored description -- -localized_pattern = re.compile(r'([^\*]+)\*') +RE_LTGT = "&(lt|gt);" +L_MARK = "<localized hint="" +LOCALIZED_PATTERN = re.compile(r'([^\*]+)\*') -def _resolveShip(fitting, sMkt): +def _extract_matche(t): + m = LOCALIZED_PATTERN.match(t) + # hint attribute, text content + return m.group(1), m.group(2) + + +def _resolve_ship(fitting, sMkt, b_localized): + # type: (xml.dom.minidom.Element, Market, bool) -> eos.saveddata.fit.Fit + fitobj = Fit() + fitobj.name = fitting.getAttribute("name") # 2017/03/29 NOTE: # if fit name contained "<" or ">" then reprace to named html entity by EVE client - fitobj.name = replaceLTGT(fitting.getAttribute("name")) + # if re.search(RE_LTGT, fitobj.name): + if "<" in fitobj.name or ">" in fitobj.name: + fitobj.name = replace_ltgt(fitobj.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) + # emergency = None + if b_localized: + # expect an official name, emergency cache + shipType, emergency = _extract_matche(shipType) + limit = 2 while True: must_retry = False @@ -101,24 +115,28 @@ def _resolveShip(fitting, sMkt): except ValueError: fitobj.ship = Citadel(sMkt.getItem(shipType)) except Exception as e: - pyfalog.warning("Caught exception on _resolveShip") + pyfalog.warning("Caught exception on _resolve_ship") pyfalog.error(e) + limit -= 1 + if limit is 0: + break shipType = emergency must_retry = True - limit -= 1 - if not must_retry or limit is 0: + if not must_retry: break - # True means localized - return matches is not None, fitobj + + return fitobj -def _resolveModule(hardware, sMkt, b_localized): +def _resolve_module(hardware, sMkt, b_localized): + # type: (xml.dom.minidom.Element, Market, bool) -> eos.saveddata.module.Module + moduleName = hardware.getAttribute("type") - emergency = None + # emergency = None if b_localized: - emergency = localized_pattern.sub("\g<2>", moduleName) - # expect an official name - moduleName = localized_pattern.sub("\g<1>", moduleName) + # expect an official name, emergency cache + moduleName, emergency = _extract_matche(moduleName) + item = None limit = 2 while True: @@ -126,12 +144,14 @@ def _resolveModule(hardware, sMkt, b_localized): try: item = sMkt.getItem(moduleName, eager="group.category") except Exception as e: - pyfalog.warning("Caught exception on _resolveModule") + pyfalog.warning("Caught exception on _resolve_module") pyfalog.error(e) + limit -= 1 + if limit is 0: + break moduleName = emergency must_retry = True - limit -= 1 - if not must_retry or limit is 0: + if not must_retry: break return item @@ -160,7 +180,7 @@ class IPortUser: # means import process. @abstractmethod - def onPortProcessing(self, action, data=None): + 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 @@ -178,7 +198,7 @@ class IPortUser: """return: True is continue process, False is cancel.""" pass - def onPortProcessStart(self): + def on_port_process_start(self): pass @@ -189,6 +209,7 @@ class Port(object): 2. i think should not write wx.CallAfter in here """ instance = None + __tag_replace_flag = True @classmethod def getInstance(cls): @@ -197,6 +218,16 @@ class Port(object): return cls.instance + @classmethod + def set_tag_replace(cls, b): + cls.__tag_replace_flag = b + + @classmethod + def is_tag_replace(cls): + # might there is a person who wants to hold tags. + # (item link in EVE client etc. When importing again to EVE) + return cls.__tag_replace_flag + @staticmethod def backupFits(path, iportuser): pyfalog.debug("Starting backup fits thread.") @@ -209,7 +240,12 @@ class Port(object): @staticmethod def importFitsThreaded(paths, iportuser): - """param iportuser: IPortUser implemented class""" + # type: (tuple, IPortUser) -> None + """ + :param paths: fits data file path list. + :param iportuser: IPortUser implemented class. + :rtype: None + """ pyfalog.debug("Starting import fits thread.") # thread = FitImportThread(paths, iportuser) # thread.start() @@ -238,8 +274,8 @@ class Port(object): PortProcessing.notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg) # wx.CallAfter(callback, 1, msg) - file_ = open(path, "r") - srcString = file_.read() + with open(path, "r") as file_: + srcString = file_.read() if len(srcString) == 0: # ignore blank files pyfalog.debug("File is blank.") @@ -442,7 +478,7 @@ class Port(object): firstLine = firstLine.strip() # If XML-style start of tag encountered, detect as XML - if re.match("<", firstLine): + if re.match(RE_XML_START, firstLine): if encoding: return "XML", cls.importXml(string, iportuser, encoding) else: @@ -975,12 +1011,16 @@ class Port(object): sMkt = Market.getInstance() doc = xml.dom.minidom.parseString(text.encode(encoding)) + # NOTE: + # When L_MARK is included at this point, + # Decided to be localized data + b_localized = L_MARK in text fittings = doc.getElementsByTagName("fittings").item(0) fittings = fittings.getElementsByTagName("fitting") fit_list = [] for fitting in (fittings): - b_localized, fitobj = _resolveShip(fitting, sMkt) + fitobj = _resolve_ship(fitting, sMkt, b_localized) # -- 170327 Ignored description -- # read description from exported xml. (EVE client, EFT) description = fitting.getElementsByTagName("description").item(0).getAttribute("value") @@ -988,16 +1028,17 @@ class Port(object): 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) + if Port.is_tag_replace(): + description = replace_ltgt( + sequential_rep(description, r"<(br|BR)>", "\n", r"<[^<>]+>", "") + ) fitobj.notes = description hardwares = fitting.getElementsByTagName("hardware") moduleList = [] for hardware in hardwares: try: - item = _resolveModule(hardware, sMkt, b_localized) + item = _resolve_module(hardware, sMkt, b_localized) if not item: continue @@ -1340,7 +1381,7 @@ class PortProcessing(object): def backupFits(path, iportuser): success = True try: - iportuser.onPortProcessStart() + iportuser.on_port_process_start() backedUpFits = Port.exportXml(iportuser, *svcFit.getInstance().getAllFits()) backupFile = open(path, "w", encoding="utf-8") backupFile.write(backedUpFits) @@ -1350,17 +1391,17 @@ class PortProcessing(object): # Send done signal to GUI # wx.CallAfter(callback, -1, "Done.") flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE - iportuser.onPortProcessing(IPortUser.PROCESS_EXPORT | flag, + iportuser.on_port_processing(IPortUser.PROCESS_EXPORT | flag, "User canceled or some error occurrence." if not success else "Done.") @staticmethod def importFitsFromFile(paths, iportuser): - iportuser.onPortProcessStart() + iportuser.on_port_process_start() success, result = Port.importFitFromFiles(paths, iportuser) flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE - iportuser.onPortProcessing(IPortUser.PROCESS_IMPORT | flag, result) + iportuser.on_port_processing(IPortUser.PROCESS_IMPORT | flag, result) @staticmethod def notify(iportuser, flag, data): - if not iportuser.onPortProcessing(flag, data): + if not iportuser.on_port_processing(flag, data): raise UserCancelException diff --git a/utils/stopwatch.py b/utils/stopwatch.py new file mode 100644 index 000000000..c14339e90 --- /dev/null +++ b/utils/stopwatch.py @@ -0,0 +1,107 @@ +# coding: utf-8 + +import time +import os + +# 2017/04/05: +class Stopwatch(object): + """ + --- on python console --- +import re +from utils.stopwatch import Stopwatch +# measurement re.sub +def m_re_sub(t, set_count, executes, texts): + t.reset() + while set_count: + set_count -= 1 + with t: + while executes: + executes -= 1 + ret = re.sub("[a|s]+", "-", texts) + # stat string + return str(t) + +# measurementor +stpwth = Stopwatch("test") +# statistics loop: 1000(exec re.sub: 100000) +m_re_sub(stpwth, 1000, 100000, "asdfadsasdaasdfadsasda") + +----------- records ----------- + text: "asdfadsasda" + 'elapsed record(ms): min=0.000602411446948, max=220.85578571' + 'elapsed record(ms): min=0.000602411446948, max=217.331377504' + + text: "asdfadsasdaasdfadsasda" + 'elapsed record(ms): min=0.000602411446948, max=287.784902967' + 'elapsed record(ms): min=0.000602411432737, max=283.653264016' + + NOTE: about max + The value is large only at the first execution, + Will it be optimized, after that it will be significantly smaller + """ + + # time.clock() is μs? 1/1000ms + # https://docs.python.jp/2.7/library/time.html#time.clock + _tfunc = time.clock if os.name == "nt" else time.time + + def __init__(self, name='', logger=None): + self.name = name + self.start = Stopwatch._tfunc() + self.__last = self.start + # __last field is means last checkpoint system clock value? + self.logger = logger + self.reset() + + @property + def stat(self): + # :return: (float, float) + return (self.min, self.max) + + @property + def elapsed(self): + # :return: time as ms + return (Stopwatch._tfunc() - self.start) * 1000 + + @property + def last(self): + return self.__last * 1000 + + def __update_stat(self, v): + # :param v: unit of ms + if self.min == 0.0: self.min = v + if self.max < v: self.max = v + if self.min > v: self.min = v + + def checkpoint(self, name=''): + span = self.elapsed + self.__update_stat(span) + text = u'Stopwatch("{tname}") - {checkpoint} - {last:.6f}ms ({elapsed:.12f}ms elapsed)'.format( + tname=self.name, + checkpoint=unicode(name, "utf-8"), + last=self.last, + elapsed=span + ).strip() + self.__last = Stopwatch._tfunc() + if self.logger: + self.logger.debug(text) + else: + print(text) + + def reset(self): + self.min = 0.0 + self.max = 0.0 + + def __enter__(self): + self.start = Stopwatch._tfunc() + return self + + def __exit__(self, type, value, traceback): + # https://docs.python.org/2.7/reference/datamodel.html?highlight=__enter__#object.__exit__ + # If the context was exited without an exception, all three arguments will be None + self.checkpoint('finished') + # ex: "type=None, value=None, traceback=None" + # print "type=%s, value=%s, traceback=%s" % (type, value, traceback) + return True + + def __repr__(self): + return "elapsed record(ms): min=%s, max=%s" % self.stat diff --git a/utils/strfunctions.py b/utils/strfunctions.py index cd769d20b..ec6013284 100644 --- a/utils/strfunctions.py +++ b/utils/strfunctions.py @@ -5,30 +5,26 @@ import re def sequential_rep(text_, *args): + # type: (basestring, *list) -> basestring """ - params - text_: string content - args : , , , , ... - - return - empty string when text_ length was zero or invalid. + :param text_: string content + :param args: like , , , , ... + :return: if text_ length was zero or invalid parameters then no manipulation to text_ """ - - 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 + if arg_len % 2 == 0 and isinstance(text_, basestring) and len(text_) > 0: + i = 0 + while i < arg_len: + text_ = re.sub(args[i], args[i + 1], text_) + i += 2 return text_ -def replaceLTGT(text_): +def replace_ltgt(text_): + # type: (basestring) -> basestring """if fit name contained "<" or ">" then reprace to named html entity by EVE client. - - for fit name. + :param text_: string content of fit name from exported by EVE client. + :return: if text_ is not instance of basestring then no manipulation to text_. """ - return text_.replace("<", "<").replace(">", ">") if isinstance(text_, unicode) else text_ + return text_.replace("<", "<").replace(">", ">") if isinstance(text_, basestring) else text_