Improved processing status notification for import and export
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
616
service/port.py
616
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'<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 "<" 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 "<", ">" 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
34
utils/strfunctions.py
Normal 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("<", "<").replace(">", ">") if isinstance(text_, unicode) else text_
|
||||
|
||||
Reference in New Issue
Block a user