Improved processing status notification for import and export

This commit is contained in:
jeffy-g
2017-04-03 01:34:07 +09:00
parent 216dac068d
commit c560d17bdd
5 changed files with 490 additions and 256 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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))

View File

@@ -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'<localized hint="([^"]+)">([^\*]+)\*</localized>')
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"))
# <localized hint="Maelstrom">Maelstrom</localized>
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'] = "<pyfa:%d />" % ofit.ID
# 2017/03/29 NOTE: "<" or "&lt;" is Ignored
# fit['description'] = "<pyfa:%d />" % 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 "&lt;", "&gt;" 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 <br> 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")
# <localized hint="Maelstrom">Maelstrom</localized>
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]+", "<br>", 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)

34
utils/strfunctions.py Normal file
View File

@@ -0,0 +1,34 @@
'''
string manipulation module
'''
import re
def sequential_rep(text_, *args):
"""
params
text_: string content
args : <pattern>, <replacement>, <pattern>, <replacement>, ...
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("&lt;", "<").replace("&gt;", ">") if isinstance(text_, unicode) else text_