diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py
index 90e757788..c6ee5c70b 100644
--- a/eos/db/gamedata/queries.py
+++ b/eos/db/gamedata/queries.py
@@ -396,6 +396,21 @@ def getAbyssalTypes():
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
+@cachedQuery(1, "itemID")
+def getDynamicItem(itemID, eager=None):
+ try:
+ if isinstance(itemID, int):
+ if eager is None:
+ result = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == itemID).one()
+ else:
+ result = gamedata_session.query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
+ else:
+ raise TypeError("Need integer as argument")
+ except exc.NoResultFound:
+ result = None
+ return result
+
+
def getRequiredFor(itemID, attrMapping):
Attribute1 = aliased(Attribute)
Attribute2 = aliased(Attribute)
diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py
index 194429d31..3b5206185 100644
--- a/eos/effectHandlerHelpers.py
+++ b/eos/effectHandlerHelpers.py
@@ -114,6 +114,7 @@ class HandledList(list):
class HandledModuleList(HandledList):
+
def append(self, mod):
emptyPosition = float("Inf")
for i in range(len(self)):
@@ -131,6 +132,9 @@ class HandledModuleList(HandledList):
self.remove(mod)
return
+ self.appendIgnoreEmpty(mod)
+
+ def appendIgnoreEmpty(self, mod):
mod.position = len(self)
HandledList.append(self, mod)
if mod.isInvalid:
diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py
index 9f5291026..10eff2647 100644
--- a/gui/copySelectDialog.py
+++ b/gui/copySelectDialog.py
@@ -20,51 +20,84 @@
# noinspection PyPackageRequirements
import wx
+from service.port.eft import EFT_OPTIONS
+from service.settings import SettingsProvider
class CopySelectDialog(wx.Dialog):
copyFormatEft = 0
- copyFormatEftImps = 1
- copyFormatXml = 2
- copyFormatDna = 3
- copyFormatEsi = 4
- copyFormatMultiBuy = 5
- copyFormatEfs = 6
+ copyFormatXml = 1
+ copyFormatDna = 2
+ copyFormatEsi = 3
+ copyFormatMultiBuy = 4
+ copyFormatEfs = 5
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1),
style=wx.DEFAULT_DIALOG_STYLE)
mainSizer = wx.BoxSizer(wx.VERTICAL)
- copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "ESI", "MultiBuy", "EFS"]
- copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format",
- CopySelectDialog.copyFormatEftImps: "EFT text format",
- CopySelectDialog.copyFormatXml: "EVE native XML format",
- CopySelectDialog.copyFormatDna: "A one-line text format",
- CopySelectDialog.copyFormatEsi: "A JSON format used for ESI",
- CopySelectDialog.copyFormatMultiBuy: "MultiBuy text format",
- CopySelectDialog.copyFormatEfs: "JSON data format used by EFS"}
- selector = wx.RadioBox(self, wx.ID_ANY, label="Copy to the clipboard using:", choices=copyFormats,
- style=wx.RA_SPECIFY_ROWS)
- selector.Bind(wx.EVT_RADIOBOX, self.Selected)
- for format, tooltip in copyFormatTooltips.items():
- selector.SetItemToolTip(format, tooltip)
+ self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": 0, "options": 0})
- self.copyFormat = CopySelectDialog.copyFormatEft
- selector.SetSelection(self.copyFormat)
+ self.copyFormats = {
+ "EFT": CopySelectDialog.copyFormatEft,
+ "XML": CopySelectDialog.copyFormatXml,
+ "DNA": CopySelectDialog.copyFormatDna,
+ "ESI": CopySelectDialog.copyFormatEsi,
+ "MultiBuy": CopySelectDialog.copyFormatMultiBuy,
+ "EFS": CopySelectDialog.copyFormatEfs
+ }
- mainSizer.Add(selector, 0, wx.EXPAND | wx.ALL, 5)
+ self.options = {}
+
+ for i, format in enumerate(self.copyFormats.keys()):
+ if i == 0:
+ rdo = wx.RadioButton(self, wx.ID_ANY, format, style=wx.RB_GROUP)
+ else:
+ rdo = wx.RadioButton(self, wx.ID_ANY, format)
+ rdo.Bind(wx.EVT_RADIOBUTTON, self.Selected)
+ if self.settings['format'] == self.copyFormats[format]:
+ rdo.SetValue(True)
+ self.copyFormat = self.copyFormats[format]
+ mainSizer.Add(rdo, 0, wx.EXPAND | wx.ALL, 5)
+
+ if format == "EFT":
+ bsizer = wx.BoxSizer(wx.VERTICAL)
+
+ for x, v in EFT_OPTIONS.items():
+ ch = wx.CheckBox(self, -1, v['name'])
+ self.options[x] = ch
+ if self.settings['options'] & x:
+ ch.SetValue(True)
+ bsizer.Add(ch, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
+ mainSizer.Add(bsizer, 1, wx.EXPAND | wx.LEFT, 20)
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
if buttonSizer:
mainSizer.Add(buttonSizer, 0, wx.EXPAND | wx.ALL, 5)
+ self.toggleOptions()
self.SetSizer(mainSizer)
self.Fit()
self.Center()
def Selected(self, event):
- self.copyFormat = event.GetSelection()
+ obj = event.GetEventObject()
+ format = obj.GetLabel()
+ self.copyFormat = self.copyFormats[format]
+ self.toggleOptions()
+ self.Fit()
+
+ def toggleOptions(self):
+ for ch in self.options.values():
+ ch.Enable(self.GetSelected() == CopySelectDialog.copyFormatEft)
def GetSelected(self):
return self.copyFormat
+
+ def GetOptions(self):
+ i = 0
+ for x, v in self.options.items():
+ if v.IsChecked():
+ i = i ^ x
+ return i
diff --git a/gui/esiFittings.py b/gui/esiFittings.py
index af3da77a5..c471a1334 100644
--- a/gui/esiFittings.py
+++ b/gui/esiFittings.py
@@ -15,7 +15,7 @@ import gui.globalEvents as GE
from logbook import Logger
from service.esi import Esi
from service.esiAccess import APIException
-from service.port import ESIExportException
+from service.port.esi import ESIExportException
pyfalog = Logger(__name__)
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index a4befdeda..b8277e8cb 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -76,8 +76,7 @@ from service.esiAccess import SsoMode
from eos.modifiedAttributeDict import ModifiedAttributeDict
from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues
from eos.db.saveddata.queries import getFit as db_getFit
-from service.port import Port, IPortUser
-from service.efsPort import EfsPort
+from service.port import Port, IPortUser, EfsPort
from service.settings import HTMLExportSettings
from time import gmtime, strftime
@@ -711,31 +710,31 @@ class MainFrame(wx.Frame):
else:
self.marketBrowser.search.Focus()
- def clipboardEft(self):
+ def clipboardEft(self, options):
fit = db_getFit(self.getActiveFit())
- toClipboard(Port.exportEft(fit))
+ toClipboard(Port.exportEft(fit, options))
- def clipboardEftImps(self):
+ def clipboardEftImps(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportEftImps(fit))
- def clipboardDna(self):
+ def clipboardDna(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportDna(fit))
- def clipboardEsi(self):
+ def clipboardEsi(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportESI(fit))
- def clipboardXml(self):
+ def clipboardXml(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportXml(None, fit))
- def clipboardMultiBuy(self):
+ def clipboardMultiBuy(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportMultiBuy(fit))
- def clipboardEfs(self):
+ def clipboardEfs(self, options):
fit = db_getFit(self.getActiveFit())
toClipboard(EfsPort.exportEfs(fit, 0))
@@ -750,7 +749,7 @@ class MainFrame(wx.Frame):
def exportToClipboard(self, event):
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,
- CopySelectDialog.copyFormatEftImps: self.clipboardEftImps,
+ # CopySelectDialog.copyFormatEftImps: self.clipboardEftImps,
CopySelectDialog.copyFormatXml: self.clipboardXml,
CopySelectDialog.copyFormatDna: self.clipboardDna,
CopySelectDialog.copyFormatEsi: self.clipboardEsi,
@@ -759,8 +758,13 @@ class MainFrame(wx.Frame):
dlg = CopySelectDialog(self)
dlg.ShowModal()
selected = dlg.GetSelected()
+ options = dlg.GetOptions()
- CopySelectDict[selected]()
+ settings = SettingsProvider.getInstance().getSettings("pyfaExport")
+ settings["format"] = selected
+ settings["options"] = options
+
+ CopySelectDict[selected](options)
try:
dlg.Destroy()
diff --git a/service/port.py b/service/port.py
deleted file mode 100644
index 5ad5c4ed3..000000000
--- a/service/port.py
+++ /dev/null
@@ -1,1399 +0,0 @@
-# =============================================================================
-# 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
-import os
-import xml.dom
-from logbook import Logger
-import collections
-import json
-import threading
-from bs4 import UnicodeDammit
-
-
-from codecs import open
-
-import xml.parsers.expat
-
-from eos import db
-from eos.db.gamedata.queries import getAttributeInfo
-from service.fit import Fit as svcFit
-
-# noinspection PyPackageRequirements
-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
-from eos.saveddata.ship import Ship
-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 collections import OrderedDict
-
-
-class ESIExportException(Exception):
- pass
-
-
-pyfalog = Logger(__name__)
-
-EFT_SLOT_ORDER = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE]
-INV_FLAGS = {
- Slot.LOW: 11,
- Slot.MED: 19,
- Slot.HIGH: 27,
- Slot.RIG: 92,
- Slot.SUBSYSTEM: 125,
- Slot.SERVICE: 164
-}
-
-INV_FLAG_CARGOBAY = 5
-INV_FLAG_DRONEBAY = 87
-INV_FLAG_FIGHTER = 158
-
-# 2017/04/05 NOTE: simple validation, for xml file
-RE_XML_START = r'<\?xml\s+version="1.0"\s*\?>'
-
-# -- 170327 Ignored description --
-RE_LTGT = "&(lt|gt);"
-L_MARK = "<localized hint=""
-# <localized hint="([^"]+)">([^\*]+)\*<\/localized>
-LOCALIZED_PATTERN = re.compile(r'([^\*]+)\*')
-
-
-def _extract_match(t):
- m = LOCALIZED_PATTERN.match(t)
- # hint attribute, text content
- return m.group(1), m.group(2)
-
-
-def _resolve_ship(fitting, sMkt, b_localized):
- # type: (xml.dom.minidom.Element, service.market.Market, bool) -> eos.saveddata.fit.Fit
- """ NOTE: Since it is meaningless unless a correct ship object can be constructed,
- process flow changed
- """
- # ------ Confirm ship
- # Maelstrom
- shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value")
- anything = None
- if b_localized:
- # expect an official name, emergency cache
- shipType, anything = _extract_match(shipType)
-
- limit = 2
- ship = None
- while True:
- must_retry = False
- try:
- try:
- ship = Ship(sMkt.getItem(shipType))
- except ValueError:
- ship = Citadel(sMkt.getItem(shipType))
- except Exception as e:
- pyfalog.warning("Caught exception on _resolve_ship")
- pyfalog.error(e)
- limit -= 1
- if limit is 0:
- break
- shipType = anything
- must_retry = True
- if not must_retry:
- break
-
- if ship is None:
- raise Exception("cannot resolve ship type.")
-
- fitobj = Fit(ship=ship)
- # ------ Confirm fit name
- anything = fitting.getAttribute("name")
- # 2017/03/29 NOTE:
- # if fit name contained "<" or ">" then reprace to named html entity by EVE client
- # if re.search(RE_LTGT, anything):
- if "<" in anything or ">" in anything:
- anything = replace_ltgt(anything)
- fitobj.name = anything
-
- return fitobj
-
-
-def _resolve_module(hardware, sMkt, b_localized):
- # type: (xml.dom.minidom.Element, service.market.Market, bool) -> eos.saveddata.module.Module
- moduleName = hardware.getAttribute("type")
- emergency = None
- if b_localized:
- # expect an official name, emergency cache
- moduleName, emergency = _extract_match(moduleName)
-
- item = None
- limit = 2
- while True:
- must_retry = False
- try:
- item = sMkt.getItem(moduleName, eager="group.category")
- except Exception as e:
- pyfalog.warning("Caught exception on _resolve_module")
- pyfalog.error(e)
- limit -= 1
- if limit is 0:
- break
- moduleName = emergency
- must_retry = True
- if not must_retry:
- break
- return item
-
-
-class UserCancelException(Exception):
- """when user cancel on port processing."""
- pass
-
-
-class IPortUser(metaclass=ABCMeta):
-
- ID_PULSE = 1
- # Pulse the progress bar
- ID_UPDATE = ID_PULSE << 1
- # Replace message with data: update messate
- ID_DONE = ID_PULSE << 2
- # open fits: import process done
- ID_ERROR = ID_PULSE << 3
- # display error: raise some error
-
- PROCESS_IMPORT = ID_PULSE << 4
- # means import process.
- PROCESS_EXPORT = ID_PULSE << 5
- # means import process.
-
- @abstractmethod
- def on_port_processing(self, action, data=None):
- """
- While importing fits from file, the logic calls back to this function to
- update progress bar to show activity. XML files can contain multiple
- ships with multiple fits, whereas EFT cfg files contain many fits of
- a single ship. When iterating through the files, we update the message
- when we start a new file, and then Pulse the progress bar with every fit
- that is processed.
-
- action : a flag that lets us know how to deal with :data
- None: Pulse the progress bar
- 1: Replace message with data
- other: Close dialog and handle based on :action (-1 open fits, -2 display error)
- """
-
- """return: True is continue process, False is cancel."""
- pass
-
- def on_port_process_start(self):
- pass
-
-
-class Port(object):
- """
- 2017/03/31 NOTE: About change
- 1. want to keep the description recorded in fit
- 2. i think should not write wx.CallAfter in here
- """
- instance = None
- __tag_replace_flag = True
-
- @classmethod
- def getInstance(cls):
- if cls.instance is None:
- cls.instance = Port()
-
- return cls.instance
-
- @classmethod
- def set_tag_replace(cls, b):
- cls.__tag_replace_flag = b
-
- @classmethod
- def is_tag_replace(cls):
- # might there is a person who wants to hold tags.
- # (item link in EVE client etc. When importing again to EVE)
- return cls.__tag_replace_flag
-
- @staticmethod
- def backupFits(path, iportuser):
- pyfalog.debug("Starting backup fits thread.")
-# thread = FitBackupThread(path, callback)
-# thread.start()
- threading.Thread(
- target=PortProcessing.backupFits,
- args=(path, iportuser)
- ).start()
-
- @staticmethod
- def importFitsThreaded(paths, iportuser):
- # type: (tuple, IPortUser) -> None
- """
- :param paths: fits data file path list.
- :param iportuser: IPortUser implemented class.
- :rtype: None
- """
- pyfalog.debug("Starting import fits thread.")
-# thread = FitImportThread(paths, iportuser)
-# thread.start()
- threading.Thread(
- target=PortProcessing.importFitsFromFile,
- args=(paths, iportuser)
- ).start()
-
- @staticmethod
- def importFitFromFiles(paths, iportuser=None):
- """
- Imports fits from file(s). First processes all provided paths and stores
- assembled fits into a list. This allows us to call back to the GUI as
- fits are processed as well as when fits are being saved.
- returns
- """
-
- sFit = svcFit.getInstance()
-
- fit_list = []
- try:
- for path in paths:
- if iportuser: # Pulse
- msg = "Processing file:\n%s" % path
- pyfalog.debug(msg)
- PortProcessing.notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg)
- # wx.CallAfter(callback, 1, msg)
-
- with open(path, "rb") as file_:
- srcString = file_.read()
- dammit = UnicodeDammit(srcString)
- srcString = dammit.unicode_markup
-
- if len(srcString) == 0: # ignore blank files
- pyfalog.debug("File is blank.")
- continue
-
- try:
- _, fitsImport = Port.importAuto(srcString, path, iportuser=iportuser)
- fit_list += fitsImport
- except xml.parsers.expat.ExpatError:
- pyfalog.warning("Malformed XML in:\n{0}", path)
- return False, "Malformed XML in %s" % path
-
- # IDs = [] # NOTE: what use for IDs?
- numFits = len(fit_list)
- for idx, fit in enumerate(fit_list):
- # Set some more fit attributes and save
- fit.character = sFit.character
- fit.damagePattern = sFit.pattern
- fit.targetResists = sFit.targetResists
- if len(fit.implants) > 0:
- fit.implantLocation = ImplantLocation.FIT
- else:
- useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
- fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
- db.save(fit)
- # IDs.append(fit.ID)
- if iportuser: # Pulse
- pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits)
- PortProcessing.notify(
- iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
- "Processing complete, saving fits to database\n(%d/%d) %s" % (idx + 1, numFits, fit.ship.name)
- )
-
- except UserCancelException:
- return False, "Processing has been canceled.\n"
- except Exception as e:
- pyfalog.critical("Unknown exception processing: {0}", path)
- pyfalog.critical(e)
- # TypeError: not all arguments converted during string formatting
-# return False, "Unknown Error while processing {0}" % path
- return False, "Unknown error while processing %s\n\n Error: %s" % (path, e.message)
-
- return True, fit_list
-
- @staticmethod
- def importFitFromBuffer(bufferStr, activeFit=None):
- # type: (basestring, object) -> object
- # TODO: catch the exception?
- # activeFit is reserved?, bufferStr is unicode? (assume only clipboard string?
- sFit = svcFit.getInstance()
- _, fits = Port.importAuto(bufferStr, activeFit=activeFit)
- for fit in fits:
- fit.character = sFit.character
- fit.damagePattern = sFit.pattern
- fit.targetResists = sFit.targetResists
- if len(fit.implants) > 0:
- fit.implantLocation = ImplantLocation.FIT
- else:
- useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
- fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
- db.save(fit)
- return fits
-
- """Service which houses all import/export format functions"""
-
- @classmethod
- def exportESI(cls, ofit, callback=None):
- # A few notes:
- # max fit name length is 50 characters
- # Most keys are created simply because they are required, but bogus data is okay
-
- nested_dict = lambda: collections.defaultdict(nested_dict)
- fit = nested_dict()
- sFit = svcFit.getInstance()
-
- # max length is 50 characters
- name = ofit.name[:47] + '...' if len(ofit.name) > 50 else ofit.name
- fit['name'] = name
- fit['ship_type_id'] = ofit.ship.item.ID
-
- # 2017/03/29 NOTE: "<" or "<" is Ignored
- # fit['description'] = "" % ofit.ID
- fit['description'] = ofit.notes[:397] + '...' if len(ofit.notes) > 400 else ofit.notes if ofit.notes is not None else ""
- fit['items'] = []
-
- slotNum = {}
- charges = {}
- for module in ofit.modules:
- if module.isEmpty:
- continue
-
- item = nested_dict()
- slot = module.slot
-
- if slot == Slot.SUBSYSTEM:
- # Order of subsystem matters based on this attr. See GH issue #130
- slot = int(module.getModifiedItemAttr("subSystemSlot"))
- item['flag'] = slot
- else:
- if slot not in slotNum:
- slotNum[slot] = INV_FLAGS[slot]
-
- item['flag'] = slotNum[slot]
- slotNum[slot] += 1
-
- item['quantity'] = 1
- item['type_id'] = module.item.ID
- fit['items'].append(item)
-
- if module.charge and sFit.serviceFittingOptions["exportCharges"]:
- if module.chargeID not in charges:
- charges[module.chargeID] = 0
- # `or 1` because some charges (ie scripts) are without qty
- charges[module.chargeID] += module.numCharges or 1
-
- for cargo in ofit.cargo:
- item = nested_dict()
- item['flag'] = INV_FLAG_CARGOBAY
- item['quantity'] = cargo.amount
- item['type_id'] = cargo.item.ID
- fit['items'].append(item)
-
- for chargeID, amount in list(charges.items()):
- item = nested_dict()
- item['flag'] = INV_FLAG_CARGOBAY
- item['quantity'] = amount
- item['type_id'] = chargeID
- fit['items'].append(item)
-
- for drone in ofit.drones:
- item = nested_dict()
- item['flag'] = INV_FLAG_DRONEBAY
- item['quantity'] = drone.amount
- item['type_id'] = drone.item.ID
- fit['items'].append(item)
-
- for fighter in ofit.fighters:
- item = nested_dict()
- item['flag'] = INV_FLAG_FIGHTER
- item['quantity'] = fighter.amountActive
- item['type_id'] = fighter.item.ID
- fit['items'].append(item)
-
- if len(fit['items']) == 0:
- raise ESIExportException("Cannot export fitting: module list cannot be empty.")
-
- return json.dumps(fit)
-
- @classmethod
- def importAuto(cls, string, path=None, activeFit=None, iportuser=None):
- # type: (basestring, basestring, object, IPortUser, basestring) -> object
- # Get first line and strip space symbols of it to avoid possible detection errors
- firstLine = re.split("[\n\r]+", string.strip(), maxsplit=1)[0]
- firstLine = firstLine.strip()
-
- # If XML-style start of tag encountered, detect as XML
- if re.search(RE_XML_START, firstLine):
- return "XML", cls.importXml(string, iportuser)
-
- # If JSON-style start, parse as CREST/JSON
- if firstLine[0] == '{':
- return "JSON", (cls.importESI(string),)
-
- # If we've got source file name which is used to describe ship name
- # and first line contains something like [setup name], detect as eft config file
- if re.match("\[.*\]", firstLine) and path is not None:
- filename = os.path.split(path)[1]
- shipName = filename.rsplit('.')[0]
- return "EFT Config", cls.importEftCfg(shipName, string, iportuser)
-
- # If no file is specified and there's comma between brackets,
- # consider that we have [ship, setup name] and detect like eft export format
- if re.match("\[.*,.*\]", firstLine):
- return "EFT", (cls.importEft(string),)
-
- # Use DNA format for all other cases
- return "DNA", (cls.importDna(string),)
-
- @staticmethod
- def importESI(str_):
-
- sMkt = Market.getInstance()
- fitobj = Fit()
- refobj = json.loads(str_)
- items = refobj['items']
- # "<" and ">" is replace to "<", ">" by EVE client
- fitobj.name = refobj['name']
- # 2017/03/29: read description
- fitobj.notes = refobj['description']
-
- try:
- ship = refobj['ship_type_id']
- try:
- fitobj.ship = Ship(sMkt.getItem(ship))
- except ValueError:
- fitobj.ship = Citadel(sMkt.getItem(ship))
- except:
- pyfalog.warning("Caught exception in importESI")
- return None
-
- items.sort(key=lambda k: k['flag'])
-
- moduleList = []
- for module in items:
- try:
- item = sMkt.getItem(module['type_id'], eager="group.category")
- if not item.published:
- continue
- if module['flag'] == INV_FLAG_DRONEBAY:
- d = Drone(item)
- d.amount = module['quantity']
- fitobj.drones.append(d)
- elif module['flag'] == INV_FLAG_CARGOBAY:
- c = Cargo(item)
- c.amount = module['quantity']
- fitobj.cargo.append(c)
- elif module['flag'] == INV_FLAG_FIGHTER:
- fighter = Fighter(item)
- fitobj.fighters.append(fighter)
- else:
- try:
- m = Module(item)
- # When item can't be added to any slot (unknown item or just charge), ignore it
- except ValueError:
- pyfalog.debug("Item can't be added to any slot (unknown item or just charge)")
- continue
- # Add subsystems before modules to make sure T3 cruisers have subsystems installed
- if item.category.name == "Subsystem":
- if m.fits(fitobj):
- fitobj.modules.append(m)
- else:
- if m.isValidState(State.ACTIVE):
- m.state = State.ACTIVE
-
- moduleList.append(m)
-
- except:
- pyfalog.warning("Could not process module.")
- continue
-
- # 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)
-
- 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):
- sMkt = Market.getInstance()
- offineSuffix = " /OFFLINE"
-
- fit = Fit()
- eftString = eftString.strip()
- lines = re.split('[\n\r]+', eftString)
- info = lines[0][1:-1].split(",", 1)
-
- if len(info) == 2:
- shipType = info[0].strip()
- fitName = info[1].strip()
- else:
- shipType = info[0].strip()
- fitName = "Imported %s" % shipType
-
- try:
- ship = sMkt.getItem(shipType)
- try:
- fit.ship = Ship(ship)
- except ValueError:
- fit.ship = Citadel(ship)
- fit.name = fitName
- except:
- pyfalog.warning("Exception caught in importEft")
- return
-
- # maintain map of drones and their quantities
- droneMap = {}
- cargoMap = {}
- moduleList = []
- for i in range(1, len(lines)):
- ammoName = None
- extraAmount = None
-
- line = lines[i].strip()
- if not line:
- continue
-
- setOffline = line.endswith(offineSuffix)
- if setOffline is True:
- # remove offline suffix from line
- line = line[:len(line) - len(offineSuffix)]
-
- modAmmo = line.split(",")
- # matches drone and cargo with x{qty}
- modExtra = modAmmo[0].split(" x")
-
- if len(modAmmo) == 2:
- # line with a module and ammo
- ammoName = modAmmo[1].strip()
- modName = modAmmo[0].strip()
- elif len(modExtra) == 2:
- # line with drone/cargo and qty
- extraAmount = modExtra[1].strip()
- modName = modExtra[0].strip()
- else:
- # line with just module
- modName = modExtra[0].strip()
-
- try:
- # get item information. If we are on a Drone/Cargo line, throw out cargo
- item = sMkt.getItem(modName, eager="group.category")
- except:
- # if no data can be found (old names)
- pyfalog.warning("no data can be found (old names)")
- continue
-
- if not item.published:
- continue
-
- if item.category.name == "Drone":
- extraAmount = int(extraAmount) if extraAmount is not None else 1
- if modName not in droneMap:
- droneMap[modName] = 0
- droneMap[modName] += extraAmount
- elif item.category.name == "Fighter":
- extraAmount = int(extraAmount) if extraAmount is not None else 1
- fighterItem = Fighter(item)
- if extraAmount > fighterItem.fighterSquadronMaxSize: # Amount bigger then max fightergroup size
- extraAmount = fighterItem.fighterSquadronMaxSize
- if fighterItem.fits(fit):
- fit.fighters.append(fighterItem)
-
- if len(modExtra) == 2 and item.category.name != "Drone" and item.category.name != "Fighter":
- extraAmount = int(extraAmount) if extraAmount is not None else 1
- if modName not in cargoMap:
- cargoMap[modName] = 0
- cargoMap[modName] += extraAmount
- elif item.category.name == "Implant":
- if "implantness" in item.attributes:
- fit.implants.append(Implant(item))
- elif "boosterness" in item.attributes:
- fit.boosters.append(Booster(item))
- else:
- pyfalog.error("Failed to import implant: {0}", line)
- # elif item.category.name == "Subsystem":
- # try:
- # subsystem = Module(item)
- # except ValueError:
- # continue
- #
- # if subsystem.fits(fit):
- # fit.modules.append(subsystem)
- else:
- try:
- m = Module(item)
- except ValueError:
- continue
- # Add subsystems before modules to make sure T3 cruisers have subsystems installed
- if item.category.name == "Subsystem":
- if m.fits(fit):
- fit.modules.append(m)
- else:
- if ammoName:
- try:
- ammo = sMkt.getItem(ammoName)
- if m.isValidCharge(ammo) and m.charge is None:
- m.charge = ammo
- except:
- pass
-
- if setOffline is True and m.isValidState(State.OFFLINE):
- m.state = State.OFFLINE
- elif m.isValidState(State.ACTIVE):
- m.state = State.ACTIVE
-
- moduleList.append(m)
-
- # Recalc to get slot numbers correct for T3 cruisers
- svcFit.getInstance().recalc(fit)
-
- for m in moduleList:
- if m.fits(fit):
- m.owner = fit
- if not m.isValidState(m.state):
- pyfalog.warning("Error: Module {0} cannot have state {1}", m, m.state)
-
- fit.modules.append(m)
-
- for droneName in droneMap:
- d = Drone(sMkt.getItem(droneName))
- d.amount = droneMap[droneName]
- fit.drones.append(d)
-
- for cargoName in cargoMap:
- c = Cargo(sMkt.getItem(cargoName))
- c.amount = cargoMap[cargoName]
- fit.cargo.append(c)
-
- return fit
-
- @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
-
- @staticmethod
- def importXml(text, iportuser=None):
- # type: (basestring, IPortUser, basestring) -> list[eos.saveddata.fit.Fit]
- sMkt = Market.getInstance()
- doc = xml.dom.minidom.parseString(text)
- # NOTE:
- # When L_MARK is included at this point,
- # Decided to be localized data
- b_localized = L_MARK in text
- fittings = doc.getElementsByTagName("fittings").item(0)
- fittings = fittings.getElementsByTagName("fitting")
- fit_list = []
- failed = 0
-
- for fitting in fittings:
- try:
- fitobj = _resolve_ship(fitting, sMkt, b_localized)
- except:
- failed += 1
- continue
-
- # -- 170327 Ignored description --
- # read description from exported xml. (EVE client, EFT)
- description = fitting.getElementsByTagName("description").item(0).getAttribute("value")
- if description is None:
- description = ""
- elif len(description):
- # convert
to "\n" and remove html tags.
- if Port.is_tag_replace():
- description = replace_ltgt(
- sequential_rep(description, r"<(br|BR)>", "\n", r"<[^<>]+>", "")
- )
- fitobj.notes = description
-
- hardwares = fitting.getElementsByTagName("hardware")
- moduleList = []
- for hardware in hardwares:
- try:
- item = _resolve_module(hardware, sMkt, b_localized)
- if not item or not item.published:
- continue
-
- if item.category.name == "Drone":
- d = Drone(item)
- d.amount = int(hardware.getAttribute("qty"))
- fitobj.drones.append(d)
- elif item.category.name == "Fighter":
- ft = Fighter(item)
- ft.amount = int(hardware.getAttribute("qty")) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize
- fitobj.fighters.append(ft)
- elif hardware.getAttribute("slot").lower() == "cargo":
- # although the eve client only support charges in cargo, third-party programs
- # may support items or "refits" in cargo. Support these by blindly adding all
- # cargo, not just charges
- c = Cargo(item)
- c.amount = int(hardware.getAttribute("qty"))
- fitobj.cargo.append(c)
- else:
- try:
- m = Module(item)
- # When item can't be added to any slot (unknown item or just charge), ignore it
- except ValueError:
- pyfalog.warning("item can't be added to any slot (unknown item or just charge), ignore it")
- continue
- # Add subsystems before modules to make sure T3 cruisers have subsystems installed
- if item.category.name == "Subsystem":
- if m.fits(fitobj):
- m.owner = fitobj
- fitobj.modules.append(m)
- else:
- if m.isValidState(State.ACTIVE):
- m.state = State.ACTIVE
-
- moduleList.append(m)
-
- except KeyboardInterrupt:
- pyfalog.warning("Keyboard Interrupt")
- continue
-
- # Recalc to get slot numbers correct for T3 cruisers
- svcFit.getInstance().recalc(fitobj)
-
- for module in moduleList:
- if module.fits(fitobj):
- module.owner = fitobj
- fitobj.modules.append(module)
-
- fit_list.append(fitobj)
- if iportuser: # NOTE: Send current processing status
- PortProcessing.notify(
- iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
- "Processing %s\n%s" % (fitobj.ship.name, fitobj.name)
- )
-
- return fit_list
-
-
- @classmethod
- def exportEft(cls, fit, mutations=False, implants=False):
- # 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
- def formatAttrVal(val):
- if int(val) == val:
- return int(val)
- return val
-
- offineSuffix = ' /OFFLINE'
- modsBySlotType = {}
- sFit = svcFit.getInstance()
- for module in fit.modules:
- slot = module.slot
- slotTypeMods = modsBySlotType.setdefault(slot, [])
- if module.item:
- mutatedMod = bool(module.mutators)
- # if module was mutated, use base item name for export
- if mutatedMod:
- modName = module.baseItem.name
- else:
- modName = module.item.name
- modOfflineSuffix = offineSuffix if module.state == State.OFFLINE else ''
- if module.charge and sFit.serviceFittingOptions['exportCharges']:
- slotTypeMods.append('{}, {}{}'.format(modName, module.charge.name, modOfflineSuffix))
- else:
- slotTypeMods.append('{}{}'.format(modName, modOfflineSuffix))
- if mutatedMod and mutations:
- mutationGrade = module.mutaplasmid.item.name.split(' ', 1)[0].lower()
- mutatedAttrs = {}
- for attrID, mutator in module.mutators.items():
- attrName = getAttributeInfo(attrID).name
- mutatedAttrs[attrName] = mutator.value
- customAttrsLine = ', '.join('{} {}'.format(a, formatAttrVal(mutatedAttrs[a])) for a in sorted(mutatedAttrs))
- slotTypeMods.append(' {}: {}'.format(mutationGrade, customAttrsLine))
- else:
- slotTypeMods.append('[Empty {} slot]'.format(Slot.getName(slot).capitalize() if slot is not None else ''))
- modSection = []
- for slotType in EFT_SLOT_ORDER:
- rackLines = []
- data = modsBySlotType.get(slotType, ())
- for line in data:
- rackLines.append(line)
- 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 implants:
- 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))
-
- return '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
-
- @classmethod
- def exportEftImps(cls, fit):
- return cls.exportEft(fit, implants=True)
-
- @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()
- fittings = doc.createElement("fittings")
- # fit count
- fit_count = len(fits)
- fittings.setAttribute("count", "%s" % fit_count)
- doc.appendChild(fittings)
- sFit = svcFit.getInstance()
-
- for i, fit in enumerate(fits):
- try:
- fitting = doc.createElement("fitting")
- fitting.setAttribute("name", fit.name)
- fittings.appendChild(fitting)
- description = doc.createElement("description")
- # -- 170327 Ignored description --
- try:
- notes = fit.notes # unicode
-
- if notes:
- notes = notes[:397] + '...' if len(notes) > 400 else notes
-
- description.setAttribute(
- "value", re.sub("(\r|\n|\r\n)+", "
", notes) if notes is not None else ""
- )
- except Exception as e:
- pyfalog.warning("read description is failed, msg=%s\n" % e.args)
-
- fitting.appendChild(description)
- shipType = doc.createElement("shipType")
- shipType.setAttribute("value", fit.ship.name)
- fitting.appendChild(shipType)
-
- charges = {}
- slotNum = {}
- for module in fit.modules:
- if module.isEmpty:
- continue
-
- slot = module.slot
-
- if slot == Slot.SUBSYSTEM:
- # Order of subsystem matters based on this attr. See GH issue #130
- slotId = module.getModifiedItemAttr("subSystemSlot") - 125
- else:
- if slot not in slotNum:
- slotNum[slot] = 0
-
- slotId = slotNum[slot]
- slotNum[slot] += 1
-
- hardware = doc.createElement("hardware")
- hardware.setAttribute("type", module.item.name)
- slotName = Slot.getName(slot).lower()
- slotName = slotName if slotName != "high" else "hi"
- hardware.setAttribute("slot", "%s slot %d" % (slotName, slotId))
- fitting.appendChild(hardware)
-
- if module.charge and sFit.serviceFittingOptions["exportCharges"]:
- if module.charge.name not in charges:
- charges[module.charge.name] = 0
- # `or 1` because some charges (ie scripts) are without qty
- charges[module.charge.name] += module.numCharges or 1
-
- for drone in fit.drones:
- hardware = doc.createElement("hardware")
- hardware.setAttribute("qty", "%d" % drone.amount)
- hardware.setAttribute("slot", "drone bay")
- hardware.setAttribute("type", drone.item.name)
- fitting.appendChild(hardware)
-
- for fighter in fit.fighters:
- hardware = doc.createElement("hardware")
- hardware.setAttribute("qty", "%d" % fighter.amountActive)
- hardware.setAttribute("slot", "fighter bay")
- hardware.setAttribute("type", fighter.item.name)
- fitting.appendChild(hardware)
-
- for cargo in fit.cargo:
- if cargo.item.name not in charges:
- charges[cargo.item.name] = 0
- charges[cargo.item.name] += cargo.amount
-
- for name, qty in list(charges.items()):
- hardware = doc.createElement("hardware")
- hardware.setAttribute("qty", "%d" % qty)
- hardware.setAttribute("slot", "cargo")
- hardware.setAttribute("type", name)
- fitting.appendChild(hardware)
- except Exception as e:
- # print("Failed on fitID: %d" % fit.ID)
- pyfalog.error("Failed on fitID: %d, message: %s" % e.message)
- continue
- finally:
- if iportuser:
- PortProcessing.notify(
- iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE,
- (i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name))
- )
-# wx.CallAfter(callback, i, "(%s/%s) %s" % (i, fit_count, fit.ship.name))
-
- return doc.toprettyxml()
-
- @staticmethod
- def exportMultiBuy(fit):
- export = "%s\n" % fit.ship.item.name
- stuff = {}
- sFit = svcFit.getInstance()
- for module in fit.modules:
- slot = module.slot
- if slot not in stuff:
- stuff[slot] = []
- curr = "%s\n" % module.item.name if module.item else ""
- if module.charge and sFit.serviceFittingOptions["exportCharges"]:
- curr += "%s x%s\n" % (module.charge.name, module.numCharges)
- stuff[slot].append(curr)
-
- for slotType in EFT_SLOT_ORDER:
- data = stuff.get(slotType)
- if data is not None:
- # export += "\n"
- for curr in data:
- export += curr
-
- if len(fit.drones) > 0:
- for drone in fit.drones:
- export += "%s x%s\n" % (drone.item.name, drone.amount)
-
- if len(fit.cargo) > 0:
- for cargo in fit.cargo:
- export += "%s x%s\n" % (cargo.item.name, cargo.amount)
-
- if len(fit.implants) > 0:
- for implant in fit.implants:
- export += "%s\n" % implant.item.name
-
- if len(fit.boosters) > 0:
- for booster in fit.boosters:
- export += "%s\n" % booster.item.name
-
- if len(fit.fighters) > 0:
- for fighter in fit.fighters:
- export += "%s x%s\n" % (fighter.item.name, fighter.amountActive)
-
- if export[-1] == "\n":
- 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
diff --git a/service/port/__init__.py b/service/port/__init__.py
new file mode 100644
index 000000000..2e884d214
--- /dev/null
+++ b/service/port/__init__.py
@@ -0,0 +1,2 @@
+from .efs import EfsPort
+from .port import Port, IPortUser
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/efsPort.py b/service/port/efs.py
similarity index 100%
rename from service/efsPort.py
rename to service/port/efs.py
diff --git a/service/port/eft.py b/service/port/eft.py
new file mode 100644
index 000000000..fc8576d01
--- /dev/null
+++ b/service/port/eft.py
@@ -0,0 +1,878 @@
+# =============================================================================
+# 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 logbook import Logger
+
+from eos.db.gamedata.queries import getAttributeInfo, getDynamicItem
+from eos.saveddata.cargo import Cargo
+from eos.saveddata.citadel import Citadel
+from eos.saveddata.booster import Booster
+from eos.saveddata.drone import Drone
+from eos.saveddata.fighter import Fighter
+from eos.saveddata.implant import Implant
+from eos.saveddata.module import Module, State, Slot
+from eos.saveddata.ship import Ship
+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
+
+
+pyfalog = Logger(__name__)
+
+
+class Options(Enum):
+ IMPLANTS = 1
+ MUTATIONS = 2
+
+
+MODULE_CATS = ('Module', 'Subsystem', 'Structure Module')
+SLOT_ORDER = (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE)
+OFFLINE_SUFFIX = '/OFFLINE'
+
+EFT_OPTIONS = {
+ Options.IMPLANTS.value: {
+ "name": "Implants",
+ "description": "Exports implants"
+ },
+ Options.MUTATIONS.value: {
+ "name": "Mutated Attributes",
+ "description": "Exports Abyssal stats"
+ }
+}
+
+
+def exportEft(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))
+
+
+def importEft(eftString):
+ lines = _importPrepareString(eftString)
+ try:
+ fit = _importCreateFit(lines)
+ except EftImportError:
+ return
+
+ aFit = AbstractFit()
+ aFit.mutations = _importGetMutationData(lines)
+
+ nameChars = '[^,/\[\]]' # Characters which are allowed to be used in name
+ stubPattern = '^\[.+?\]$'
+ modulePattern = '^(?P{0}+?)(,\s*(?P{0}+?))?(?P\s*{1})?(\s*\[(?P\d+?)\])?$'.format(nameChars, OFFLINE_SUFFIX)
+ droneCargoPattern = '^(?P{}+?) x(?P\d+?)$'.format(nameChars)
+
+ sections = []
+ for section in _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('service.port.eft.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
+
+
+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
+
+
+def _importPrepareString(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
+
+
+def _importGetMutationData(lines):
+ data = {}
+ consumedIndices = set()
+ for i in range(len(lines)):
+ line = lines[i]
+ m = re.match('^\[(?P[\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
+
+
+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
+
+
+def _importCreateFit(lines):
+ """Create fit and set top-level entity (ship or citadel)."""
+ fit = Fit()
+ header = lines.pop(0)
+ m = re.match('\[(?P[\w\s]+),\s*(?P.+)\]', header)
+ if not m:
+ pyfalog.warning('service.port.eft.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('service.port.eft.importEft: exception caught when parsing header')
+ raise EftImportError
+ return fit
+
+
+def _fetchItem(typeName, eagerCat=False):
+ sMkt = Market.getInstance()
+ eager = 'group.category' if eagerCat else None
+ try:
+ item = sMkt.getItem(typeName, eager=eager)
+ except:
+ pyfalog.warning('service.port.eft: unable to fetch item "{}"'.format(typeName))
+ return None
+ if sMkt.getPublicityByItem(item):
+ return item
+ else:
+ return None
+
+
+def _clearTail(lst):
+ while lst and lst[-1] is None:
+ del lst[-1]
+
+
+class EftImportError(Exception):
+ """Exception class emitted and consumed by EFT importer internally."""
+ ...
+
+
+class Section:
+
+ def __init__(self):
+ self.lines = []
+ self.itemSpecs = []
+ self.__itemDataCats = None
+
+ @property
+ def itemDataCats(self):
+ if self.__itemDataCats is None:
+ cats = set()
+ for itemSpec in self.itemSpecs:
+ if itemSpec is None:
+ continue
+ cats.add(itemSpec.item.category.name)
+ self.__itemDataCats = tuple(sorted(cats))
+ return self.__itemDataCats
+
+ @property
+ def isModuleRack(self):
+ return all(i is None or i.isModule for i in self.itemSpecs)
+
+ @property
+ def isImplantRack(self):
+ return all(i is not None and i.isImplant for i in self.itemSpecs)
+
+ @property
+ def isDroneBay(self):
+ return all(i is not None and i.isDrone for i in self.itemSpecs)
+
+ @property
+ def isFighterBay(self):
+ return all(i is not None and i.isFighter for i in self.itemSpecs)
+
+ @property
+ def isCargoHold(self):
+ return (
+ all(i is not None and i.isCargo for i in self.itemSpecs) and
+ not self.isDroneBay and not self.isFighterBay)
+
+
+class BaseItemSpec:
+
+ def __init__(self, typeName):
+ item = _fetchItem(typeName, eagerCat=True)
+ if item is None:
+ raise EftImportError
+ self.typeName = typeName
+ self.item = item
+
+ @property
+ def isModule(self):
+ return False
+
+ @property
+ def isImplant(self):
+ return False
+
+ @property
+ def isDrone(self):
+ return False
+
+ @property
+ def isFighter(self):
+ return False
+
+ @property
+ def isCargo(self):
+ return False
+
+
+class RegularItemSpec(BaseItemSpec):
+
+ def __init__(self, typeName, chargeName=None):
+ super().__init__(typeName)
+ self.charge = self.__fetchCharge(chargeName)
+ self.offline = False
+ self.mutationIdx = None
+
+ def __fetchCharge(self, chargeName):
+ if chargeName:
+ charge = _fetchItem(chargeName, eagerCat=True)
+ if not charge or charge.category.name != 'Charge':
+ charge = None
+ else:
+ charge = None
+ return charge
+
+ @property
+ def isModule(self):
+ return self.item.category.name in MODULE_CATS
+
+ @property
+ def isImplant(self):
+ return (
+ self.item.category.name == 'Implant' and (
+ 'implantness' in self.item.attributes or
+ 'boosterness' in self.item.attributes))
+
+
+class MultiItemSpec(BaseItemSpec):
+
+ def __init__(self, typeName):
+ super().__init__(typeName)
+ self.amount = 0
+
+ @property
+ def isDrone(self):
+ return self.item.category.name == 'Drone'
+
+ @property
+ def isFighter(self):
+ return self.item.category.name == 'Fighter'
+
+ @property
+ def isCargo(self):
+ return True
+
+
+class AbstractFit:
+
+ def __init__(self):
+ # Modules
+ self.modulesHigh = []
+ self.modulesMed = []
+ self.modulesLow = []
+ self.rigs = []
+ self.subsystems = []
+ self.services = []
+ # Non-modules
+ self.implants = []
+ self.boosters = []
+ self.drones = {} # Format: {item: Drone}
+ self.fighters = []
+ self.cargo = {} # Format: {item: Cargo}
+ # Other stuff
+ self.mutations = {} # Format: {reference: (mutaplamid item, {attr ID: attr value})}
+
+ @property
+ def __slotContainerMap(self):
+ return {
+ Slot.HIGH: self.modulesHigh,
+ Slot.MED: self.modulesMed,
+ Slot.LOW: self.modulesLow,
+ Slot.RIG: self.rigs,
+ Slot.SUBSYSTEM: self.subsystems,
+ Slot.SERVICE: self.services}
+
+ def getContainerBySlot(self, slotType):
+ return self.__slotContainerMap.get(slotType)
+
+ def getSlotByContainer(self, container):
+ slotType = None
+ for k, v in self.__slotContainerMap.items():
+ if v is container:
+ slotType = k
+ break
+ return slotType
+
+ def addModules(self, itemSpecs):
+ modules = []
+ slotTypes = set()
+ for itemSpec in itemSpecs:
+ if itemSpec is None:
+ modules.append(None)
+ continue
+ m = self.__makeModule(itemSpec)
+ if m is None:
+ modules.append(None)
+ continue
+ modules.append(m)
+ slotTypes.add(m.slot)
+ _clearTail(modules)
+ # If all the modules have same slot type, put them to appropriate
+ # container with stubs
+ if len(slotTypes) == 1:
+ slotType = tuple(slotTypes)[0]
+ self.getContainerBySlot(slotType).extend(modules)
+ # Otherwise, put just modules
+ else:
+ for m in modules:
+ if m is None:
+ continue
+ self.getContainerBySlot(m.slot).append(m)
+
+ def addModule(self, itemSpec):
+ if itemSpec is None:
+ return
+ m = self.__makeModule(itemSpec)
+ if m is not None:
+ self.getContainerBySlot(m.slot).append(m)
+
+ def __makeModule(self, itemSpec):
+ # Mutate item if needed
+ m = None
+ if itemSpec.mutationIdx in self.mutations:
+ mutaItem, mutaAttrs = self.mutations[itemSpec.mutationIdx]
+ mutaplasmid = getDynamicItem(mutaItem.ID)
+ if mutaplasmid:
+ try:
+ m = Module(mutaplasmid.resultingItem, itemSpec.item, mutaplasmid)
+ except ValueError:
+ pass
+ else:
+ for attrID, mutator in m.mutators.items():
+ if attrID in mutaAttrs:
+ mutator.value = mutaAttrs[attrID]
+ # If we still don't have item (item is not mutated or we
+ # failed to construct mutated item), try to make regular item
+ if m is None:
+ try:
+ m = Module(itemSpec.item)
+ except ValueError:
+ return None
+
+ if itemSpec.charge is not None and m.isValidCharge(itemSpec.charge):
+ m.charge = itemSpec.charge
+ if itemSpec.offline and m.isValidState(State.OFFLINE):
+ m.state = State.OFFLINE
+ elif m.isValidState(State.ACTIVE):
+ m.state = State.ACTIVE
+ return m
+
+ def addImplant(self, itemSpec):
+ if itemSpec is None:
+ return
+ if 'implantness' in itemSpec.item.attributes:
+ self.implants.append(Implant(itemSpec.item))
+ elif 'boosterness' in itemSpec.item.attributes:
+ self.boosters.append(Booster(itemSpec.item))
+ else:
+ pyfalog.error('Failed to import implant: {}', itemSpec.typeName)
+
+ def addDrone(self, itemSpec):
+ if itemSpec is None:
+ return
+ if itemSpec.item not in self.drones:
+ self.drones[itemSpec.item] = Drone(itemSpec.item)
+ self.drones[itemSpec.item].amount += itemSpec.amount
+
+ def addFighter(self, itemSpec):
+ if itemSpec is None:
+ return
+ fighter = Fighter(itemSpec.item)
+ fighter.amount = itemSpec.amount
+ self.fighters.append(fighter)
+
+ def addCargo(self, itemSpec):
+ if itemSpec is None:
+ return
+ if itemSpec.item not in self.cargo:
+ self.cargo[itemSpec.item] = Cargo(itemSpec.item)
+ self.cargo[itemSpec.item].amount += itemSpec.amount
diff --git a/service/port/esi.py b/service/port/esi.py
new file mode 100644
index 000000000..f1e02d13a
--- /dev/null
+++ b/service/port/esi.py
@@ -0,0 +1,208 @@
+# =============================================================================
+# 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 collections
+import json
+
+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
+
+
+class ESIExportException(Exception):
+ pass
+
+
+pyfalog = Logger(__name__)
+
+INV_FLAGS = {
+ Slot.LOW: 11,
+ Slot.MED: 19,
+ Slot.HIGH: 27,
+ Slot.RIG: 92,
+ Slot.SUBSYSTEM: 125,
+ Slot.SERVICE: 164
+}
+
+INV_FLAG_CARGOBAY = 5
+INV_FLAG_DRONEBAY = 87
+INV_FLAG_FIGHTER = 158
+
+
+def exportESI(ofit):
+ # A few notes:
+ # max fit name length is 50 characters
+ # Most keys are created simply because they are required, but bogus data is okay
+
+ nested_dict = lambda: collections.defaultdict(nested_dict)
+ fit = nested_dict()
+ sFit = svcFit.getInstance()
+
+ # max length is 50 characters
+ name = ofit.name[:47] + '...' if len(ofit.name) > 50 else ofit.name
+ fit['name'] = name
+ fit['ship_type_id'] = ofit.ship.item.ID
+
+ # 2017/03/29 NOTE: "<" or "<" is Ignored
+ # fit['description'] = "" % ofit.ID
+ fit['description'] = ofit.notes[:397] + '...' if len(ofit.notes) > 400 else ofit.notes if ofit.notes is not None else ""
+ fit['items'] = []
+
+ slotNum = {}
+ charges = {}
+ for module in ofit.modules:
+ if module.isEmpty:
+ continue
+
+ item = nested_dict()
+ slot = module.slot
+
+ if slot == Slot.SUBSYSTEM:
+ # Order of subsystem matters based on this attr. See GH issue #130
+ slot = int(module.getModifiedItemAttr("subSystemSlot"))
+ item['flag'] = slot
+ else:
+ if slot not in slotNum:
+ slotNum[slot] = INV_FLAGS[slot]
+
+ item['flag'] = slotNum[slot]
+ slotNum[slot] += 1
+
+ item['quantity'] = 1
+ item['type_id'] = module.item.ID
+ fit['items'].append(item)
+
+ if module.charge and sFit.serviceFittingOptions["exportCharges"]:
+ if module.chargeID not in charges:
+ charges[module.chargeID] = 0
+ # `or 1` because some charges (ie scripts) are without qty
+ charges[module.chargeID] += module.numCharges or 1
+
+ for cargo in ofit.cargo:
+ item = nested_dict()
+ item['flag'] = INV_FLAG_CARGOBAY
+ item['quantity'] = cargo.amount
+ item['type_id'] = cargo.item.ID
+ fit['items'].append(item)
+
+ for chargeID, amount in list(charges.items()):
+ item = nested_dict()
+ item['flag'] = INV_FLAG_CARGOBAY
+ item['quantity'] = amount
+ item['type_id'] = chargeID
+ fit['items'].append(item)
+
+ for drone in ofit.drones:
+ item = nested_dict()
+ item['flag'] = INV_FLAG_DRONEBAY
+ item['quantity'] = drone.amount
+ item['type_id'] = drone.item.ID
+ fit['items'].append(item)
+
+ for fighter in ofit.fighters:
+ item = nested_dict()
+ item['flag'] = INV_FLAG_FIGHTER
+ item['quantity'] = fighter.amountActive
+ item['type_id'] = fighter.item.ID
+ fit['items'].append(item)
+
+ if len(fit['items']) == 0:
+ raise ESIExportException("Cannot export fitting: module list cannot be empty.")
+
+ return json.dumps(fit)
+
+
+def importESI(str_):
+
+ sMkt = Market.getInstance()
+ fitobj = Fit()
+ refobj = json.loads(str_)
+ items = refobj['items']
+ # "<" and ">" is replace to "<", ">" by EVE client
+ fitobj.name = refobj['name']
+ # 2017/03/29: read description
+ fitobj.notes = refobj['description']
+
+ try:
+ ship = refobj['ship_type_id']
+ try:
+ fitobj.ship = Ship(sMkt.getItem(ship))
+ except ValueError:
+ fitobj.ship = Citadel(sMkt.getItem(ship))
+ except:
+ pyfalog.warning("Caught exception in importESI")
+ return None
+
+ items.sort(key=lambda k: k['flag'])
+
+ moduleList = []
+ for module in items:
+ try:
+ item = sMkt.getItem(module['type_id'], eager="group.category")
+ if not item.published:
+ continue
+ if module['flag'] == INV_FLAG_DRONEBAY:
+ d = Drone(item)
+ d.amount = module['quantity']
+ fitobj.drones.append(d)
+ elif module['flag'] == INV_FLAG_CARGOBAY:
+ c = Cargo(item)
+ c.amount = module['quantity']
+ fitobj.cargo.append(c)
+ elif module['flag'] == INV_FLAG_FIGHTER:
+ fighter = Fighter(item)
+ fitobj.fighters.append(fighter)
+ else:
+ try:
+ m = Module(item)
+ # When item can't be added to any slot (unknown item or just charge), ignore it
+ except ValueError:
+ pyfalog.debug("Item can't be added to any slot (unknown item or just charge)")
+ continue
+ # Add subsystems before modules to make sure T3 cruisers have subsystems installed
+ if item.category.name == "Subsystem":
+ if m.fits(fitobj):
+ fitobj.modules.append(m)
+ else:
+ if m.isValidState(State.ACTIVE):
+ m.state = State.ACTIVE
+
+ moduleList.append(m)
+
+ except:
+ pyfalog.warning("Could not process module.")
+ continue
+
+ # 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)
+
+ return fitobj
diff --git a/service/port/multibuy.py b/service/port/multibuy.py
new file mode 100644
index 000000000..2bf2d7a8a
--- /dev/null
+++ b/service/port/multibuy.py
@@ -0,0 +1,68 @@
+# =============================================================================
+# 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 .
+# =============================================================================
+
+
+from service.fit import Fit as svcFit
+from service.port.eft import SLOT_ORDER as EFT_SLOT_ORDER
+
+
+def exportMultiBuy(fit):
+ export = "%s\n" % fit.ship.item.name
+ stuff = {}
+ sFit = svcFit.getInstance()
+ for module in fit.modules:
+ slot = module.slot
+ if slot not in stuff:
+ stuff[slot] = []
+ curr = "%s\n" % module.item.name if module.item else ""
+ if module.charge and sFit.serviceFittingOptions["exportCharges"]:
+ curr += "%s x%s\n" % (module.charge.name, module.numCharges)
+ stuff[slot].append(curr)
+
+ for slotType in EFT_SLOT_ORDER:
+ data = stuff.get(slotType)
+ if data is not None:
+ # export += "\n"
+ for curr in data:
+ export += curr
+
+ if len(fit.drones) > 0:
+ for drone in fit.drones:
+ export += "%s x%s\n" % (drone.item.name, drone.amount)
+
+ if len(fit.cargo) > 0:
+ for cargo in fit.cargo:
+ export += "%s x%s\n" % (cargo.item.name, cargo.amount)
+
+ if len(fit.implants) > 0:
+ for implant in fit.implants:
+ export += "%s\n" % implant.item.name
+
+ if len(fit.boosters) > 0:
+ for booster in fit.boosters:
+ export += "%s\n" % booster.item.name
+
+ if len(fit.fighters) > 0:
+ for fighter in fit.fighters:
+ export += "%s x%s\n" % (fighter.item.name, fighter.amountActive)
+
+ if export[-1] == "\n":
+ export = export[:-1]
+
+ return export
diff --git a/service/port/port.py b/service/port/port.py
new file mode 100644
index 000000000..560af14ea
--- /dev/null
+++ b/service/port/port.py
@@ -0,0 +1,277 @@
+# =============================================================================
+# 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
+import os
+import threading
+import xml.dom
+import xml.parsers.expat
+from codecs import open
+
+from bs4 import UnicodeDammit
+from logbook import Logger
+
+from eos import db
+from eos.saveddata.fit import ImplantLocation
+from service.fit import Fit as svcFit
+from service.port.dna import exportDna, importDna
+from service.port.eft import exportEft, importEft, importEftCfg
+from service.port.esi import exportESI, importESI
+from service.port.multibuy import exportMultiBuy
+from service.port.shared import IPortUser, UserCancelException, processing_notify
+from service.port.xml import importXml, exportXml
+
+
+pyfalog = Logger(__name__)
+
+# 2017/04/05 NOTE: simple validation, for xml file
+RE_XML_START = r'<\?xml\s+version="1.0"\s*\?>'
+
+
+class Port(object):
+ """Service which houses all import/export format functions"""
+ instance = None
+ __tag_replace_flag = True
+
+ @classmethod
+ def getInstance(cls):
+ if cls.instance is None:
+ cls.instance = Port()
+
+ return cls.instance
+
+ @classmethod
+ def set_tag_replace(cls, b):
+ cls.__tag_replace_flag = b
+
+ @classmethod
+ def is_tag_replace(cls):
+ # might there is a person who wants to hold tags.
+ # (item link in EVE client etc. When importing again to EVE)
+ return cls.__tag_replace_flag
+
+ @staticmethod
+ def backupFits(path, iportuser):
+ pyfalog.debug("Starting backup fits thread.")
+
+ 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=backupFitsWorkerFunc,
+ args=(path, iportuser)
+ ).start()
+
+ @staticmethod
+ def importFitsThreaded(paths, iportuser):
+ # type: (tuple, IPortUser) -> None
+ """
+ :param paths: fits data file path list.
+ :param iportuser: IPortUser implemented class.
+ :rtype: None
+ """
+ pyfalog.debug("Starting import fits thread.")
+
+ 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=importFitsFromFileWorkerFunc,
+ args=(paths, iportuser)
+ ).start()
+
+ @staticmethod
+ def importFitFromFiles(paths, iportuser=None):
+ """
+ Imports fits from file(s). First processes all provided paths and stores
+ assembled fits into a list. This allows us to call back to the GUI as
+ fits are processed as well as when fits are being saved.
+ returns
+ """
+
+ sFit = svcFit.getInstance()
+
+ fit_list = []
+ try:
+ for path in paths:
+ if iportuser: # Pulse
+ msg = "Processing file:\n%s" % path
+ pyfalog.debug(msg)
+ processing_notify(iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE, msg)
+ # wx.CallAfter(callback, 1, msg)
+
+ with open(path, "rb") as file_:
+ srcString = file_.read()
+ dammit = UnicodeDammit(srcString)
+ srcString = dammit.unicode_markup
+
+ if len(srcString) == 0: # ignore blank files
+ pyfalog.debug("File is blank.")
+ continue
+
+ try:
+ _, fitsImport = Port.importAuto(srcString, path, iportuser=iportuser)
+ fit_list += fitsImport
+ except xml.parsers.expat.ExpatError:
+ pyfalog.warning("Malformed XML in:\n{0}", path)
+ return False, "Malformed XML in %s" % path
+
+ # IDs = [] # NOTE: what use for IDs?
+ numFits = len(fit_list)
+ for idx, fit in enumerate(fit_list):
+ # Set some more fit attributes and save
+ fit.character = sFit.character
+ fit.damagePattern = sFit.pattern
+ fit.targetResists = sFit.targetResists
+ if len(fit.implants) > 0:
+ fit.implantLocation = ImplantLocation.FIT
+ else:
+ useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
+ fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
+ db.save(fit)
+ # IDs.append(fit.ID)
+ if iportuser: # Pulse
+ pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", idx + 1, numFits)
+ 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)
+ )
+
+ except UserCancelException:
+ return False, "Processing has been canceled.\n"
+ except Exception as e:
+ pyfalog.critical("Unknown exception processing: {0}", path)
+ pyfalog.critical(e)
+ # TypeError: not all arguments converted during string formatting
+# return False, "Unknown Error while processing {0}" % path
+ return False, "Unknown error while processing %s\n\n Error: %s" % (path, e.message)
+
+ return True, fit_list
+
+ @staticmethod
+ def importFitFromBuffer(bufferStr, activeFit=None):
+ # type: (str, object) -> object
+ # TODO: catch the exception?
+ # activeFit is reserved?, bufferStr is unicode? (assume only clipboard string?
+ sFit = svcFit.getInstance()
+ _, fits = Port.importAuto(bufferStr, activeFit=activeFit)
+ for fit in fits:
+ fit.character = sFit.character
+ fit.damagePattern = sFit.pattern
+ fit.targetResists = sFit.targetResists
+ if len(fit.implants) > 0:
+ fit.implantLocation = ImplantLocation.FIT
+ else:
+ useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
+ fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
+ db.save(fit)
+ return fits
+
+ @classmethod
+ def importAuto(cls, string, path=None, activeFit=None, iportuser=None):
+ # type: (Port, str, str, object, IPortUser) -> object
+ # Get first line and strip space symbols of it to avoid possible detection errors
+ firstLine = re.split("[\n\r]+", string.strip(), maxsplit=1)[0]
+ firstLine = firstLine.strip()
+
+ # If XML-style start of tag encountered, detect as XML
+ if re.search(RE_XML_START, firstLine):
+ return "XML", cls.importXml(string, iportuser)
+
+ # If JSON-style start, parse as CREST/JSON
+ if firstLine[0] == '{':
+ return "JSON", (cls.importESI(string),)
+
+ # If we've got source file name which is used to describe ship name
+ # and first line contains something like [setup name], detect as eft config file
+ if re.match("\[.*\]", firstLine) and path is not None:
+ filename = os.path.split(path)[1]
+ shipName = filename.rsplit('.')[0]
+ return "EFT Config", cls.importEftCfg(shipName, string, iportuser)
+
+ # If no file is specified and there's comma between brackets,
+ # consider that we have [ship, setup name] and detect like eft export format
+ if re.match("\[.*,.*\]", firstLine):
+ return "EFT", (cls.importEft(string),)
+
+ # Use DNA format for all other cases
+ return "DNA", (cls.importDna(string),)
+
+ # EFT-related methods
+ @staticmethod
+ def importEft(eftString):
+ return importEft(eftString)
+
+ @staticmethod
+ def importEftCfg(shipname, contents, iportuser=None):
+ return importEftCfg(shipname, contents, iportuser)
+
+ @classmethod
+ def exportEft(cls, fit, options):
+ return exportEft(fit, options)
+
+ # DNA-related methods
+ @staticmethod
+ def importDna(string):
+ return importDna(string)
+
+ @staticmethod
+ def exportDna(fit):
+ return exportDna(fit)
+
+ # ESI-related methods
+ @staticmethod
+ def importESI(string):
+ return importESI(string)
+
+ @staticmethod
+ def exportESI(fit):
+ return exportESI(fit)
+
+ # XML-related methods
+ @staticmethod
+ def importXml(text, iportuser=None):
+ return importXml(text, iportuser)
+
+ @staticmethod
+ def exportXml(iportuser=None, *fits):
+ return exportXml(iportuser, *fits)
+
+ # Multibuy-related methods
+ @staticmethod
+ def exportMultiBuy(fit):
+ return exportMultiBuy(fit)
diff --git a/service/port/shared.py b/service/port/shared.py
new file mode 100644
index 000000000..a21a81d63
--- /dev/null
+++ b/service/port/shared.py
@@ -0,0 +1,70 @@
+# =============================================================================
+# 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 .
+# =============================================================================
+
+
+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
diff --git a/service/port/xml.py b/service/port/xml.py
new file mode 100644
index 000000000..54d5a98eb
--- /dev/null
+++ b/service/port/xml.py
@@ -0,0 +1,326 @@
+# =============================================================================
+# 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
+import xml.dom
+import xml.parsers.expat
+
+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
+from utils.strfunctions import sequential_rep, replace_ltgt
+
+from service.port.shared import IPortUser, processing_notify
+
+
+pyfalog = Logger(__name__)
+
+# -- 170327 Ignored description --
+RE_LTGT = "&(lt|gt);"
+L_MARK = "<localized hint=""
+# <localized hint="([^"]+)">([^\*]+)\*<\/localized>
+LOCALIZED_PATTERN = re.compile(r'([^\*]+)\*')
+
+
+def _extract_match(t):
+ m = LOCALIZED_PATTERN.match(t)
+ # hint attribute, text content
+ return m.group(1), m.group(2)
+
+
+def _resolve_ship(fitting, sMkt, b_localized):
+ # type: (xml.dom.minidom.Element, service.market.Market, bool) -> eos.saveddata.fit.Fit
+ """ NOTE: Since it is meaningless unless a correct ship object can be constructed,
+ process flow changed
+ """
+ # ------ Confirm ship
+ # Maelstrom
+ shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value")
+ anything = None
+ if b_localized:
+ # expect an official name, emergency cache
+ shipType, anything = _extract_match(shipType)
+
+ limit = 2
+ ship = None
+ while True:
+ must_retry = False
+ try:
+ try:
+ ship = Ship(sMkt.getItem(shipType))
+ except ValueError:
+ ship = Citadel(sMkt.getItem(shipType))
+ except Exception as e:
+ pyfalog.warning("Caught exception on _resolve_ship")
+ pyfalog.error(e)
+ limit -= 1
+ if limit is 0:
+ break
+ shipType = anything
+ must_retry = True
+ if not must_retry:
+ break
+
+ if ship is None:
+ raise Exception("cannot resolve ship type.")
+
+ fitobj = Fit(ship=ship)
+ # ------ Confirm fit name
+ anything = fitting.getAttribute("name")
+ # 2017/03/29 NOTE:
+ # if fit name contained "<" or ">" then reprace to named html entity by EVE client
+ # if re.search(RE_LTGT, anything):
+ if "<" in anything or ">" in anything:
+ anything = replace_ltgt(anything)
+ fitobj.name = anything
+
+ return fitobj
+
+
+def _resolve_module(hardware, sMkt, b_localized):
+ # type: (xml.dom.minidom.Element, service.market.Market, bool) -> eos.saveddata.module.Module
+ moduleName = hardware.getAttribute("type")
+ emergency = None
+ if b_localized:
+ # expect an official name, emergency cache
+ moduleName, emergency = _extract_match(moduleName)
+
+ item = None
+ limit = 2
+ while True:
+ must_retry = False
+ try:
+ item = sMkt.getItem(moduleName, eager="group.category")
+ except Exception as e:
+ pyfalog.warning("Caught exception on _resolve_module")
+ pyfalog.error(e)
+ limit -= 1
+ if limit is 0:
+ break
+ moduleName = emergency
+ must_retry = True
+ if not must_retry:
+ break
+ return item
+
+
+def importXml(text, iportuser):
+ from .port import Port
+ # type: (str, IPortUser) -> list[eos.saveddata.fit.Fit]
+ sMkt = Market.getInstance()
+ doc = xml.dom.minidom.parseString(text)
+ # NOTE:
+ # When L_MARK is included at this point,
+ # Decided to be localized data
+ b_localized = L_MARK in text
+ fittings = doc.getElementsByTagName("fittings").item(0)
+ fittings = fittings.getElementsByTagName("fitting")
+ fit_list = []
+ failed = 0
+
+ for fitting in fittings:
+ try:
+ fitobj = _resolve_ship(fitting, sMkt, b_localized)
+ except:
+ failed += 1
+ continue
+
+ # -- 170327 Ignored description --
+ # read description from exported xml. (EVE client, EFT)
+ description = fitting.getElementsByTagName("description").item(0).getAttribute("value")
+ if description is None:
+ description = ""
+ elif len(description):
+ # convert ]
to "\n" and remove html tags.
+ if Port.is_tag_replace():
+ description = replace_ltgt(
+ sequential_rep(description, r"<(br|BR)>", "\n", r"<[^<>]+>", "")
+ )
+ fitobj.notes = description
+
+ hardwares = fitting.getElementsByTagName("hardware")
+ moduleList = []
+ for hardware in hardwares:
+ try:
+ item = _resolve_module(hardware, sMkt, b_localized)
+ if not item or not item.published:
+ continue
+
+ if item.category.name == "Drone":
+ d = Drone(item)
+ d.amount = int(hardware.getAttribute("qty"))
+ fitobj.drones.append(d)
+ elif item.category.name == "Fighter":
+ ft = Fighter(item)
+ ft.amount = int(hardware.getAttribute("qty")) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize
+ fitobj.fighters.append(ft)
+ elif hardware.getAttribute("slot").lower() == "cargo":
+ # although the eve client only support charges in cargo, third-party programs
+ # may support items or "refits" in cargo. Support these by blindly adding all
+ # cargo, not just charges
+ c = Cargo(item)
+ c.amount = int(hardware.getAttribute("qty"))
+ fitobj.cargo.append(c)
+ else:
+ try:
+ m = Module(item)
+ # When item can't be added to any slot (unknown item or just charge), ignore it
+ except ValueError:
+ pyfalog.warning("item can't be added to any slot (unknown item or just charge), ignore it")
+ continue
+ # Add subsystems before modules to make sure T3 cruisers have subsystems installed
+ if item.category.name == "Subsystem":
+ if m.fits(fitobj):
+ m.owner = fitobj
+ fitobj.modules.append(m)
+ else:
+ if m.isValidState(State.ACTIVE):
+ m.state = State.ACTIVE
+
+ moduleList.append(m)
+
+ except KeyboardInterrupt:
+ pyfalog.warning("Keyboard Interrupt")
+ continue
+
+ # Recalc to get slot numbers correct for T3 cruisers
+ svcFit.getInstance().recalc(fitobj)
+
+ for module in moduleList:
+ if module.fits(fitobj):
+ module.owner = fitobj
+ fitobj.modules.append(module)
+
+ fit_list.append(fitobj)
+ if iportuser: # NOTE: Send current processing status
+ processing_notify(
+ iportuser, IPortUser.PROCESS_IMPORT | IPortUser.ID_UPDATE,
+ "Processing %s\n%s" % (fitobj.ship.name, fitobj.name)
+ )
+
+ return fit_list
+
+
+def exportXml(iportuser, *fits):
+ doc = xml.dom.minidom.Document()
+ fittings = doc.createElement("fittings")
+ # fit count
+ fit_count = len(fits)
+ fittings.setAttribute("count", "%s" % fit_count)
+ doc.appendChild(fittings)
+ sFit = svcFit.getInstance()
+
+ for i, fit in enumerate(fits):
+ try:
+ fitting = doc.createElement("fitting")
+ fitting.setAttribute("name", fit.name)
+ fittings.appendChild(fitting)
+ description = doc.createElement("description")
+ # -- 170327 Ignored description --
+ try:
+ notes = fit.notes # unicode
+
+ if notes:
+ notes = notes[:397] + '...' if len(notes) > 400 else notes
+
+ description.setAttribute(
+ "value", re.sub("(\r|\n|\r\n)+", "
", notes) if notes is not None else ""
+ )
+ except Exception as e:
+ pyfalog.warning("read description is failed, msg=%s\n" % e.args)
+
+ fitting.appendChild(description)
+ shipType = doc.createElement("shipType")
+ shipType.setAttribute("value", fit.ship.name)
+ fitting.appendChild(shipType)
+
+ charges = {}
+ slotNum = {}
+ for module in fit.modules:
+ if module.isEmpty:
+ continue
+
+ slot = module.slot
+
+ if slot == Slot.SUBSYSTEM:
+ # Order of subsystem matters based on this attr. See GH issue #130
+ slotId = module.getModifiedItemAttr("subSystemSlot") - 125
+ else:
+ if slot not in slotNum:
+ slotNum[slot] = 0
+
+ slotId = slotNum[slot]
+ slotNum[slot] += 1
+
+ hardware = doc.createElement("hardware")
+ hardware.setAttribute("type", module.item.name)
+ slotName = Slot.getName(slot).lower()
+ slotName = slotName if slotName != "high" else "hi"
+ hardware.setAttribute("slot", "%s slot %d" % (slotName, slotId))
+ fitting.appendChild(hardware)
+
+ if module.charge and sFit.serviceFittingOptions["exportCharges"]:
+ if module.charge.name not in charges:
+ charges[module.charge.name] = 0
+ # `or 1` because some charges (ie scripts) are without qty
+ charges[module.charge.name] += module.numCharges or 1
+
+ for drone in fit.drones:
+ hardware = doc.createElement("hardware")
+ hardware.setAttribute("qty", "%d" % drone.amount)
+ hardware.setAttribute("slot", "drone bay")
+ hardware.setAttribute("type", drone.item.name)
+ fitting.appendChild(hardware)
+
+ for fighter in fit.fighters:
+ hardware = doc.createElement("hardware")
+ hardware.setAttribute("qty", "%d" % fighter.amountActive)
+ hardware.setAttribute("slot", "fighter bay")
+ hardware.setAttribute("type", fighter.item.name)
+ fitting.appendChild(hardware)
+
+ for cargo in fit.cargo:
+ if cargo.item.name not in charges:
+ charges[cargo.item.name] = 0
+ charges[cargo.item.name] += cargo.amount
+
+ for name, qty in list(charges.items()):
+ hardware = doc.createElement("hardware")
+ hardware.setAttribute("qty", "%d" % qty)
+ hardware.setAttribute("slot", "cargo")
+ hardware.setAttribute("type", name)
+ fitting.appendChild(hardware)
+ except Exception as e:
+ pyfalog.error("Failed on fitID: %d, message: %s" % e.message)
+ continue
+ finally:
+ if iportuser:
+ processing_notify(
+ iportuser, IPortUser.PROCESS_EXPORT | IPortUser.ID_UPDATE,
+ (i, "convert to xml (%s/%s) %s" % (i + 1, fit_count, fit.ship.name))
+ )
+ return doc.toprettyxml()