Get rid of PortProcessing object
This commit is contained in:
@@ -35,6 +35,7 @@ from eos.saveddata.fit import Fit
|
|||||||
from gui.utils.numberFormatter import roundToPrec
|
from gui.utils.numberFormatter import roundToPrec
|
||||||
from service.fit import Fit as svcFit
|
from service.fit import Fit as svcFit
|
||||||
from service.market import Market
|
from service.market import Market
|
||||||
|
from service.port.shared import IPortUser, processing_notify
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
@@ -59,10 +60,550 @@ EFT_OPTIONS = {
|
|||||||
"name": "Mutated Attributes",
|
"name": "Mutated Attributes",
|
||||||
"description": "Exports Abyssal stats"
|
"description": "Exports Abyssal stats"
|
||||||
}
|
}
|
||||||
# 4: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EftPort:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def exportEft(cls, fit, options):
|
||||||
|
# EFT formatted export is split in several sections, each section is
|
||||||
|
# separated from another using 2 blank lines. Sections might have several
|
||||||
|
# sub-sections, which are separated by 1 blank line
|
||||||
|
sections = []
|
||||||
|
|
||||||
|
header = '[{}, {}]'.format(fit.ship.item.name, fit.name)
|
||||||
|
|
||||||
|
# Section 1: modules, rigs, subsystems, services
|
||||||
|
modsBySlotType = {}
|
||||||
|
sFit = svcFit.getInstance()
|
||||||
|
for module in fit.modules:
|
||||||
|
modsBySlotType.setdefault(module.slot, []).append(module)
|
||||||
|
modSection = []
|
||||||
|
|
||||||
|
mutants = {} # Format: {reference number: module}
|
||||||
|
mutantReference = 1
|
||||||
|
for slotType in SLOT_ORDER:
|
||||||
|
rackLines = []
|
||||||
|
modules = modsBySlotType.get(slotType, ())
|
||||||
|
for module in modules:
|
||||||
|
if module.item:
|
||||||
|
mutated = bool(module.mutators)
|
||||||
|
# if module was mutated, use base item name for export
|
||||||
|
if mutated:
|
||||||
|
modName = module.baseItem.name
|
||||||
|
else:
|
||||||
|
modName = module.item.name
|
||||||
|
if mutated and options & Options.MUTATIONS.value:
|
||||||
|
mutants[mutantReference] = module
|
||||||
|
mutationSuffix = ' [{}]'.format(mutantReference)
|
||||||
|
mutantReference += 1
|
||||||
|
else:
|
||||||
|
mutationSuffix = ''
|
||||||
|
modOfflineSuffix = ' {}'.format(OFFLINE_SUFFIX) if module.state == State.OFFLINE else ''
|
||||||
|
if module.charge and sFit.serviceFittingOptions['exportCharges']:
|
||||||
|
rackLines.append('{}, {}{}{}'.format(
|
||||||
|
modName, module.charge.name, modOfflineSuffix, mutationSuffix))
|
||||||
|
else:
|
||||||
|
rackLines.append('{}{}{}'.format(modName, modOfflineSuffix, mutationSuffix))
|
||||||
|
else:
|
||||||
|
rackLines.append('[Empty {} slot]'.format(
|
||||||
|
Slot.getName(slotType).capitalize() if slotType is not None else ''))
|
||||||
|
if rackLines:
|
||||||
|
modSection.append('\n'.join(rackLines))
|
||||||
|
if modSection:
|
||||||
|
sections.append('\n\n'.join(modSection))
|
||||||
|
|
||||||
|
# Section 2: drones, fighters
|
||||||
|
minionSection = []
|
||||||
|
droneLines = []
|
||||||
|
for drone in sorted(fit.drones, key=lambda d: d.item.name):
|
||||||
|
droneLines.append('{} x{}'.format(drone.item.name, drone.amount))
|
||||||
|
if droneLines:
|
||||||
|
minionSection.append('\n'.join(droneLines))
|
||||||
|
fighterLines = []
|
||||||
|
for fighter in sorted(fit.fighters, key=lambda f: f.item.name):
|
||||||
|
fighterLines.append('{} x{}'.format(fighter.item.name, fighter.amountActive))
|
||||||
|
if fighterLines:
|
||||||
|
minionSection.append('\n'.join(fighterLines))
|
||||||
|
if minionSection:
|
||||||
|
sections.append('\n\n'.join(minionSection))
|
||||||
|
|
||||||
|
# Section 3: implants, boosters
|
||||||
|
if options & Options.IMPLANTS.value:
|
||||||
|
charSection = []
|
||||||
|
implantLines = []
|
||||||
|
for implant in fit.implants:
|
||||||
|
implantLines.append(implant.item.name)
|
||||||
|
if implantLines:
|
||||||
|
charSection.append('\n'.join(implantLines))
|
||||||
|
boosterLines = []
|
||||||
|
for booster in fit.boosters:
|
||||||
|
boosterLines.append(booster.item.name)
|
||||||
|
if boosterLines:
|
||||||
|
charSection.append('\n'.join(boosterLines))
|
||||||
|
if charSection:
|
||||||
|
sections.append('\n\n'.join(charSection))
|
||||||
|
|
||||||
|
# Section 4: cargo
|
||||||
|
cargoLines = []
|
||||||
|
for cargo in sorted(
|
||||||
|
fit.cargo,
|
||||||
|
key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name)
|
||||||
|
):
|
||||||
|
cargoLines.append('{} x{}'.format(cargo.item.name, cargo.amount))
|
||||||
|
if cargoLines:
|
||||||
|
sections.append('\n'.join(cargoLines))
|
||||||
|
|
||||||
|
# Section 5: mutated modules' details
|
||||||
|
mutationLines = []
|
||||||
|
if mutants and options & Options.MUTATIONS.value:
|
||||||
|
for mutantReference in sorted(mutants):
|
||||||
|
mutant = mutants[mutantReference]
|
||||||
|
mutatedAttrs = {}
|
||||||
|
for attrID, mutator in mutant.mutators.items():
|
||||||
|
attrName = getAttributeInfo(attrID).name
|
||||||
|
mutatedAttrs[attrName] = mutator.value
|
||||||
|
mutationLines.append('[{}] {}'.format(mutantReference, mutant.baseItem.name))
|
||||||
|
mutationLines.append(' {}'.format(mutant.mutaplasmid.item.name))
|
||||||
|
# Round to 7th significant number to avoid exporting float errors
|
||||||
|
customAttrsLine = ', '.join(
|
||||||
|
'{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7))
|
||||||
|
for a in sorted(mutatedAttrs))
|
||||||
|
mutationLines.append(' {}'.format(customAttrsLine))
|
||||||
|
if mutationLines:
|
||||||
|
sections.append('\n'.join(mutationLines))
|
||||||
|
|
||||||
|
return '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def importEft(cls, eftString):
|
||||||
|
lines = cls.__prepareImportString(eftString)
|
||||||
|
try:
|
||||||
|
fit = cls.__createFit(lines)
|
||||||
|
except EftImportError:
|
||||||
|
return
|
||||||
|
|
||||||
|
aFit = AbstractFit()
|
||||||
|
aFit.mutations = cls.__getMutationData(lines)
|
||||||
|
|
||||||
|
nameChars = '[^,/\[\]]' # Characters which are allowed to be used in name
|
||||||
|
stubPattern = '^\[.+?\]$'
|
||||||
|
modulePattern = '^(?P<typeName>{0}+?)(,\s*(?P<chargeName>{0}+?))?(?P<offline>\s*{1})?(\s*\[(?P<mutation>\d+?)\])?$'.format(nameChars, OFFLINE_SUFFIX)
|
||||||
|
droneCargoPattern = '^(?P<typeName>{}+?) x(?P<amount>\d+?)$'.format(nameChars)
|
||||||
|
|
||||||
|
sections = []
|
||||||
|
for section in cls.__importSectionIter(lines):
|
||||||
|
for line in section.lines:
|
||||||
|
# Stub line
|
||||||
|
if re.match(stubPattern, line):
|
||||||
|
section.itemSpecs.append(None)
|
||||||
|
continue
|
||||||
|
# Items with quantity specifier
|
||||||
|
m = re.match(droneCargoPattern, line)
|
||||||
|
if m:
|
||||||
|
try:
|
||||||
|
itemSpec = MultiItemSpec(m.group('typeName'))
|
||||||
|
# Items which cannot be fetched are considered as stubs
|
||||||
|
except EftImportError:
|
||||||
|
section.itemSpecs.append(None)
|
||||||
|
else:
|
||||||
|
itemSpec.amount = int(m.group('amount'))
|
||||||
|
section.itemSpecs.append(itemSpec)
|
||||||
|
continue
|
||||||
|
# All other items
|
||||||
|
m = re.match(modulePattern, line)
|
||||||
|
if m:
|
||||||
|
try:
|
||||||
|
itemSpec = RegularItemSpec(m.group('typeName'), chargeName=m.group('chargeName'))
|
||||||
|
# Items which cannot be fetched are considered as stubs
|
||||||
|
except EftImportError:
|
||||||
|
section.itemSpecs.append(None)
|
||||||
|
else:
|
||||||
|
if m.group('offline'):
|
||||||
|
itemSpec.offline = True
|
||||||
|
if m.group('mutation'):
|
||||||
|
itemSpec.mutationIdx = int(m.group('mutation'))
|
||||||
|
section.itemSpecs.append(itemSpec)
|
||||||
|
continue
|
||||||
|
clearTail(section.itemSpecs)
|
||||||
|
sections.append(section)
|
||||||
|
|
||||||
|
|
||||||
|
hasDroneBay = any(s.isDroneBay for s in sections)
|
||||||
|
hasFighterBay = any(s.isFighterBay for s in sections)
|
||||||
|
for section in sections:
|
||||||
|
if section.isModuleRack:
|
||||||
|
aFit.addModules(section.itemSpecs)
|
||||||
|
elif section.isImplantRack:
|
||||||
|
for itemSpec in section.itemSpecs:
|
||||||
|
aFit.addImplant(itemSpec)
|
||||||
|
elif section.isDroneBay:
|
||||||
|
for itemSpec in section.itemSpecs:
|
||||||
|
aFit.addDrone(itemSpec)
|
||||||
|
elif section.isFighterBay:
|
||||||
|
for itemSpec in section.itemSpecs:
|
||||||
|
aFit.addFighter(itemSpec)
|
||||||
|
elif section.isCargoHold:
|
||||||
|
for itemSpec in section.itemSpecs:
|
||||||
|
aFit.addCargo(itemSpec)
|
||||||
|
# Mix between different kinds of item specs (can happen when some
|
||||||
|
# blank lines are removed)
|
||||||
|
else:
|
||||||
|
for itemSpec in section.itemSpecs:
|
||||||
|
if itemSpec is None:
|
||||||
|
continue
|
||||||
|
if itemSpec.isModule:
|
||||||
|
aFit.addModule(itemSpec)
|
||||||
|
elif itemSpec.isImplant:
|
||||||
|
aFit.addImplant(itemSpec)
|
||||||
|
elif itemSpec.isDrone and not hasDroneBay:
|
||||||
|
aFit.addDrone(itemSpec)
|
||||||
|
elif itemSpec.isFighter and not hasFighterBay:
|
||||||
|
aFit.addFighter(itemSpec)
|
||||||
|
elif itemSpec.isCargo:
|
||||||
|
aFit.addCargo(itemSpec)
|
||||||
|
|
||||||
|
# Subsystems first because they modify slot amount
|
||||||
|
for m in aFit.subsystems:
|
||||||
|
if m is None:
|
||||||
|
dummy = Module.buildEmpty(aFit.getSlotByContainer(aFit.subsystems))
|
||||||
|
dummy.owner = fit
|
||||||
|
fit.modules.appendIgnoreEmpty(dummy)
|
||||||
|
elif m.fits(fit):
|
||||||
|
m.owner = fit
|
||||||
|
fit.modules.appendIgnoreEmpty(m)
|
||||||
|
svcFit.getInstance().recalc(fit)
|
||||||
|
|
||||||
|
# Other stuff
|
||||||
|
for modRack in (
|
||||||
|
aFit.rigs,
|
||||||
|
aFit.services,
|
||||||
|
aFit.modulesHigh,
|
||||||
|
aFit.modulesMed,
|
||||||
|
aFit.modulesLow,
|
||||||
|
):
|
||||||
|
for m in modRack:
|
||||||
|
if m is None:
|
||||||
|
dummy = Module.buildEmpty(aFit.getSlotByContainer(modRack))
|
||||||
|
dummy.owner = fit
|
||||||
|
fit.modules.appendIgnoreEmpty(dummy)
|
||||||
|
elif m.fits(fit):
|
||||||
|
m.owner = fit
|
||||||
|
if not m.isValidState(m.state):
|
||||||
|
pyfalog.warning('EftPort.importEft: module {} cannot have state {}', m, m.state)
|
||||||
|
fit.modules.appendIgnoreEmpty(m)
|
||||||
|
for implant in aFit.implants:
|
||||||
|
fit.implants.append(implant)
|
||||||
|
for booster in aFit.boosters:
|
||||||
|
fit.boosters.append(booster)
|
||||||
|
for drone in aFit.drones.values():
|
||||||
|
fit.drones.append(drone)
|
||||||
|
for fighter in aFit.fighters:
|
||||||
|
fit.fighters.append(fighter)
|
||||||
|
for cargo in aFit.cargo.values():
|
||||||
|
fit.cargo.append(cargo)
|
||||||
|
|
||||||
|
return fit
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def importEftCfg(shipname, contents, iportuser):
|
||||||
|
"""Handle import from EFT config store file"""
|
||||||
|
|
||||||
|
# Check if we have such ship in database, bail if we don't
|
||||||
|
sMkt = Market.getInstance()
|
||||||
|
try:
|
||||||
|
sMkt.getItem(shipname)
|
||||||
|
except:
|
||||||
|
return [] # empty list is expected
|
||||||
|
|
||||||
|
fits = [] # List for fits
|
||||||
|
fitIndices = [] # List for starting line numbers for each fit
|
||||||
|
lines = re.split('[\n\r]+', contents) # Separate string into lines
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
# Detect fit header
|
||||||
|
if line[:1] == "[" and line[-1:] == "]":
|
||||||
|
# Line index where current fit starts
|
||||||
|
startPos = lines.index(line)
|
||||||
|
fitIndices.append(startPos)
|
||||||
|
|
||||||
|
for i, startPos in enumerate(fitIndices):
|
||||||
|
# End position is last file line if we're trying to get it for last fit,
|
||||||
|
# or start position of next fit minus 1
|
||||||
|
endPos = len(lines) if i == len(fitIndices) - 1 else fitIndices[i + 1]
|
||||||
|
|
||||||
|
# Finally, get lines for current fitting
|
||||||
|
fitLines = lines[startPos:endPos]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create fit object
|
||||||
|
fitobj = Fit()
|
||||||
|
# Strip square brackets and pull out a fit name
|
||||||
|
fitobj.name = fitLines[0][1:-1]
|
||||||
|
# Assign ship to fitting
|
||||||
|
try:
|
||||||
|
fitobj.ship = Ship(sMkt.getItem(shipname))
|
||||||
|
except ValueError:
|
||||||
|
fitobj.ship = Citadel(sMkt.getItem(shipname))
|
||||||
|
|
||||||
|
moduleList = []
|
||||||
|
for x in range(1, len(fitLines)):
|
||||||
|
line = fitLines[x]
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
entityState = misc.group(2)
|
||||||
|
entityData = misc.group(3)
|
||||||
|
if entityType == "Drones":
|
||||||
|
droneData = re.match("(.+),([0-9]+)", entityData)
|
||||||
|
# Get drone name and attempt to detect drone number
|
||||||
|
droneName = droneData.group(1) if droneData else entityData
|
||||||
|
droneAmount = int(droneData.group(2)) if droneData else 1
|
||||||
|
# Bail if we can't get item or it's not from drone category
|
||||||
|
try:
|
||||||
|
droneItem = sMkt.getItem(droneName, eager="group.category")
|
||||||
|
except:
|
||||||
|
pyfalog.warning("Cannot get item.")
|
||||||
|
continue
|
||||||
|
if droneItem.category.name == "Drone":
|
||||||
|
# Add drone to the fitting
|
||||||
|
d = Drone(droneItem)
|
||||||
|
d.amount = droneAmount
|
||||||
|
if entityState == "Active":
|
||||||
|
d.amountActive = droneAmount
|
||||||
|
elif entityState == "Inactive":
|
||||||
|
d.amountActive = 0
|
||||||
|
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
|
||||||
|
fitobj.fighters.append(ft)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif entityType == "Implant":
|
||||||
|
# Bail if we can't get item or it's not from implant category
|
||||||
|
try:
|
||||||
|
implantItem = sMkt.getItem(entityData, eager="group.category")
|
||||||
|
except:
|
||||||
|
pyfalog.warning("Cannot get item.")
|
||||||
|
continue
|
||||||
|
if implantItem.category.name != "Implant":
|
||||||
|
continue
|
||||||
|
# Add implant to the fitting
|
||||||
|
imp = Implant(implantItem)
|
||||||
|
if entityState == "Active":
|
||||||
|
imp.active = True
|
||||||
|
elif entityState == "Inactive":
|
||||||
|
imp.active = False
|
||||||
|
fitobj.implants.append(imp)
|
||||||
|
elif entityType == "Booster":
|
||||||
|
# Bail if we can't get item or it's not from implant category
|
||||||
|
try:
|
||||||
|
boosterItem = sMkt.getItem(entityData, eager="group.category")
|
||||||
|
except:
|
||||||
|
pyfalog.warning("Cannot get item.")
|
||||||
|
continue
|
||||||
|
# All boosters have implant category
|
||||||
|
if boosterItem.category.name != "Implant":
|
||||||
|
continue
|
||||||
|
# Add booster to the fitting
|
||||||
|
b = Booster(boosterItem)
|
||||||
|
if entityState == "Active":
|
||||||
|
b.active = True
|
||||||
|
elif entityState == "Inactive":
|
||||||
|
b.active = False
|
||||||
|
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))
|
||||||
|
cargoName = cargoData.group(1) if cargoData else cargo.group(1)
|
||||||
|
cargoAmount = int(cargoData.group(2)) if cargoData else 1
|
||||||
|
# Bail if we can't get item
|
||||||
|
try:
|
||||||
|
item = sMkt.getItem(cargoName)
|
||||||
|
except:
|
||||||
|
pyfalog.warning("Cannot get item.")
|
||||||
|
continue
|
||||||
|
# Add Cargo to the fitting
|
||||||
|
c = Cargo(item)
|
||||||
|
c.amount = cargoAmount
|
||||||
|
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
|
||||||
|
chargeName = withCharge.group(2) if withCharge else None
|
||||||
|
# If we can't get module item, skip it
|
||||||
|
try:
|
||||||
|
modItem = sMkt.getItem(modName)
|
||||||
|
except:
|
||||||
|
pyfalog.warning("Cannot get item.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create module
|
||||||
|
m = Module(modItem)
|
||||||
|
|
||||||
|
# Add subsystems before modules to make sure T3 cruisers have subsystems installed
|
||||||
|
if modItem.category.name == "Subsystem":
|
||||||
|
if m.fits(fitobj):
|
||||||
|
fitobj.modules.append(m)
|
||||||
|
else:
|
||||||
|
m.owner = fitobj
|
||||||
|
# Activate mod if it is activable
|
||||||
|
if m.isValidState(State.ACTIVE):
|
||||||
|
m.state = State.ACTIVE
|
||||||
|
# Add charge to mod if applicable, on any errors just don't add anything
|
||||||
|
if chargeName:
|
||||||
|
try:
|
||||||
|
chargeItem = sMkt.getItem(chargeName, eager="group.category")
|
||||||
|
if chargeItem.category.name == "Charge":
|
||||||
|
m.charge = chargeItem
|
||||||
|
except:
|
||||||
|
pyfalog.warning("Cannot get item.")
|
||||||
|
pass
|
||||||
|
# Append module to fit
|
||||||
|
moduleList.append(m)
|
||||||
|
|
||||||
|
# Recalc to get slot numbers correct for T3 cruisers
|
||||||
|
svcFit.getInstance().recalc(fitobj)
|
||||||
|
|
||||||
|
for module in moduleList:
|
||||||
|
if module.fits(fitobj):
|
||||||
|
fitobj.modules.append(module)
|
||||||
|
|
||||||
|
# Append fit to list of fits
|
||||||
|
fits.append(fitobj)
|
||||||
|
|
||||||
|
if iportuser: # NOTE: Send current processing status
|
||||||
|
processing_notify(
|
||||||
|
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
|
||||||
|
"%s:\n%s" % (fitobj.ship.name, fitobj.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Skip fit silently if we get an exception
|
||||||
|
except Exception as e:
|
||||||
|
pyfalog.error("Caught exception on fit.")
|
||||||
|
pyfalog.error(e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
return fits
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __prepareImportString(eftString):
|
||||||
|
lines = eftString.splitlines()
|
||||||
|
for i in range(len(lines)):
|
||||||
|
lines[i] = lines[i].strip()
|
||||||
|
while lines and not lines[0]:
|
||||||
|
del lines[0]
|
||||||
|
while lines and not lines[-1]:
|
||||||
|
del lines[-1]
|
||||||
|
return lines
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __getMutationData(lines):
|
||||||
|
data = {}
|
||||||
|
consumedIndices = set()
|
||||||
|
for i in range(len(lines)):
|
||||||
|
line = lines[i]
|
||||||
|
m = re.match('^\[(?P<ref>\d+)\]', line)
|
||||||
|
if m:
|
||||||
|
ref = int(m.group('ref'))
|
||||||
|
# Attempt to apply mutation is useless w/o mutaplasmid, so skip it
|
||||||
|
# altogether if we have no info on it
|
||||||
|
try:
|
||||||
|
mutaName = lines[i + 1]
|
||||||
|
except IndexError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
consumedIndices.add(i)
|
||||||
|
consumedIndices.add(i + 1)
|
||||||
|
# Get custom attribute values
|
||||||
|
mutaAttrs = {}
|
||||||
|
try:
|
||||||
|
mutaAttrsLine = lines[i + 2]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
consumedIndices.add(i + 2)
|
||||||
|
pairs = [p.strip() for p in mutaAttrsLine.split(',')]
|
||||||
|
for pair in pairs:
|
||||||
|
try:
|
||||||
|
attrName, value = pair.split(' ')
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
continue
|
||||||
|
attrInfo = getAttributeInfo(attrName.strip())
|
||||||
|
if attrInfo is None:
|
||||||
|
continue
|
||||||
|
mutaAttrs[attrInfo.ID] = value
|
||||||
|
mutaItem = fetchItem(mutaName)
|
||||||
|
if mutaItem is None:
|
||||||
|
continue
|
||||||
|
data[ref] = (mutaItem, mutaAttrs)
|
||||||
|
# If we got here, we have seen at least correct reference line and
|
||||||
|
# mutaplasmid name line
|
||||||
|
i += 2
|
||||||
|
# Bonus points for seeing correct attrs line. Worst case we
|
||||||
|
# will have to scan it once again
|
||||||
|
if mutaAttrs:
|
||||||
|
i += 1
|
||||||
|
# Cleanup the lines from mutaplasmid info
|
||||||
|
for i in sorted(consumedIndices, reverse=True):
|
||||||
|
del lines[i]
|
||||||
|
return data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __importSectionIter(lines):
|
||||||
|
section = Section()
|
||||||
|
for line in lines:
|
||||||
|
if not line:
|
||||||
|
if section.lines:
|
||||||
|
yield section
|
||||||
|
section = Section()
|
||||||
|
else:
|
||||||
|
section.lines.append(line)
|
||||||
|
if section.lines:
|
||||||
|
yield section
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __createFit(lines):
|
||||||
|
"""Create fit and set top-level entity (ship or citadel)."""
|
||||||
|
fit = Fit()
|
||||||
|
header = lines.pop(0)
|
||||||
|
m = re.match('\[(?P<shipType>[\w\s]+),\s*(?P<fitName>.+)\]', header)
|
||||||
|
if not m:
|
||||||
|
pyfalog.warning('EftPort.importEft: corrupted fit header')
|
||||||
|
raise EftImportError
|
||||||
|
shipType = m.group('shipType').strip()
|
||||||
|
fitName = m.group('fitName').strip()
|
||||||
|
try:
|
||||||
|
ship = fetchItem(shipType)
|
||||||
|
try:
|
||||||
|
fit.ship = Ship(ship)
|
||||||
|
except ValueError:
|
||||||
|
fit.ship = Citadel(ship)
|
||||||
|
fit.name = fitName
|
||||||
|
except:
|
||||||
|
pyfalog.warning('EftPort.importEft: exception caught when parsing header')
|
||||||
|
raise EftImportError
|
||||||
|
return fit
|
||||||
|
|
||||||
|
# Various methods and functions which assist with EFT import-export
|
||||||
|
|
||||||
def fetchItem(typeName, eagerCat=False):
|
def fetchItem(typeName, eagerCat=False):
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
eager = 'group.category' if eagerCat else None
|
eager = 'group.category' if eagerCat else None
|
||||||
@@ -340,350 +881,3 @@ class AbstractFit:
|
|||||||
if itemSpec.item not in self.cargo:
|
if itemSpec.item not in self.cargo:
|
||||||
self.cargo[itemSpec.item] = Cargo(itemSpec.item)
|
self.cargo[itemSpec.item] = Cargo(itemSpec.item)
|
||||||
self.cargo[itemSpec.item].amount += itemSpec.amount
|
self.cargo[itemSpec.item].amount += itemSpec.amount
|
||||||
|
|
||||||
|
|
||||||
class EftPort:
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def exportEft(cls, fit, options):
|
|
||||||
# EFT formatted export is split in several sections, each section is
|
|
||||||
# separated from another using 2 blank lines. Sections might have several
|
|
||||||
# sub-sections, which are separated by 1 blank line
|
|
||||||
sections = []
|
|
||||||
|
|
||||||
header = '[{}, {}]'.format(fit.ship.item.name, fit.name)
|
|
||||||
|
|
||||||
# Section 1: modules, rigs, subsystems, services
|
|
||||||
modsBySlotType = {}
|
|
||||||
sFit = svcFit.getInstance()
|
|
||||||
for module in fit.modules:
|
|
||||||
modsBySlotType.setdefault(module.slot, []).append(module)
|
|
||||||
modSection = []
|
|
||||||
|
|
||||||
mutants = {} # Format: {reference number: module}
|
|
||||||
mutantReference = 1
|
|
||||||
for slotType in SLOT_ORDER:
|
|
||||||
rackLines = []
|
|
||||||
modules = modsBySlotType.get(slotType, ())
|
|
||||||
for module in modules:
|
|
||||||
if module.item:
|
|
||||||
mutated = bool(module.mutators)
|
|
||||||
# if module was mutated, use base item name for export
|
|
||||||
if mutated:
|
|
||||||
modName = module.baseItem.name
|
|
||||||
else:
|
|
||||||
modName = module.item.name
|
|
||||||
if mutated and options & Options.MUTATIONS.value:
|
|
||||||
mutants[mutantReference] = module
|
|
||||||
mutationSuffix = ' [{}]'.format(mutantReference)
|
|
||||||
mutantReference += 1
|
|
||||||
else:
|
|
||||||
mutationSuffix = ''
|
|
||||||
modOfflineSuffix = ' {}'.format(OFFLINE_SUFFIX) if module.state == State.OFFLINE else ''
|
|
||||||
if module.charge and sFit.serviceFittingOptions['exportCharges']:
|
|
||||||
rackLines.append('{}, {}{}{}'.format(
|
|
||||||
modName, module.charge.name, modOfflineSuffix, mutationSuffix))
|
|
||||||
else:
|
|
||||||
rackLines.append('{}{}{}'.format(modName, modOfflineSuffix, mutationSuffix))
|
|
||||||
else:
|
|
||||||
rackLines.append('[Empty {} slot]'.format(
|
|
||||||
Slot.getName(slotType).capitalize() if slotType is not None else ''))
|
|
||||||
if rackLines:
|
|
||||||
modSection.append('\n'.join(rackLines))
|
|
||||||
if modSection:
|
|
||||||
sections.append('\n\n'.join(modSection))
|
|
||||||
|
|
||||||
# Section 2: drones, fighters
|
|
||||||
minionSection = []
|
|
||||||
droneLines = []
|
|
||||||
for drone in sorted(fit.drones, key=lambda d: d.item.name):
|
|
||||||
droneLines.append('{} x{}'.format(drone.item.name, drone.amount))
|
|
||||||
if droneLines:
|
|
||||||
minionSection.append('\n'.join(droneLines))
|
|
||||||
fighterLines = []
|
|
||||||
for fighter in sorted(fit.fighters, key=lambda f: f.item.name):
|
|
||||||
fighterLines.append('{} x{}'.format(fighter.item.name, fighter.amountActive))
|
|
||||||
if fighterLines:
|
|
||||||
minionSection.append('\n'.join(fighterLines))
|
|
||||||
if minionSection:
|
|
||||||
sections.append('\n\n'.join(minionSection))
|
|
||||||
|
|
||||||
# Section 3: implants, boosters
|
|
||||||
if options & Options.IMPLANTS.value:
|
|
||||||
charSection = []
|
|
||||||
implantLines = []
|
|
||||||
for implant in fit.implants:
|
|
||||||
implantLines.append(implant.item.name)
|
|
||||||
if implantLines:
|
|
||||||
charSection.append('\n'.join(implantLines))
|
|
||||||
boosterLines = []
|
|
||||||
for booster in fit.boosters:
|
|
||||||
boosterLines.append(booster.item.name)
|
|
||||||
if boosterLines:
|
|
||||||
charSection.append('\n'.join(boosterLines))
|
|
||||||
if charSection:
|
|
||||||
sections.append('\n\n'.join(charSection))
|
|
||||||
|
|
||||||
# Section 4: cargo
|
|
||||||
cargoLines = []
|
|
||||||
for cargo in sorted(
|
|
||||||
fit.cargo,
|
|
||||||
key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name)
|
|
||||||
):
|
|
||||||
cargoLines.append('{} x{}'.format(cargo.item.name, cargo.amount))
|
|
||||||
if cargoLines:
|
|
||||||
sections.append('\n'.join(cargoLines))
|
|
||||||
|
|
||||||
# Section 5: mutated modules' details
|
|
||||||
mutationLines = []
|
|
||||||
if mutants and options & Options.MUTATIONS.value:
|
|
||||||
for mutantReference in sorted(mutants):
|
|
||||||
mutant = mutants[mutantReference]
|
|
||||||
mutatedAttrs = {}
|
|
||||||
for attrID, mutator in mutant.mutators.items():
|
|
||||||
attrName = getAttributeInfo(attrID).name
|
|
||||||
mutatedAttrs[attrName] = mutator.value
|
|
||||||
mutationLines.append('[{}] {}'.format(mutantReference, mutant.baseItem.name))
|
|
||||||
mutationLines.append(' {}'.format(mutant.mutaplasmid.item.name))
|
|
||||||
# Round to 7th significant number to avoid exporting float errors
|
|
||||||
customAttrsLine = ', '.join(
|
|
||||||
'{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7))
|
|
||||||
for a in sorted(mutatedAttrs))
|
|
||||||
mutationLines.append(' {}'.format(customAttrsLine))
|
|
||||||
if mutationLines:
|
|
||||||
sections.append('\n'.join(mutationLines))
|
|
||||||
|
|
||||||
return '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def importEft(cls, eftString):
|
|
||||||
lines = cls.__prepareImportString(eftString)
|
|
||||||
try:
|
|
||||||
fit = cls.__createFit(lines)
|
|
||||||
except EftImportError:
|
|
||||||
return
|
|
||||||
|
|
||||||
aFit = AbstractFit()
|
|
||||||
aFit.mutations = cls.__getMutationData(lines)
|
|
||||||
|
|
||||||
nameChars = '[^,/\[\]]' # Characters which are allowed to be used in name
|
|
||||||
stubPattern = '^\[.+?\]$'
|
|
||||||
modulePattern = '^(?P<typeName>{0}+?)(,\s*(?P<chargeName>{0}+?))?(?P<offline>\s*{1})?(\s*\[(?P<mutation>\d+?)\])?$'.format(nameChars, OFFLINE_SUFFIX)
|
|
||||||
droneCargoPattern = '^(?P<typeName>{}+?) x(?P<amount>\d+?)$'.format(nameChars)
|
|
||||||
|
|
||||||
sections = []
|
|
||||||
for section in cls.__importSectionIter(lines):
|
|
||||||
for line in section.lines:
|
|
||||||
# Stub line
|
|
||||||
if re.match(stubPattern, line):
|
|
||||||
section.itemSpecs.append(None)
|
|
||||||
continue
|
|
||||||
# Items with quantity specifier
|
|
||||||
m = re.match(droneCargoPattern, line)
|
|
||||||
if m:
|
|
||||||
try:
|
|
||||||
itemSpec = MultiItemSpec(m.group('typeName'))
|
|
||||||
# Items which cannot be fetched are considered as stubs
|
|
||||||
except EftImportError:
|
|
||||||
section.itemSpecs.append(None)
|
|
||||||
else:
|
|
||||||
itemSpec.amount = int(m.group('amount'))
|
|
||||||
section.itemSpecs.append(itemSpec)
|
|
||||||
continue
|
|
||||||
# All other items
|
|
||||||
m = re.match(modulePattern, line)
|
|
||||||
if m:
|
|
||||||
try:
|
|
||||||
itemSpec = RegularItemSpec(m.group('typeName'), chargeName=m.group('chargeName'))
|
|
||||||
# Items which cannot be fetched are considered as stubs
|
|
||||||
except EftImportError:
|
|
||||||
section.itemSpecs.append(None)
|
|
||||||
else:
|
|
||||||
if m.group('offline'):
|
|
||||||
itemSpec.offline = True
|
|
||||||
if m.group('mutation'):
|
|
||||||
itemSpec.mutationIdx = int(m.group('mutation'))
|
|
||||||
section.itemSpecs.append(itemSpec)
|
|
||||||
continue
|
|
||||||
clearTail(section.itemSpecs)
|
|
||||||
sections.append(section)
|
|
||||||
|
|
||||||
|
|
||||||
hasDroneBay = any(s.isDroneBay for s in sections)
|
|
||||||
hasFighterBay = any(s.isFighterBay for s in sections)
|
|
||||||
for section in sections:
|
|
||||||
if section.isModuleRack:
|
|
||||||
aFit.addModules(section.itemSpecs)
|
|
||||||
elif section.isImplantRack:
|
|
||||||
for itemSpec in section.itemSpecs:
|
|
||||||
aFit.addImplant(itemSpec)
|
|
||||||
elif section.isDroneBay:
|
|
||||||
for itemSpec in section.itemSpecs:
|
|
||||||
aFit.addDrone(itemSpec)
|
|
||||||
elif section.isFighterBay:
|
|
||||||
for itemSpec in section.itemSpecs:
|
|
||||||
aFit.addFighter(itemSpec)
|
|
||||||
elif section.isCargoHold:
|
|
||||||
for itemSpec in section.itemSpecs:
|
|
||||||
aFit.addCargo(itemSpec)
|
|
||||||
# Mix between different kinds of item specs (can happen when some
|
|
||||||
# blank lines are removed)
|
|
||||||
else:
|
|
||||||
for itemSpec in section.itemSpecs:
|
|
||||||
if itemSpec is None:
|
|
||||||
continue
|
|
||||||
if itemSpec.isModule:
|
|
||||||
aFit.addModule(itemSpec)
|
|
||||||
elif itemSpec.isImplant:
|
|
||||||
aFit.addImplant(itemSpec)
|
|
||||||
elif itemSpec.isDrone and not hasDroneBay:
|
|
||||||
aFit.addDrone(itemSpec)
|
|
||||||
elif itemSpec.isFighter and not hasFighterBay:
|
|
||||||
aFit.addFighter(itemSpec)
|
|
||||||
elif itemSpec.isCargo:
|
|
||||||
aFit.addCargo(itemSpec)
|
|
||||||
|
|
||||||
# Subsystems first because they modify slot amount
|
|
||||||
for m in aFit.subsystems:
|
|
||||||
if m is None:
|
|
||||||
dummy = Module.buildEmpty(aFit.getSlotByContainer(aFit.subsystems))
|
|
||||||
dummy.owner = fit
|
|
||||||
fit.modules.appendIgnoreEmpty(dummy)
|
|
||||||
elif m.fits(fit):
|
|
||||||
m.owner = fit
|
|
||||||
fit.modules.appendIgnoreEmpty(m)
|
|
||||||
svcFit.getInstance().recalc(fit)
|
|
||||||
|
|
||||||
# Other stuff
|
|
||||||
for modRack in (
|
|
||||||
aFit.rigs,
|
|
||||||
aFit.services,
|
|
||||||
aFit.modulesHigh,
|
|
||||||
aFit.modulesMed,
|
|
||||||
aFit.modulesLow,
|
|
||||||
):
|
|
||||||
for m in modRack:
|
|
||||||
if m is None:
|
|
||||||
dummy = Module.buildEmpty(aFit.getSlotByContainer(modRack))
|
|
||||||
dummy.owner = fit
|
|
||||||
fit.modules.appendIgnoreEmpty(dummy)
|
|
||||||
elif m.fits(fit):
|
|
||||||
m.owner = fit
|
|
||||||
if not m.isValidState(m.state):
|
|
||||||
pyfalog.warning('EftPort.importEft: module {} cannot have state {}', m, m.state)
|
|
||||||
fit.modules.appendIgnoreEmpty(m)
|
|
||||||
for implant in aFit.implants:
|
|
||||||
fit.implants.append(implant)
|
|
||||||
for booster in aFit.boosters:
|
|
||||||
fit.boosters.append(booster)
|
|
||||||
for drone in aFit.drones.values():
|
|
||||||
fit.drones.append(drone)
|
|
||||||
for fighter in aFit.fighters:
|
|
||||||
fit.fighters.append(fighter)
|
|
||||||
for cargo in aFit.cargo.values():
|
|
||||||
fit.cargo.append(cargo)
|
|
||||||
|
|
||||||
return fit
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __prepareImportString(eftString):
|
|
||||||
lines = eftString.splitlines()
|
|
||||||
for i in range(len(lines)):
|
|
||||||
lines[i] = lines[i].strip()
|
|
||||||
while lines and not lines[0]:
|
|
||||||
del lines[0]
|
|
||||||
while lines and not lines[-1]:
|
|
||||||
del lines[-1]
|
|
||||||
return lines
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __getMutationData(lines):
|
|
||||||
data = {}
|
|
||||||
consumedIndices = set()
|
|
||||||
for i in range(len(lines)):
|
|
||||||
line = lines[i]
|
|
||||||
m = re.match('^\[(?P<ref>\d+)\]', line)
|
|
||||||
if m:
|
|
||||||
ref = int(m.group('ref'))
|
|
||||||
# Attempt to apply mutation is useless w/o mutaplasmid, so skip it
|
|
||||||
# altogether if we have no info on it
|
|
||||||
try:
|
|
||||||
mutaName = lines[i + 1]
|
|
||||||
except IndexError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
consumedIndices.add(i)
|
|
||||||
consumedIndices.add(i + 1)
|
|
||||||
# Get custom attribute values
|
|
||||||
mutaAttrs = {}
|
|
||||||
try:
|
|
||||||
mutaAttrsLine = lines[i + 2]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
consumedIndices.add(i + 2)
|
|
||||||
pairs = [p.strip() for p in mutaAttrsLine.split(',')]
|
|
||||||
for pair in pairs:
|
|
||||||
try:
|
|
||||||
attrName, value = pair.split(' ')
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
value = float(value)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
continue
|
|
||||||
attrInfo = getAttributeInfo(attrName.strip())
|
|
||||||
if attrInfo is None:
|
|
||||||
continue
|
|
||||||
mutaAttrs[attrInfo.ID] = value
|
|
||||||
mutaItem = fetchItem(mutaName)
|
|
||||||
if mutaItem is None:
|
|
||||||
continue
|
|
||||||
data[ref] = (mutaItem, mutaAttrs)
|
|
||||||
# If we got here, we have seen at least correct reference line and
|
|
||||||
# mutaplasmid name line
|
|
||||||
i += 2
|
|
||||||
# Bonus points for seeing correct attrs line. Worst case we
|
|
||||||
# will have to scan it once again
|
|
||||||
if mutaAttrs:
|
|
||||||
i += 1
|
|
||||||
# Cleanup the lines from mutaplasmid info
|
|
||||||
for i in sorted(consumedIndices, reverse=True):
|
|
||||||
del lines[i]
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __importSectionIter(lines):
|
|
||||||
section = Section()
|
|
||||||
for line in lines:
|
|
||||||
if not line:
|
|
||||||
if section.lines:
|
|
||||||
yield section
|
|
||||||
section = Section()
|
|
||||||
else:
|
|
||||||
section.lines.append(line)
|
|
||||||
if section.lines:
|
|
||||||
yield section
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __createFit(cls, lines):
|
|
||||||
"""Create fit and set top-level entity (ship or citadel)."""
|
|
||||||
fit = Fit()
|
|
||||||
header = lines.pop(0)
|
|
||||||
m = re.match('\[(?P<shipType>[\w\s]+),\s*(?P<fitName>.+)\]', header)
|
|
||||||
if not m:
|
|
||||||
pyfalog.warning('EftPort.importEft: corrupted fit header')
|
|
||||||
raise EftImportError
|
|
||||||
shipType = m.group('shipType').strip()
|
|
||||||
fitName = m.group('fitName').strip()
|
|
||||||
try:
|
|
||||||
ship = fetchItem(shipType)
|
|
||||||
try:
|
|
||||||
fit.ship = Ship(ship)
|
|
||||||
except ValueError:
|
|
||||||
fit.ship = Citadel(ship)
|
|
||||||
fit.name = fitName
|
|
||||||
except:
|
|
||||||
pyfalog.warning('EftPort.importEft: exception caught when parsing header')
|
|
||||||
raise EftImportError
|
|
||||||
return fit
|
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ from service.fit import Fit as svcFit
|
|||||||
import wx
|
import wx
|
||||||
|
|
||||||
from eos.saveddata.cargo import Cargo
|
from eos.saveddata.cargo import Cargo
|
||||||
from eos.saveddata.implant import Implant
|
|
||||||
from eos.saveddata.booster import Booster
|
|
||||||
from eos.saveddata.drone import Drone
|
from eos.saveddata.drone import Drone
|
||||||
from eos.saveddata.fighter import Fighter
|
from eos.saveddata.fighter import Fighter
|
||||||
from eos.saveddata.module import Module, State, Slot
|
from eos.saveddata.module import Module, State, Slot
|
||||||
@@ -52,6 +50,7 @@ from abc import ABCMeta, abstractmethod
|
|||||||
|
|
||||||
from service.esi import Esi
|
from service.esi import Esi
|
||||||
from service.port.eft import EftPort, SLOT_ORDER as EFT_SLOT_ORDER
|
from service.port.eft import EftPort, SLOT_ORDER as EFT_SLOT_ORDER
|
||||||
|
from service.port.shared import processing_notify
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
@@ -235,10 +234,26 @@ class Port(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def backupFits(path, iportuser):
|
def backupFits(path, iportuser):
|
||||||
pyfalog.debug("Starting backup fits thread.")
|
pyfalog.debug("Starting backup fits thread.")
|
||||||
# thread = FitBackupThread(path, callback)
|
|
||||||
# thread.start()
|
def backupFitsWorkerFunc(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(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.")
|
||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=PortProcessing.backupFits,
|
target=backupFitsWorkerFunc,
|
||||||
args=(path, iportuser)
|
args=(path, iportuser)
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
@@ -251,10 +266,14 @@ class Port(object):
|
|||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
pyfalog.debug("Starting import fits thread.")
|
pyfalog.debug("Starting import fits thread.")
|
||||||
# thread = FitImportThread(paths, iportuser)
|
|
||||||
# thread.start()
|
def importFitsFromFileWorkerFunc(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)
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=PortProcessing.importFitsFromFile,
|
target=importFitsFromFileWorkerFunc,
|
||||||
args=(paths, iportuser)
|
args=(paths, iportuser)
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
@@ -275,7 +294,7 @@ class Port(object):
|
|||||||
if iportuser: # Pulse
|
if iportuser: # Pulse
|
||||||
msg = "Processing file:\n%s" % path
|
msg = "Processing file:\n%s" % path
|
||||||
pyfalog.debug(msg)
|
pyfalog.debug(msg)
|
||||||
PortProcessing.notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg)
|
processing_notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg)
|
||||||
# wx.CallAfter(callback, 1, msg)
|
# wx.CallAfter(callback, 1, msg)
|
||||||
|
|
||||||
with open(path, "rb") as file_:
|
with open(path, "rb") as file_:
|
||||||
@@ -310,7 +329,7 @@ class Port(object):
|
|||||||
# IDs.append(fit.ID)
|
# IDs.append(fit.ID)
|
||||||
if iportuser: # Pulse
|
if iportuser: # Pulse
|
||||||
pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits)
|
pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits)
|
||||||
PortProcessing.notify(
|
processing_notify(
|
||||||
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
|
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
|
||||||
"Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name)
|
"Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name)
|
||||||
)
|
)
|
||||||
@@ -621,196 +640,7 @@ class Port(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def importEftCfg(shipname, contents, iportuser=None):
|
def importEftCfg(shipname, contents, iportuser=None):
|
||||||
"""Handle import from EFT config store file"""
|
return EftPort.importEftCfg(shipname, contents, iportuser)
|
||||||
|
|
||||||
# Check if we have such ship in database, bail if we don't
|
|
||||||
sMkt = Market.getInstance()
|
|
||||||
try:
|
|
||||||
sMkt.getItem(shipname)
|
|
||||||
except:
|
|
||||||
return [] # empty list is expected
|
|
||||||
|
|
||||||
fits = [] # List for fits
|
|
||||||
fitIndices = [] # List for starting line numbers for each fit
|
|
||||||
lines = re.split('[\n\r]+', contents) # Separate string into lines
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
# Detect fit header
|
|
||||||
if line[:1] == "[" and line[-1:] == "]":
|
|
||||||
# Line index where current fit starts
|
|
||||||
startPos = lines.index(line)
|
|
||||||
fitIndices.append(startPos)
|
|
||||||
|
|
||||||
for i, startPos in enumerate(fitIndices):
|
|
||||||
# End position is last file line if we're trying to get it for last fit,
|
|
||||||
# or start position of next fit minus 1
|
|
||||||
endPos = len(lines) if i == len(fitIndices) - 1 else fitIndices[i + 1]
|
|
||||||
|
|
||||||
# Finally, get lines for current fitting
|
|
||||||
fitLines = lines[startPos:endPos]
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create fit object
|
|
||||||
fitobj = Fit()
|
|
||||||
# Strip square brackets and pull out a fit name
|
|
||||||
fitobj.name = fitLines[0][1:-1]
|
|
||||||
# Assign ship to fitting
|
|
||||||
try:
|
|
||||||
fitobj.ship = Ship(sMkt.getItem(shipname))
|
|
||||||
except ValueError:
|
|
||||||
fitobj.ship = Citadel(sMkt.getItem(shipname))
|
|
||||||
|
|
||||||
moduleList = []
|
|
||||||
for x in range(1, len(fitLines)):
|
|
||||||
line = fitLines[x]
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
entityState = misc.group(2)
|
|
||||||
entityData = misc.group(3)
|
|
||||||
if entityType == "Drones":
|
|
||||||
droneData = re.match("(.+),([0-9]+)", entityData)
|
|
||||||
# Get drone name and attempt to detect drone number
|
|
||||||
droneName = droneData.group(1) if droneData else entityData
|
|
||||||
droneAmount = int(droneData.group(2)) if droneData else 1
|
|
||||||
# Bail if we can't get item or it's not from drone category
|
|
||||||
try:
|
|
||||||
droneItem = sMkt.getItem(droneName, eager="group.category")
|
|
||||||
except:
|
|
||||||
pyfalog.warning("Cannot get item.")
|
|
||||||
continue
|
|
||||||
if droneItem.category.name == "Drone":
|
|
||||||
# Add drone to the fitting
|
|
||||||
d = Drone(droneItem)
|
|
||||||
d.amount = droneAmount
|
|
||||||
if entityState == "Active":
|
|
||||||
d.amountActive = droneAmount
|
|
||||||
elif entityState == "Inactive":
|
|
||||||
d.amountActive = 0
|
|
||||||
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
|
|
||||||
fitobj.fighters.append(ft)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif entityType == "Implant":
|
|
||||||
# Bail if we can't get item or it's not from implant category
|
|
||||||
try:
|
|
||||||
implantItem = sMkt.getItem(entityData, eager="group.category")
|
|
||||||
except:
|
|
||||||
pyfalog.warning("Cannot get item.")
|
|
||||||
continue
|
|
||||||
if implantItem.category.name != "Implant":
|
|
||||||
continue
|
|
||||||
# Add implant to the fitting
|
|
||||||
imp = Implant(implantItem)
|
|
||||||
if entityState == "Active":
|
|
||||||
imp.active = True
|
|
||||||
elif entityState == "Inactive":
|
|
||||||
imp.active = False
|
|
||||||
fitobj.implants.append(imp)
|
|
||||||
elif entityType == "Booster":
|
|
||||||
# Bail if we can't get item or it's not from implant category
|
|
||||||
try:
|
|
||||||
boosterItem = sMkt.getItem(entityData, eager="group.category")
|
|
||||||
except:
|
|
||||||
pyfalog.warning("Cannot get item.")
|
|
||||||
continue
|
|
||||||
# All boosters have implant category
|
|
||||||
if boosterItem.category.name != "Implant":
|
|
||||||
continue
|
|
||||||
# Add booster to the fitting
|
|
||||||
b = Booster(boosterItem)
|
|
||||||
if entityState == "Active":
|
|
||||||
b.active = True
|
|
||||||
elif entityState == "Inactive":
|
|
||||||
b.active = False
|
|
||||||
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))
|
|
||||||
cargoName = cargoData.group(1) if cargoData else cargo.group(1)
|
|
||||||
cargoAmount = int(cargoData.group(2)) if cargoData else 1
|
|
||||||
# Bail if we can't get item
|
|
||||||
try:
|
|
||||||
item = sMkt.getItem(cargoName)
|
|
||||||
except:
|
|
||||||
pyfalog.warning("Cannot get item.")
|
|
||||||
continue
|
|
||||||
# Add Cargo to the fitting
|
|
||||||
c = Cargo(item)
|
|
||||||
c.amount = cargoAmount
|
|
||||||
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
|
|
||||||
chargeName = withCharge.group(2) if withCharge else None
|
|
||||||
# If we can't get module item, skip it
|
|
||||||
try:
|
|
||||||
modItem = sMkt.getItem(modName)
|
|
||||||
except:
|
|
||||||
pyfalog.warning("Cannot get item.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Create module
|
|
||||||
m = Module(modItem)
|
|
||||||
|
|
||||||
# Add subsystems before modules to make sure T3 cruisers have subsystems installed
|
|
||||||
if modItem.category.name == "Subsystem":
|
|
||||||
if m.fits(fitobj):
|
|
||||||
fitobj.modules.append(m)
|
|
||||||
else:
|
|
||||||
m.owner = fitobj
|
|
||||||
# Activate mod if it is activable
|
|
||||||
if m.isValidState(State.ACTIVE):
|
|
||||||
m.state = State.ACTIVE
|
|
||||||
# Add charge to mod if applicable, on any errors just don't add anything
|
|
||||||
if chargeName:
|
|
||||||
try:
|
|
||||||
chargeItem = sMkt.getItem(chargeName, eager="group.category")
|
|
||||||
if chargeItem.category.name == "Charge":
|
|
||||||
m.charge = chargeItem
|
|
||||||
except:
|
|
||||||
pyfalog.warning("Cannot get item.")
|
|
||||||
pass
|
|
||||||
# Append module to fit
|
|
||||||
moduleList.append(m)
|
|
||||||
|
|
||||||
# Recalc to get slot numbers correct for T3 cruisers
|
|
||||||
svcFit.getInstance().recalc(fitobj)
|
|
||||||
|
|
||||||
for module in moduleList:
|
|
||||||
if module.fits(fitobj):
|
|
||||||
fitobj.modules.append(module)
|
|
||||||
|
|
||||||
# Append fit to list of fits
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Skip fit silently if we get an exception
|
|
||||||
except Exception as e:
|
|
||||||
pyfalog.error("Caught exception on fit.")
|
|
||||||
pyfalog.error(e)
|
|
||||||
pass
|
|
||||||
|
|
||||||
return fits
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def importXml(text, iportuser=None):
|
def importXml(text, iportuser=None):
|
||||||
@@ -901,7 +731,7 @@ class Port(object):
|
|||||||
|
|
||||||
fit_list.append(fitobj)
|
fit_list.append(fitobj)
|
||||||
if iportuser: # NOTE: Send current processing status
|
if iportuser: # NOTE: Send current processing status
|
||||||
PortProcessing.notify(
|
processing_notify(
|
||||||
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
|
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
|
||||||
"Processing %s\n%s" % (fitobj.ship.name, fitobj.name)
|
"Processing %s\n%s" % (fitobj.ship.name, fitobj.name)
|
||||||
)
|
)
|
||||||
@@ -912,10 +742,6 @@ class Port(object):
|
|||||||
def exportEft(cls, fit, options):
|
def exportEft(cls, fit, options):
|
||||||
return EftPort.exportEft(fit, options)
|
return EftPort.exportEft(fit, options)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def exportEftImps(cls, fit):
|
|
||||||
return EftPort.exportEft(fit, mutations=False, implants=True)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def exportDna(fit):
|
def exportDna(fit):
|
||||||
dna = str(fit.shipID)
|
dna = str(fit.shipID)
|
||||||
@@ -1065,7 +891,7 @@ class Port(object):
|
|||||||
continue
|
continue
|
||||||
finally:
|
finally:
|
||||||
if iportuser:
|
if iportuser:
|
||||||
PortProcessing.notify(
|
processing_notify(
|
||||||
iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE,
|
iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE,
|
||||||
(i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name))
|
(i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name))
|
||||||
)
|
)
|
||||||
@@ -1118,36 +944,3 @@ class Port(object):
|
|||||||
export = export[:-1]
|
export = export[:-1]
|
||||||
|
|
||||||
return export
|
return export
|
||||||
|
|
||||||
|
|
||||||
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(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)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def notify(iportuser, flag, data):
|
|
||||||
if not iportuser.on_port_processing(flag, data):
|
|
||||||
raise UserCancelException
|
|
||||||
|
|||||||
69
service/port/shared.py
Normal file
69
service/port/shared.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Copyright (C) 2014 Ryan Holmes
|
||||||
|
#
|
||||||
|
# This file is part of pyfa.
|
||||||
|
#
|
||||||
|
# pyfa is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pyfa is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def processing_notify(iportuser, flag, data):
|
||||||
|
if not iportuser.on_port_processing(flag, data):
|
||||||
|
raise UserCancelException
|
||||||
Reference in New Issue
Block a user