Get rid of PortProcessing object

This commit is contained in:
DarkPhoenix
2018-08-30 15:23:24 +03:00
parent ba157af496
commit 302cab54fd
3 changed files with 643 additions and 587 deletions

View File

@@ -35,6 +35,7 @@ from eos.saveddata.fit import Fit
from gui.utils.numberFormatter import roundToPrec
from service.fit import Fit as svcFit
from service.market import Market
from service.port.shared import IPortUser, processing_notify
from enum import Enum
@@ -59,10 +60,550 @@ EFT_OPTIONS = {
"name": "Mutated Attributes",
"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):
sMkt = Market.getInstance()
eager = 'group.category' if eagerCat else None
@@ -340,350 +881,3 @@ class AbstractFit:
if itemSpec.item not in self.cargo:
self.cargo[itemSpec.item] = Cargo(itemSpec.item)
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

View File

@@ -38,8 +38,6 @@ from service.fit import Fit as svcFit
import wx
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.fighter import Fighter
from eos.saveddata.module import Module, State, Slot
@@ -52,6 +50,7 @@ from abc import ABCMeta, abstractmethod
from service.esi import Esi
from service.port.eft import EftPort, SLOT_ORDER as EFT_SLOT_ORDER
from service.port.shared import processing_notify
from collections import OrderedDict
@@ -235,10 +234,26 @@ class Port(object):
@staticmethod
def backupFits(path, iportuser):
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(
target=PortProcessing.backupFits,
target=backupFitsWorkerFunc,
args=(path, iportuser)
).start()
@@ -251,10 +266,14 @@ class Port(object):
:rtype: None
"""
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(
target=PortProcessing.importFitsFromFile,
target=importFitsFromFileWorkerFunc,
args=(paths, iportuser)
).start()
@@ -275,7 +294,7 @@ class Port(object):
if iportuser: # Pulse
msg = "Processing file:\n%s" % path
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)
with open(path, "rb") as file_:
@@ -310,7 +329,7 @@ class Port(object):
# IDs.append(fit.ID)
if iportuser: # Pulse
pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits)
PortProcessing.notify(
processing_notify(
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
"Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name)
)
@@ -621,196 +640,7 @@ class Port(object):
@staticmethod
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
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
return EftPort.importEftCfg(shipname, contents, iportuser)
@staticmethod
def importXml(text, iportuser=None):
@@ -901,7 +731,7 @@ class Port(object):
fit_list.append(fitobj)
if iportuser: # NOTE: Send current processing status
PortProcessing.notify(
processing_notify(
iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
"Processing %s\n%s" % (fitobj.ship.name, fitobj.name)
)
@@ -912,10 +742,6 @@ class Port(object):
def exportEft(cls, fit, options):
return EftPort.exportEft(fit, options)
@classmethod
def exportEftImps(cls, fit):
return EftPort.exportEft(fit, mutations=False, implants=True)
@staticmethod
def exportDna(fit):
dna = str(fit.shipID)
@@ -1065,7 +891,7 @@ class Port(object):
continue
finally:
if iportuser:
PortProcessing.notify(
processing_notify(
iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE,
(i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name))
)
@@ -1118,36 +944,3 @@ class Port(object):
export = export[:-1]
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
View 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