Merge remote-tracking branch 'jeffy/fix_unread_description' into dev, see #1099
Conflicts: eos/config.py gui/notesView.py service/settings.py
This commit is contained in:
672
service/port.py
672
service/port.py
@@ -46,6 +46,8 @@ 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, replace_ltgt
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)):
|
||||
from service.crest import Crest
|
||||
@@ -70,9 +72,152 @@ 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 --
|
||||
RE_LTGT = "&(lt|gt);"
|
||||
L_MARK = "<localized hint=""
|
||||
# <localized hint="([^"]+)">([^\*]+)\*<\/localized>
|
||||
LOCALIZED_PATTERN = re.compile(r'<localized hint="([^"]+)">([^\*]+)\*</localized>')
|
||||
|
||||
|
||||
def _extract_match(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, service.market.Market, bool) -> eos.saveddata.fit.Fit
|
||||
""" NOTE: Since it is meaningless unless a correct ship object can be constructed,
|
||||
process flow changed
|
||||
"""
|
||||
# ------ Confirm ship
|
||||
# <localized hint="Maelstrom">Maelstrom</localized>
|
||||
shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value")
|
||||
anything = None
|
||||
if b_localized:
|
||||
# expect an official name, emergency cache
|
||||
shipType, anything = _extract_match(shipType)
|
||||
|
||||
limit = 2
|
||||
ship = None
|
||||
while True:
|
||||
must_retry = False
|
||||
try:
|
||||
try:
|
||||
ship = Ship(sMkt.getItem(shipType))
|
||||
except ValueError:
|
||||
ship = Citadel(sMkt.getItem(shipType))
|
||||
except Exception as e:
|
||||
pyfalog.warning("Caught exception on _resolve_ship")
|
||||
pyfalog.error(e)
|
||||
limit -= 1
|
||||
if limit is 0:
|
||||
break
|
||||
shipType = anything
|
||||
must_retry = True
|
||||
if not must_retry:
|
||||
break
|
||||
|
||||
if ship is None:
|
||||
raise Exception("cannot resolve ship type.")
|
||||
|
||||
fitobj = Fit(ship=ship)
|
||||
# ------ Confirm fit name
|
||||
anything = fitting.getAttribute("name")
|
||||
# 2017/03/29 NOTE:
|
||||
# if fit name contained "<" or ">" then reprace to named html entity by EVE client
|
||||
# if re.search(RE_LTGT, anything):
|
||||
if "<" in anything or ">" in anything:
|
||||
anything = replace_ltgt(anything)
|
||||
fitobj.name = anything
|
||||
|
||||
return fitobj
|
||||
|
||||
|
||||
def _resolve_module(hardware, sMkt, b_localized):
|
||||
# type: (xml.dom.minidom.Element, service.market.Market, bool) -> eos.saveddata.module.Module
|
||||
moduleName = hardware.getAttribute("type")
|
||||
emergency = None
|
||||
if b_localized:
|
||||
# expect an official name, emergency cache
|
||||
moduleName, emergency = _extract_match(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 _resolve_module")
|
||||
pyfalog.error(e)
|
||||
limit -= 1
|
||||
if limit is 0:
|
||||
break
|
||||
moduleName = emergency
|
||||
must_retry = True
|
||||
if not must_retry:
|
||||
break
|
||||
return item
|
||||
|
||||
|
||||
class UserCancelException(Exception):
|
||||
"""when user cancel on port processing."""
|
||||
pass
|
||||
|
||||
|
||||
class IPortUser:
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
ID_PULSE = 1
|
||||
# Pulse the progress bar
|
||||
ID_UPDATE = ID_PULSE << 1
|
||||
# Replace message with data: update messate
|
||||
ID_DONE = ID_PULSE << 2
|
||||
# open fits: import process done
|
||||
ID_ERROR = ID_PULSE << 3
|
||||
# display error: raise some error
|
||||
|
||||
PROCESS_IMPORT = ID_PULSE << 4
|
||||
# means import process.
|
||||
PROCESS_EXPORT = ID_PULSE << 5
|
||||
# means import process.
|
||||
|
||||
@abstractmethod
|
||||
def on_port_processing(self, action, data=None):
|
||||
"""
|
||||
While importing fits from file, the logic calls back to this function to
|
||||
update progress bar to show activity. XML files can contain multiple
|
||||
ships with multiple fits, whereas EFT cfg files contain many fits of
|
||||
a single ship. When iterating through the files, we update the message
|
||||
when we start a new file, and then Pulse the progress bar with every fit
|
||||
that is processed.
|
||||
|
||||
action : a flag that lets us know how to deal with :data
|
||||
None: Pulse the progress bar
|
||||
1: Replace message with data
|
||||
other: Close dialog and handle based on :action (-1 open fits, -2 display error)
|
||||
"""
|
||||
|
||||
"""return: True is continue process, False is cancel."""
|
||||
pass
|
||||
|
||||
def on_port_process_start(self):
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
__tag_replace_flag = True
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
@@ -81,20 +226,44 @@ 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, 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):
|
||||
# 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, 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,99 +273,109 @@ 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()
|
||||
with open(path, "r") as file_:
|
||||
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
|
||||
|
||||
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
|
||||
return True, fit_list
|
||||
|
||||
@staticmethod
|
||||
def importFitFromBuffer(bufferStr, activeFit=None):
|
||||
# type: (basestring, object) -> object
|
||||
# TODO: catch the exception?
|
||||
# activeFit is reserved?, bufferStr is unicode? (assume only clipboard string?
|
||||
sFit = svcFit.getInstance()
|
||||
_, fits = Port.importAuto(bufferStr, activeFit=activeFit)
|
||||
for fit in fits:
|
||||
@@ -228,7 +407,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,17 +483,18 @@ 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):
|
||||
# type: (basestring, basestring, object, IPortUser, basestring) -> object
|
||||
# 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()
|
||||
|
||||
# If XML-style start of tag encountered, detect as XML
|
||||
if re.match("<", firstLine):
|
||||
if re.search(RE_XML_START, 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 +505,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 +517,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']
|
||||
# "<" and ">" is replace to "<", ">" by EVE client
|
||||
fitobj.name = refobj['name']
|
||||
# 2017/03/29: read description
|
||||
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 +546,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 +563,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 +576,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 +821,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 +834,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 +857,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 +875,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 +901,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 +923,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 +940,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 +955,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 +975,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 +995,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 +1019,100 @@ class Port(object):
|
||||
return fits
|
||||
|
||||
@staticmethod
|
||||
def importXml(text, callback=None, encoding="utf-8"):
|
||||
def importXml(text, iportuser=None, encoding="utf-8"):
|
||||
# type: (basestring, IPortUser, basestring) -> list[eos.saveddata.fit.Fit]
|
||||
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")
|
||||
fits = []
|
||||
fit_list = []
|
||||
|
||||
for fitting in fittings:
|
||||
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")
|
||||
if description is None:
|
||||
description = ""
|
||||
elif len(description):
|
||||
# convert <br> to "\n" and remove html tags.
|
||||
if Port.is_tag_replace():
|
||||
description = replace_ltgt(
|
||||
sequential_rep(description, r"<(br|BR)>", "\n", r"<[^<>]+>", "")
|
||||
)
|
||||
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 = _resolve_module(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 +1240,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 +1256,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|\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 +1326,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 +1387,33 @@ 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.on_port_process_start()
|
||||
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.on_port_processing(IPortUser.PROCESS_EXPORT | flag,
|
||||
"User canceled or some error occurrence." if not success else "Done.")
|
||||
|
||||
@staticmethod
|
||||
def importFitsFromFile(paths, iportuser):
|
||||
iportuser.on_port_process_start()
|
||||
success, result = Port.importFitFromFiles(paths, iportuser)
|
||||
flag = IPortUser.ID_ERROR if not success else IPortUser.ID_DONE
|
||||
iportuser.on_port_processing(IPortUser.PROCESS_IMPORT | flag, result)
|
||||
|
||||
class FitImportThread(threading.Thread):
|
||||
def __init__(self, paths, callback):
|
||||
threading.Thread.__init__(self)
|
||||
self.paths = paths
|
||||
self.callback = callback
|
||||
|
||||
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)
|
||||
@staticmethod
|
||||
def notify(iportuser, flag, data):
|
||||
if not iportuser.on_port_processing(flag, data):
|
||||
raise UserCancelException
|
||||
|
||||
@@ -46,36 +46,61 @@ class SettingsProvider(object):
|
||||
if not os.path.exists(self.BASE_PATH):
|
||||
os.mkdir(self.BASE_PATH)
|
||||
|
||||
# def getSettings(self, area, defaults=None):
|
||||
# # type: (basestring, dict) -> service.Settings
|
||||
# # NOTE: needed to change for tests
|
||||
# settings_obj = self.settings.get(area)
|
||||
#
|
||||
# if settings_obj is None and hasattr(self, 'BASE_PATH'):
|
||||
# canonical_path = os.path.join(self.BASE_PATH, area)
|
||||
#
|
||||
# if not os.path.exists(canonical_path):
|
||||
# info = {}
|
||||
# if defaults:
|
||||
# for item in defaults:
|
||||
# info[item] = defaults[item]
|
||||
#
|
||||
# else:
|
||||
# try:
|
||||
# f = open(canonical_path, "rb")
|
||||
# info = cPickle.load(f)
|
||||
# for item in defaults:
|
||||
# if item not in info:
|
||||
# info[item] = defaults[item]
|
||||
#
|
||||
# except:
|
||||
# info = {}
|
||||
# if defaults:
|
||||
# for item in defaults:
|
||||
# info[item] = defaults[item]
|
||||
#
|
||||
# self.settings[area] = settings_obj = Settings(canonical_path, info)
|
||||
#
|
||||
# return settings_obj
|
||||
def getSettings(self, area, defaults=None):
|
||||
|
||||
s = self.settings.get(area)
|
||||
|
||||
if s is None and hasattr(self, 'BASE_PATH'):
|
||||
p = os.path.join(self.BASE_PATH, area)
|
||||
|
||||
if not os.path.exists(p):
|
||||
# type: (basestring, dict) -> service.Settings
|
||||
# NOTE: needed to change for tests
|
||||
# TODO: Write to memory with mmap -> https://docs.python.org/2/library/mmap.html
|
||||
settings_obj = self.settings.get(area)
|
||||
if settings_obj is None: # and hasattr(self, 'BASE_PATH'):
|
||||
canonical_path = os.path.join(self.BASE_PATH, area) if hasattr(self, 'BASE_PATH') else ""
|
||||
if not os.path.exists(canonical_path): # path string or empty string.
|
||||
info = {}
|
||||
if defaults:
|
||||
for item in defaults:
|
||||
info[item] = defaults[item]
|
||||
|
||||
info.update(defaults)
|
||||
else:
|
||||
try:
|
||||
f = open(p, "rb")
|
||||
info = cPickle.load(f)
|
||||
with open(canonical_path, "rb") as f:
|
||||
info = cPickle.load(f)
|
||||
for item in defaults:
|
||||
if item not in info:
|
||||
info[item] = defaults[item]
|
||||
|
||||
except:
|
||||
info = {}
|
||||
if defaults:
|
||||
for item in defaults:
|
||||
info[item] = defaults[item]
|
||||
info.update(defaults)
|
||||
|
||||
self.settings[area] = s = Settings(p, info)
|
||||
|
||||
return s
|
||||
self.settings[area] = settings_obj = Settings(canonical_path, info)
|
||||
return settings_obj
|
||||
|
||||
def saveAll(self):
|
||||
for settings in self.settings.itervalues():
|
||||
@@ -84,12 +109,22 @@ class SettingsProvider(object):
|
||||
|
||||
class Settings(object):
|
||||
def __init__(self, location, info):
|
||||
# type: (basestring, dict) -> None
|
||||
# path string or empty string.
|
||||
self.location = location
|
||||
self.info = info
|
||||
|
||||
# def save(self):
|
||||
# f = open(self.location, "wb")
|
||||
# cPickle.dump(self.info, f, cPickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def save(self):
|
||||
f = open(self.location, "wb")
|
||||
cPickle.dump(self.info, f, cPickle.HIGHEST_PROTOCOL)
|
||||
# NOTE: needed to change for tests
|
||||
if self.location is None or not self.location:
|
||||
return
|
||||
# NOTE: with + open -> file handle auto close
|
||||
with open(self.location, "wb") as f:
|
||||
cPickle.dump(self.info, f, cPickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def __getitem__(self, k):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user