diff --git a/service/port/dna.py b/service/port/dna.py new file mode 100644 index 000000000..bd2645ef8 --- /dev/null +++ b/service/port/dna.py @@ -0,0 +1,176 @@ +# ============================================================================= +# 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 . +# ============================================================================= + + +import re +from collections import OrderedDict + +from logbook import Logger + +from eos.saveddata.cargo import Cargo +from eos.saveddata.citadel import Citadel +from eos.saveddata.drone import Drone +from eos.saveddata.fighter import Fighter +from eos.saveddata.fit import Fit +from eos.saveddata.module import Module, State, Slot +from eos.saveddata.ship import Ship +from service.fit import Fit as svcFit +from service.market import Market + + +pyfalog = Logger(__name__) + + +def importDna(string): + sMkt = Market.getInstance() + + ids = list(map(int, re.findall(r'\d+', string))) + for id_ in ids: + try: + try: + try: + Ship(sMkt.getItem(sMkt.getItem(id_))) + except ValueError: + Citadel(sMkt.getItem(sMkt.getItem(id_))) + except ValueError: + Citadel(sMkt.getItem(id_)) + string = string[string.index(str(id_)):] + break + except: + pyfalog.warning("Exception caught in importDna") + pass + string = string[:string.index("::") + 2] + info = string.split(":") + + f = Fit() + try: + try: + f.ship = Ship(sMkt.getItem(int(info[0]))) + except ValueError: + f.ship = Citadel(sMkt.getItem(int(info[0]))) + f.name = "{0} - DNA Imported".format(f.ship.item.name) + except UnicodeEncodeError: + def logtransform(s_): + if len(s_) > 10: + return s_[:10] + "..." + return s_ + + pyfalog.exception("Couldn't import ship data {0}", [logtransform(s) for s in info]) + return None + + moduleList = [] + for itemInfo in info[1:]: + if itemInfo: + itemID, amount = itemInfo.split(";") + item = sMkt.getItem(int(itemID), eager="group.category") + + if item.category.name == "Drone": + d = Drone(item) + d.amount = int(amount) + f.drones.append(d) + elif item.category.name == "Fighter": + ft = Fighter(item) + ft.amount = int(amount) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize + if ft.fits(f): + f.fighters.append(ft) + elif item.category.name == "Charge": + c = Cargo(item) + c.amount = int(amount) + f.cargo.append(c) + else: + for i in range(int(amount)): + try: + m = Module(item) + except: + pyfalog.warning("Exception caught in importDna") + continue + # Add subsystems before modules to make sure T3 cruisers have subsystems installed + if item.category.name == "Subsystem": + if m.fits(f): + f.modules.append(m) + else: + m.owner = f + if m.isValidState(State.ACTIVE): + m.state = State.ACTIVE + moduleList.append(m) + + # Recalc to get slot numbers correct for T3 cruisers + svcFit.getInstance().recalc(f) + + for module in moduleList: + if module.fits(f): + module.owner = f + if module.isValidState(State.ACTIVE): + module.state = State.ACTIVE + f.modules.append(module) + + return f + + +def exportDna(fit): + dna = str(fit.shipID) + subsystems = [] # EVE cares which order you put these in + mods = OrderedDict() + charges = OrderedDict() + sFit = svcFit.getInstance() + for mod in fit.modules: + if not mod.isEmpty: + if mod.slot == Slot.SUBSYSTEM: + subsystems.append(mod) + continue + if mod.itemID not in mods: + mods[mod.itemID] = 0 + mods[mod.itemID] += 1 + + if mod.charge and sFit.serviceFittingOptions["exportCharges"]: + if mod.chargeID not in charges: + charges[mod.chargeID] = 0 + # `or 1` because some charges (ie scripts) are without qty + charges[mod.chargeID] += mod.numCharges or 1 + + for subsystem in sorted(subsystems, key=lambda mod_: mod_.getModifiedItemAttr("subSystemSlot")): + dna += ":{0};1".format(subsystem.itemID) + + for mod in mods: + dna += ":{0};{1}".format(mod, mods[mod]) + + for drone in fit.drones: + dna += ":{0};{1}".format(drone.itemID, drone.amount) + + for fighter in fit.fighters: + dna += ":{0};{1}".format(fighter.itemID, fighter.amountActive) + + for fighter in fit.fighters: + dna += ":{0};{1}".format(fighter.itemID, fighter.amountActive) + + for cargo in fit.cargo: + # DNA format is a simple/dumb format. As CCP uses the slot information of the item itself + # without designating slots in the DNA standard, we need to make sure we only include + # charges in the DNA export. If modules were included, the EVE Client will interpret these + # as being "Fitted" to whatever slot they are for, and it causes an corruption error in the + # client when trying to save the fit + if cargo.item.category.name == "Charge": + if cargo.item.ID not in charges: + charges[cargo.item.ID] = 0 + charges[cargo.item.ID] += cargo.amount + + for charge in charges: + dna += ":{0};{1}".format(charge, charges[charge]) + + return dna + "::" diff --git a/service/port/port.py b/service/port/port.py index 3fc08aa25..29b5eba81 100644 --- a/service/port/port.py +++ b/service/port/port.py @@ -46,12 +46,11 @@ from eos.saveddata.citadel import Citadel from eos.saveddata.fit import Fit, ImplantLocation from service.market import Market from utils.strfunctions import sequential_rep, replace_ltgt -from abc import ABCMeta, abstractmethod from service.esi import Esi +from service.port.dna import exportDna, importDna from service.port.eft import EftPort, SLOT_ORDER as EFT_SLOT_ORDER -from service.port.shared import processing_notify -from collections import OrderedDict +from service.port.shared import IPortUser, UserCancelException, processing_notify class ESIExportException(Exception): @@ -165,50 +164,6 @@ def _resolve_module(hardware, sMkt, b_localized): return item -class UserCancelException(Exception): - """when user cancel on port processing.""" - pass - - -class IPortUser(metaclass=ABCMeta): - - ID_PULSE = 1 - # Pulse the progress bar - ID_UPDATE = ID_PULSE << 1 - # Replace message with data: update messate - ID_DONE = ID_PULSE << 2 - # open fits: import process done - ID_ERROR = ID_PULSE << 3 - # display error: raise some error - - PROCESS_IMPORT = ID_PULSE << 4 - # means import process. - PROCESS_EXPORT = ID_PULSE << 5 - # means import process. - - @abstractmethod - def on_port_processing(self, action, data=None): - """ - While importing fits from file, the logic calls back to this function to - update progress bar to show activity. XML files can contain multiple - ships with multiple fits, whereas EFT cfg files contain many fits of - a single ship. When iterating through the files, we update the message - when we start a new file, and then Pulse the progress bar with every fit - that is processed. - - action : a flag that lets us know how to deal with :data - None: Pulse the progress bar - 1: Replace message with data - other: Close dialog and handle based on :action (-1 open fits, -2 display error) - """ - - """return: True is continue process, False is cancel.""" - pass - - def on_port_process_start(self): - pass - - class Port(object): """Service which houses all import/export format functions""" instance = None @@ -272,6 +227,7 @@ class Port(object): 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=importFitsFromFileWorkerFunc, args=(paths, iportuser) @@ -548,100 +504,6 @@ class Port(object): return fitobj - @staticmethod - def importDna(string): - sMkt = Market.getInstance() - - ids = list(map(int, re.findall(r'\d+', string))) - for id_ in ids: - try: - try: - try: - Ship(sMkt.getItem(sMkt.getItem(id_))) - except ValueError: - Citadel(sMkt.getItem(sMkt.getItem(id_))) - except ValueError: - Citadel(sMkt.getItem(id_)) - string = string[string.index(str(id_)):] - break - except: - pyfalog.warning("Exception caught in importDna") - pass - string = string[:string.index("::") + 2] - info = string.split(":") - - f = Fit() - try: - try: - f.ship = Ship(sMkt.getItem(int(info[0]))) - except ValueError: - f.ship = Citadel(sMkt.getItem(int(info[0]))) - f.name = "{0} - DNA Imported".format(f.ship.item.name) - except UnicodeEncodeError: - def logtransform(s_): - if len(s_) > 10: - return s_[:10] + "..." - return s_ - - pyfalog.exception("Couldn't import ship data {0}", [logtransform(s) for s in info]) - return None - - moduleList = [] - for itemInfo in info[1:]: - if itemInfo: - itemID, amount = itemInfo.split(";") - item = sMkt.getItem(int(itemID), eager="group.category") - - if item.category.name == "Drone": - d = Drone(item) - d.amount = int(amount) - f.drones.append(d) - elif item.category.name == "Fighter": - ft = Fighter(item) - ft.amount = int(amount) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize - if ft.fits(f): - f.fighters.append(ft) - elif item.category.name == "Charge": - c = Cargo(item) - c.amount = int(amount) - f.cargo.append(c) - else: - for i in range(int(amount)): - try: - m = Module(item) - except: - pyfalog.warning("Exception caught in importDna") - continue - # Add subsystems before modules to make sure T3 cruisers have subsystems installed - if item.category.name == "Subsystem": - if m.fits(f): - f.modules.append(m) - else: - m.owner = f - if m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - moduleList.append(m) - - # Recalc to get slot numbers correct for T3 cruisers - svcFit.getInstance().recalc(f) - - for module in moduleList: - if module.fits(f): - module.owner = f - if module.isValidState(State.ACTIVE): - module.state = State.ACTIVE - f.modules.append(module) - - return f - - @staticmethod - def importEft(eftString): - return EftPort.importEft(eftString) - - @staticmethod - def importEftCfg(shipname, contents, iportuser=None): - return EftPort.importEftCfg(shipname, contents, iportuser) - @staticmethod def importXml(text, iportuser=None): # type: (str, IPortUser) -> list[eos.saveddata.fit.Fit] @@ -738,63 +600,6 @@ class Port(object): return fit_list - @classmethod - def exportEft(cls, fit, options): - return EftPort.exportEft(fit, options) - - @staticmethod - def exportDna(fit): - dna = str(fit.shipID) - subsystems = [] # EVE cares which order you put these in - mods = OrderedDict() - charges = OrderedDict() - sFit = svcFit.getInstance() - for mod in fit.modules: - if not mod.isEmpty: - if mod.slot == Slot.SUBSYSTEM: - subsystems.append(mod) - continue - if mod.itemID not in mods: - mods[mod.itemID] = 0 - mods[mod.itemID] += 1 - - if mod.charge and sFit.serviceFittingOptions["exportCharges"]: - if mod.chargeID not in charges: - charges[mod.chargeID] = 0 - # `or 1` because some charges (ie scripts) are without qty - charges[mod.chargeID] += mod.numCharges or 1 - - for subsystem in sorted(subsystems, key=lambda mod_: mod_.getModifiedItemAttr("subSystemSlot")): - dna += ":{0};1".format(subsystem.itemID) - - for mod in mods: - dna += ":{0};{1}".format(mod, mods[mod]) - - for drone in fit.drones: - dna += ":{0};{1}".format(drone.itemID, drone.amount) - - for fighter in fit.fighters: - dna += ":{0};{1}".format(fighter.itemID, fighter.amountActive) - - for fighter in fit.fighters: - dna += ":{0};{1}".format(fighter.itemID, fighter.amountActive) - - for cargo in fit.cargo: - # DNA format is a simple/dumb format. As CCP uses the slot information of the item itself - # without designating slots in the DNA standard, we need to make sure we only include - # charges in the DNA export. If modules were included, the EVE Client will interpret these - # as being "Fitted" to whatever slot they are for, and it causes an corruption error in the - # client when trying to save the fit - if cargo.item.category.name == "Charge": - if cargo.item.ID not in charges: - charges[cargo.item.ID] = 0 - charges[cargo.item.ID] += cargo.amount - - for charge in charges: - dna += ":{0};{1}".format(charge, charges[charge]) - - return dna + "::" - @staticmethod def exportXml(iportuser=None, *fits): doc = xml.dom.minidom.Document() @@ -944,3 +749,25 @@ class Port(object): export = export[:-1] return export + + # EFT-related methods + @staticmethod + def importEft(eftString): + return EftPort.importEft(eftString) + + @staticmethod + def importEftCfg(shipname, contents, iportuser=None): + return EftPort.importEftCfg(shipname, contents, iportuser) + + @classmethod + def exportEft(cls, fit, options): + return EftPort.exportEft(fit, options) + + # DNA-related methods + @staticmethod + def importDna(string): + return importDna(string) + + @staticmethod + def exportDna(fit): + return exportDna(fit) diff --git a/service/port/shared.py b/service/port/shared.py index 39838a907..a21a81d63 100644 --- a/service/port/shared.py +++ b/service/port/shared.py @@ -56,8 +56,9 @@ class IPortUser(metaclass=ABCMeta): 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. """ + + """return: True is continue process, False is cancel.""" pass def on_port_process_start(self):