From 0bf956e9b310447ffda48971bb447e7acad92d38 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 20 May 2014 12:41:57 -0400 Subject: [PATCH 1/5] Add support for explicitly converting items to another. --- eos/db/gamedata/queries.py | 3 +++ eos/itemMapping.py | 30 ++++++++++++++++++++++++++++++ service/market.py | 34 +++++++--------------------------- 3 files changed, 40 insertions(+), 27 deletions(-) create mode 100644 eos/itemMapping.py diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py index 93c43fc38..2a2a2b1f8 100755 --- a/eos/db/gamedata/queries.py +++ b/eos/db/gamedata/queries.py @@ -23,6 +23,7 @@ from sqlalchemy.sql import and_, or_, select, func from sqlalchemy.orm import join, exc from eos.types import Item, Category, Group, MarketGroup, AttributeInfo, MetaData, MetaGroup from eos.db.util import processEager, processWhere +from eos.itemMapping import itemMapping import eos.config configVal = getattr(eos.config, "gamedataCache", None) @@ -86,6 +87,8 @@ def getItem(lookfor, eager=None): itemNameMap[lookfor] = item.ID else: raise TypeError("Need integer or string as argument") + if item.name in itemMapping: + item = getItem(itemMapping[item.name], eager) return item groupNameMap = {} diff --git a/eos/itemMapping.py b/eos/itemMapping.py new file mode 100644 index 000000000..b4a84f210 --- /dev/null +++ b/eos/itemMapping.py @@ -0,0 +1,30 @@ +# Map of items that will convert to a new item (for old item names / skinned ships) +# Key must be str with exact name of item to be converted, value can be str (name) +# or int (id) of item to be converted to +itemMapping = { + "Aliastra Catalyst": "Catalyst", + "Inner Zone Shipping Catalyst": "Catalyst", + "Intaki Syndicate Catalyst": "Catalyst", + "InterBus Catalyst": "Catalyst", + "Quafe Catalyst": "Catalyst", + "Nefantar Thrasher": "Thrasher", + "Sarum Magnate": "Magnate", + "Sukuuvestaa Heron": "Heron:", + "Inner Zone Shipping Imicus": "Imicus", + "Vherokior Probe": "Probe", + "Miasmos Quafe Ultra Edition": "Miasmos", + "Miasmos Quafe Ultramarine Edition": "Miasmos", + "Miasmos Amastris Edition": "Miasmos", + "Tash-Murkon Magnate": "Magnate", + "Scorpion Ishukone Watch": "Scorpion", + "Incursus Aliastra Edition": "Incursus", + "Merlin Nugoeihuvi Edition": "Merlin", + "Police Pursuit Comet": "Federation Navy Comet", + "Punisher Kador Edition": "Punisher", + "Rifter Krusual Edition": "Rifter", + "Abaddon Kador Edition": "Abaddon", + "Hyperion Aliastra Edition": "Hyperion", + "Maelstrom Krusual Edition": "Maelstrom", + "Rokh Nugoeihuvi Edition": "Rokh", + "Mammoth Nefantar Edition": "Mammoth" +} \ No newline at end of file diff --git a/service/market.py b/service/market.py index 7432a5f1c..f6043b11f 100644 --- a/service/market.py +++ b/service/market.py @@ -25,6 +25,7 @@ import Queue import eos.db import eos.types +from eos.itemMapping import itemMapping from service.settings import SettingsProvider, ProxySettings @@ -209,33 +210,12 @@ class Market(): "QA Multiship Module - 5 Players": False, "QA Remote Armor Repair System - 5 Players": False, "QA Shield Transporter - 5 Players": False, - "Aliastra Catalyst": False, # Vanity - "Inner Zone Shipping Catalyst": False, # Vanity - "Intaki Syndicate Catalyst": False, # Vanity - "InterBus Catalyst": False, # Vanity - "Quafe Catalyst": False, # Vanity - "Nefantar Thrasher": False, # Vanity - "Sarum Magnate": False, # Vanity - "Sukuuvestaa Heron": False, # Vanity - "Inner Zone Shipping Imicus": False, # Vanity - "Vherokior Probe": False, # Vanity - "Miasmos Quafe Ultra Edition": False, # Vanity - "Miasmos Quafe Ultramarine Edition": False, # Vanity - "Miasmos Amastris Edition": False, # Vanity - "Goru's Shuttle": False, # Vanity - "Guristas Shuttle": False, # Vanity - "Tash-Murkon Magnate": False, # Vanity - "Scorpion Ishukone Watch": False, # Vanity - "Incursus Aliastra Edition": False, # Vanity - "Merlin Nugoeihuvi Edition": False, # Vanity - "Police Pursuit Comet": False, # Vanity - "Punisher Kador Edition": False, # Vanity - "Rifter Krusual Edition": False, # Vanity - "Abaddon Kador Edition": False, # Vanity - "Hyperion Aliastra Edition": False, # Vanity - "Maelstrom Krusual Edition": False, # Vanity - "Rokh Nugoeihuvi Edition": False, # Vanity - "Mammoth Nefantar Edition": False } # Vanity + "Goru's Shuttle": False, + "Guristas Shuttle": False} + + # do not publish anything that we convert + for name, _ in itemMapping.iteritems(): + self.ITEMS_FORCEPUBLISHED[name] = False # List of groups which are forcibly published self.GROUPS_FORCEPUBLISHED = { From fbc336b038bac4241d59a4fade7614b9533a3ac6 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 22 May 2014 21:00:42 -0400 Subject: [PATCH 2/5] Moved item override and fit porting to services. Also sprinkled static methods around Fit service and other PEP8 goodness because why not. --- eos/db/gamedata/queries.py | 3 - eos/itemMapping.py | 30 -- eos/saveddata/fit.py | 658 --------------------------------- service/fit.py | 173 +++++---- service/market.py | 35 +- service/port.py | 719 +++++++++++++++++++++++++++++++++++++ 6 files changed, 852 insertions(+), 766 deletions(-) delete mode 100644 eos/itemMapping.py create mode 100644 service/port.py diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py index 2a2a2b1f8..93c43fc38 100755 --- a/eos/db/gamedata/queries.py +++ b/eos/db/gamedata/queries.py @@ -23,7 +23,6 @@ from sqlalchemy.sql import and_, or_, select, func from sqlalchemy.orm import join, exc from eos.types import Item, Category, Group, MarketGroup, AttributeInfo, MetaData, MetaGroup from eos.db.util import processEager, processWhere -from eos.itemMapping import itemMapping import eos.config configVal = getattr(eos.config, "gamedataCache", None) @@ -87,8 +86,6 @@ def getItem(lookfor, eager=None): itemNameMap[lookfor] = item.ID else: raise TypeError("Need integer or string as argument") - if item.name in itemMapping: - item = getItem(itemMapping[item.name], eager) return item groupNameMap = {} diff --git a/eos/itemMapping.py b/eos/itemMapping.py deleted file mode 100644 index b4a84f210..000000000 --- a/eos/itemMapping.py +++ /dev/null @@ -1,30 +0,0 @@ -# Map of items that will convert to a new item (for old item names / skinned ships) -# Key must be str with exact name of item to be converted, value can be str (name) -# or int (id) of item to be converted to -itemMapping = { - "Aliastra Catalyst": "Catalyst", - "Inner Zone Shipping Catalyst": "Catalyst", - "Intaki Syndicate Catalyst": "Catalyst", - "InterBus Catalyst": "Catalyst", - "Quafe Catalyst": "Catalyst", - "Nefantar Thrasher": "Thrasher", - "Sarum Magnate": "Magnate", - "Sukuuvestaa Heron": "Heron:", - "Inner Zone Shipping Imicus": "Imicus", - "Vherokior Probe": "Probe", - "Miasmos Quafe Ultra Edition": "Miasmos", - "Miasmos Quafe Ultramarine Edition": "Miasmos", - "Miasmos Amastris Edition": "Miasmos", - "Tash-Murkon Magnate": "Magnate", - "Scorpion Ishukone Watch": "Scorpion", - "Incursus Aliastra Edition": "Incursus", - "Merlin Nugoeihuvi Edition": "Merlin", - "Police Pursuit Comet": "Federation Navy Comet", - "Punisher Kador Edition": "Punisher", - "Rifter Krusual Edition": "Rifter", - "Abaddon Kador Edition": "Abaddon", - "Hyperion Aliastra Edition": "Hyperion", - "Maelstrom Krusual Edition": "Maelstrom", - "Rokh Nugoeihuvi Edition": "Rokh", - "Mammoth Nefantar Edition": "Mammoth" -} \ No newline at end of file diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 8bb7a4ac1..f207d6717 100755 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -27,8 +27,6 @@ from copy import deepcopy from math import sqrt, log, asinh from eos.types import Drone, Cargo, Ship, Character, State, Slot, Module, Implant, Booster, Skill from eos.saveddata.module import State -import re -import xml.dom import time try: @@ -69,662 +67,6 @@ class Fit(object): self.timestamp = time.time() self.build() - @classmethod - def importAuto(cls, string, sourceFileName=None, activeFit=None): - # 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 string is from in-game copy of fitting window - # We match " power" instead of "High power" in case a fit has no high modules - if " power" in firstLine and activeFit is not None: - return "FIT", (cls.importFittingWindow(string, activeFit),) - # If we have ".*", firstLine) - if chatDna: - return "DNA", (cls.importDna(chatDna.group(1)),) - # If we have a CREST kill link - killLink = re.search("http://public-crest.eveonline.com/killmails/(.*)/", firstLine) - if killLink: - return "CREST", (cls.importCrest(tuple(killLink.group(1).split("/"))),) - # If we have ".*", firstLine) - if killReport: - return "CREST", (cls.importCrest(tuple(killReport.group(1).split(":"))),) - # If XML-style start of tag encountered, detect as XML - if re.match("<", firstLine): - return "XML", cls.importXml(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 - elif re.match("\[.*\]", firstLine) and sourceFileName is not None: - shipName = sourceFileName.rsplit('.')[0] - return "EFT Config", cls.importEftCfg(shipName, string) - # If no file is specified and there's comma between brackets, - # consider that we have [ship, setup name] and detect like eft export format - elif re.match("\[.*,.*\]", firstLine): - return "EFT", (cls.importEft(string),) - # Use DNA format for all other cases - else: - return "DNA", (cls.importDna(string),) - - @classmethod - def importFittingWindow(cls, string, activeFit): - from eos import db - activeFit = db.getFit(activeFit) - - # if the current fit has mods, do not mess with it. Instead, make new fit - if activeFit.modCount > 0: - fit = Fit() - fit.ship = Ship(db.getItem(activeFit.ship.item.ID)) - fit.name = "%s (copy)"%activeFit.name - else: - fit = activeFit - lines = re.split('[\n\r]+', string) - - droneMap = {} - cargoMap = {} - modules = [] - - for i in range(1, len(lines)): - line = lines[i].strip() - if not line: - continue - - try: - amount, modName = line.split("x ") - amount = int(amount) - item = db.getItem(modName, eager="group.category") - except: - # if no data can be found (old names) - continue - - if item.category.name == "Drone": - if not modName in droneMap: - droneMap[modName] = 0 - droneMap[modName] += amount - elif item.category.name == "Charge": - if not modName in cargoMap: - cargoMap[modName] = 0 - cargoMap[modName] += amount - else: - for i in xrange(amount): - try: - m = Module(item) - except ValueError: - continue - # If we are importing T3 ship, we must apply subsystems first, then - # calcModAttr() to get the ship slots - if m.slot == Slot.SUBSYSTEM and m.fits(fit): - fit.modules.append(m) - else: - modules.append(m) - - fit.clear() - fit.calculateModifiedAttributes() - - for m in modules: - # we check to see if module fits as a basic sanity check - # if it doesn't then the imported fit is most likely invalid - # (ie: user tried to import Legion fit to a Rifter) - if m.fits(fit): - fit.modules.append(m) - if m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - m.owner = fit #not sure why this is required when it's not for other import methods, but whatever - else: - return - - for droneName in droneMap: - d = Drone(db.getItem(droneName)) - d.amount = droneMap[droneName] - fit.drones.append(d) - - for cargoName in cargoMap: - c = Cargo(db.getItem(cargoName)) - c.amount = cargoMap[cargoName] - fit.cargo.append(c) - - return fit - - @classmethod - def importCrest(cls, info): - from eos import db - import urllib2 - import json - - try: - response = urllib2.urlopen("https://public-crest.eveonline.com/killmails/%s/%s/" % info) - except: - return - - kill = (json.loads(response.read()))['victim'] - - fit = Fit() - fit.ship = Ship(db.getItem(kill['shipType']['name'])) - fit.name = "CREST: %s's %s" % (kill['character']['name'], kill['shipType']['name']) - - # sort based on flag to get proper rack position - items = sorted(kill['items'], key=lambda k: k['flag']) - - # We create a relation between module flag and module position on fit at time of append: - # this allows us to know which module to apply charges to if need be (see below) - flagMap = {} - - # Charges may show up before or after the module. We process modules first, - # storing any charges that are fitted in a dict and noting their flag (module). - charges = {} - - for mod in items: - if mod['flag'] == 5: # throw out cargo - continue - - item = db.getItem(mod['itemType']['name'], eager="group.category") - - if item.category.name == "Drone": - d = Drone(item) - d.amount = mod['quantityDropped'] if 'quantityDropped' in mod else mod['quantityDestroyed'] - fit.drones.append(d) - elif item.category.name == "Charge": - charges[mod['flag']] = item - else: - m = Module(item) - if m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - fit.modules.append(m) - flagMap[mod['flag']] = fit.modules.index(m) - - for flag, item in charges.items(): - # we do not need to verify valid charge as it comes directly from CCP - fit.modules[flagMap[flag]].charge = item - - return fit - - @classmethod - def importDna(cls, string): - from eos import db - info = string.split(":") - f = Fit() - f.ship = Ship(db.getItem(int(info[0]))) - f.name = "{0} - DNA Imported".format(f.ship.item.name) - for itemInfo in info[1:]: - if itemInfo: - itemID, amount = itemInfo.split(";") - item = db.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 == "Charge": - c = Cargo(item) - c.amount = int(amount) - f.cargo.append(c) - else: - for i in xrange(int(amount)): - try: - m = Module(item) - f.modules.append(m) - except: - pass - if m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - - return f - - @classmethod - def importEft(cls, eftString): - from eos import db - offineSuffix = " /OFFLINE" - - fit = cls() - 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: - fit.ship = Ship(db.getItem(shipType)) - fit.name = fitName - except: - return - - # maintain map of drones and their quantities - droneMap = {} - cargoMap = {} - 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 == 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 = db.getItem(modName, eager="group.category") - except: - # if no data can be found (old names) - continue - - if item.category.name == "Drone": - extraAmount = int(extraAmount) if extraAmount is not None else 1 - if not modName in droneMap: - droneMap[modName] = 0 - droneMap[modName] += extraAmount - if len(modExtra) == 2 and item.category.name != "Drone": - extraAmount = int(extraAmount) if extraAmount is not None else 1 - if not modName in cargoMap: - cargoMap[modName] = 0 - cargoMap[modName] += extraAmount - elif item.category.name == "Implant": - fit.implants.append(Implant(item)) - else: - try: - m = Module(item) - except ValueError: - continue - if ammoName: - try: - ammo = db.getItem(ammoName) - if m.isValidCharge(ammo) and m.charge is None: - m.charge = ammo - except: - pass - - if setOffline == True and m.isValidState(State.OFFLINE): - m.state = State.OFFLINE - elif m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - - fit.modules.append(m) - - for droneName in droneMap: - d = Drone(db.getItem(droneName)) - d.amount = droneMap[droneName] - fit.drones.append(d) - - for cargoName in cargoMap: - c = Cargo(db.getItem(cargoName)) - c.amount = cargoMap[cargoName] - fit.cargo.append(c) - - return fit - - @classmethod - def importEftCfg(cls, shipname, contents): - """Handle import from EFT config store file""" - # Check if we have such ship in database, bail if we don't - from eos import db - try: - db.getItem(shipname) - except: - return - # If client didn't take care of encoding file contents into Unicode, - # do it using fallback encoding ourselves - if isinstance(contents, str): - contents = unicode(contents, "cp1252") - # List for fits - fits = [] - # List for starting line numbers for each fit - fitIndices = [] - # Separate string into lines - lines = re.split('[\n\r]+', contents) - 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 - f = Fit() - # Strip square brackets and pull out a fit name - f.name = fitLines[0][1:-1] - # Assign ship to fitting - f.ship = Ship(db.getItem(shipname)) - - for i in range(1, len(fitLines)): - line = fitLines[i] - 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) - 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 = db.getItem(droneName, eager="group.category") - except: - continue - if droneItem.category.name != "Drone": - continue - # Add drone to the fitting - d = Drone(droneItem) - d.amount = droneAmount - if entityState == "Active": - d.amountActive = droneAmount - elif entityState == "Inactive": - d.amountActive = 0 - f.drones.append(d) - elif entityType == "Implant": - # Bail if we can't get item or it's not from implant category - try: - implantItem = db.getItem(entityData, eager="group.category") - except: - 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 - f.implants.append(imp) - elif entityType == "Booster": - # Bail if we can't get item or it's not from implant category - try: - boosterItem = db.getItem(entityData, eager="group.category") - except: - 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 - f.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 = db.getItem(cargoName) - except: - continue - # Add Cargo to the fitting - c = Cargo(item) - c.amount = cargoAmount - f.cargo.append(c) - 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 = db.getItem(modName) - except: - continue - - # Create module and activate it if it's activable - m = Module(modItem) - 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 = db.getItem(chargeName, eager="group.category") - if chargeItem.category.name == "Charge": - m.charge = chargeItem - except: - pass - # Append module to fit - f.modules.append(m) - # Append fit to list of fits - fits.append(f) - # Skip fit silently if we get an exception - except Exception: - pass - - return fits - - @classmethod - def importXml(cls, text): - doc = xml.dom.minidom.parseString(text.encode("utf-8")) - fittings = doc.getElementsByTagName("fittings").item(0) - fittings = fittings.getElementsByTagName("fitting") - fits = [] - from eos import db - for fitting in fittings: - f = Fit() - f.name = fitting.getAttribute("name") - # Maelstrom - shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value") - try: - f.ship = Ship(db.getItem(shipType)) - except: - continue - hardwares = fitting.getElementsByTagName("hardware") - for hardware in hardwares: - try: - moduleName = hardware.getAttribute("type") - try: - item = db.getItem(moduleName, eager="group.category") - except: - continue - if item: - if item.category.name == "Drone": - d = Drone(item) - d.amount = int(hardware.getAttribute("qty")) - f.drones.append(d) - elif hardware.getAttribute("slot").lower() == "cargo": - # although the eve client only support charges in cargo, third-party programs - # may support items or "refits" in cargo. Support these by blindly adding all - # cargo, not just charges - c = Cargo(item) - c.amount = int(hardware.getAttribute("qty")) - f.cargo.append(c) - else: - try: - m = Module(item) - # When item can't be added to any slot (unknown item or just charge), ignore it - except ValueError: - continue - if m.isValidState(State.ACTIVE): - m.state = State.ACTIVE - - f.modules.append(m) - except KeyboardInterrupt: - continue - fits.append(f) - - return fits - - EXPORT_ORDER_EFT = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM] - def exportEft(self): - offineSuffix = " /OFFLINE" - export = "[%s, %s]\n" % (self.ship.item.name, self.name) - stuff = {} - for module in self.modules: - slot = module.slot - if not slot in stuff: stuff[slot] = [] - curr = module.item.name if module.item else ("[Empty %s slot]" % Slot.getName(slot).capitalize() if slot is not None else "") - if module.charge: - curr += ", %s" % module.charge.name - if module.state == State.OFFLINE: - curr += offineSuffix - curr += "\n" - stuff[slot].append(curr) - - for slotType in self.EXPORT_ORDER_EFT: - data = stuff.get(slotType) - if data is not None: - export += "\n" - for curr in data: - export += curr - - if len(self.drones) > 0: - export += "\n\n" - for drone in self.drones: - export += "%s x%s\n" % (drone.item.name, drone.amount) - if len(self.cargo) > 0: - for cargo in self.cargo: - export += "%s x%s\n" % (cargo.item.name, cargo.amount) - - if export[-1] == "\n": - export = export[:-1] - - return export - - def exportEftImps(self): - export = self.exportEft() - if len(self.implants) > 0: - export += "\n\n\n" - for implant in self.implants: - export += "%s\n" % (implant.item.name) - - if export[-1] == "\n": - export = export[:-1] - - return export - - def exportDna(self): - dna = str(self.shipID) - mods = OrderedDict() - charges = OrderedDict() - for mod in self.modules: - if not mod.isEmpty: - if not mod.itemID in mods: - mods[mod.itemID] = 0 - mods[mod.itemID] += 1 - - if mod.charge: - if not mod.chargeID in charges: - charges[mod.chargeID] = 0 - # `or 1` because some charges (ie scripts) are without qty - charges[mod.chargeID] += mod.numShots or 1 - - for mod in mods: - dna += ":{0};{1}".format(mod, mods[mod]) - - for drone in self.drones: - dna += ":{0};{1}".format(drone.itemID, drone.amount) - - for cargo in self.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 not cargo.item.ID 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 + "::" - - @classmethod - def exportXml(cls, *fits): - doc = xml.dom.minidom.Document() - fittings = doc.createElement("fittings") - doc.appendChild(fittings) - for fit in fits: - fitting = doc.createElement("fitting") - fitting.setAttribute("name", fit.name) - fittings.appendChild(fitting) - description = doc.createElement("description") - description.setAttribute("value", "") - fitting.appendChild(description) - shipType = doc.createElement("shipType") - shipType.setAttribute("value", fit.ship.item.name) - fitting.appendChild(shipType) - - charges = {} - slotNum = {} - for module in fit.modules: - if module.isEmpty: - continue - - slot = module.slot - if not slot 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: - if not module.charge.name in charges: - charges[module.charge.name] = 0 - # `or 1` because some charges (ie scripts) are without qty - charges[module.charge.name] += module.numShots 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 cargo in fit.cargo: - if not cargo.item.name in charges: - charges[cargo.item.name] = 0 - charges[cargo.item.name] += cargo.amount - - for name, qty in charges.items(): - hardware = doc.createElement("hardware") - hardware.setAttribute("qty", "%d" % qty) - hardware.setAttribute("slot", "cargo") - hardware.setAttribute("type", name) - fitting.appendChild(hardware) - - return doc.toprettyxml() - @reconstructor def init(self): self.build() diff --git a/service/fit.py b/service/fit.py index da4d26ff6..d37e9dd09 100644 --- a/service/fit.py +++ b/service/fit.py @@ -27,13 +27,15 @@ from codecs import open import eos.db import eos.types -from eos.types import State, Slot, Module, Cargo +from eos.types import State, Slot from service.market import Market from service.damagePattern import DamagePattern from service.character import Character from service.fleet import Fleet from service.settings import SettingsProvider +from service.port import Port + class FitBackupThread(threading.Thread): def __init__(self, path, callback): @@ -51,6 +53,7 @@ class FitBackupThread(threading.Thread): backupFile.close() wx.CallAfter(self.callback) + class FitImportThread(threading.Thread): def __init__(self, paths, callback): threading.Thread.__init__(self) @@ -67,8 +70,10 @@ class FitImportThread(threading.Thread): importedFits += pathImported wx.CallAfter(self.callback, importedFits) + class Fit(object): instance = None + @classmethod def getInstance(cls): if cls.instance is None: @@ -92,10 +97,11 @@ class Fit(object): "rackLabels": False, "compactSkills": False} - self.serviceFittingOptions = SettingsProvider.getInstance().getSettings("pyfaServiceFittingOptions", serviceFittingDefaultOptions) + self.serviceFittingOptions = SettingsProvider.getInstance().getSettings( + "pyfaServiceFittingOptions", serviceFittingDefaultOptions) - - def getAllFits(self): + @staticmethod + def getAllFits(): fits = eos.db.getFitList() names = [] for fit in fits: @@ -103,17 +109,19 @@ class Fit(object): return names - def getFitsWithShip(self, id): - ''' Lists fits of shipID, used with shipBrowser ''' - fits = eos.db.getFitsWithShip(id) + @staticmethod + def getFitsWithShip(shipID): + """ Lists fits of shipID, used with shipBrowser """ + fits = eos.db.getFitsWithShip(shipID) names = [] for fit in fits: names.append((fit.ID, fit.name, fit.booster, fit.timestamp)) return names - def getBoosterFits(self): - ''' Lists fits flagged as booster ''' + @staticmethod + def getBoosterFits(): + """ Lists fits flagged as booster """ fits = eos.db.getBoosterFits() names = [] for fit in fits: @@ -121,20 +129,22 @@ class Fit(object): return names - def countFitsWithShip(self, id): - count = eos.db.countFitsWithShip(id) + @staticmethod + def countFitsWithShip(shipID): + count = eos.db.countFitsWithShip(shipID) return count - def groupHasFits(self, id): + def groupHasFits(self, groupID): sMkt = Market.getInstance() - grp = sMkt.getGroup(id, eager=("items", "group")) + grp = sMkt.getGroup(groupID, eager=("items", "group")) items = sMkt.getItemsByGroup(grp) for item in items: if self.countFitsWithShip(item.ID) > 0: return True return False - def getModule(self, fitID, pos): + @staticmethod + def getModule(fitID, pos): fit = eos.db.getFit(fitID) return fit.modules[pos] @@ -149,12 +159,14 @@ class Fit(object): self.recalc(fit) return fit.ID - def toggleBoostFit(self, fitID): + @staticmethod + def toggleBoostFit(fitID): fit = eos.db.getFit(fitID) fit.booster = not fit.booster eos.db.commit() - def renameFit(self, fitID, newName): + @staticmethod + def renameFit(fitID, newName): fit = eos.db.getFit(fitID) fit.name = newName eos.db.commit() @@ -167,13 +179,15 @@ class Fit(object): eos.db.remove(fit) - def copyFit(self, fitID): + @staticmethod + def copyFit(fitID): fit = eos.db.getFit(fitID) newFit = copy.deepcopy(fit) eos.db.save(newFit) return newFit.ID - def clearFit(self, fitID): + @staticmethod + def clearFit(fitID): if fitID is None: return None @@ -181,8 +195,9 @@ class Fit(object): fit.clear() return fit - def removeProjectedData(self, fitID): - '''Removes projection relation from ships that have fitID as projection. See GitHub issue #90''' + @staticmethod + def removeProjectedData(fitID): + """Removes projection relation from ships that have fitID as projection. See GitHub issue #90""" fit = eos.db.getFit(fitID) fits = eos.db.getProjectedFits(fitID) @@ -234,11 +249,14 @@ class Fit(object): fit.inited = True return fit - def searchFits(self, name): + @staticmethod + def searchFits(name): results = eos.db.searchFits(name) fits = [] for fit in results: - fits.append((fit.ID, fit.name, fit.ship.item.ID, fit.ship.item.name, fit.booster, fit.timestamp)) + fits.append(( + fit.ID, fit.name, fit.ship.item.ID, fit.ship.item.name, fit.booster, + fit.timestamp)) return fits def addImplant(self, fitID, itemID): @@ -297,7 +315,8 @@ class Fit(object): fit = eos.db.getFit(fitID) if isinstance(thing, int): - thing = eos.db.getItem(thing, eager=("attributes", "group.category")) + thing = eos.db.getItem(thing, + eager=("attributes", "group.category")) if isinstance(thing, eos.types.Fit): if thing.ID == fitID: @@ -397,53 +416,53 @@ class Fit(object): eos.db.commit() return numSlots != len(fit.modules) - def moveCargoToModule(self, fitID, moduleIdx, cargoIdx, copy = False): - ''' + def moveCargoToModule(self, fitID, moduleIdx, cargoIdx, copyMod=False): + """ Moves cargo to fitting window. Can either do a copy, move, or swap with current module If we try to copy/move into a spot with a non-empty module, we swap instead. To avoid redundancy in converting Cargo item, this function does the sanity checks as opposed to the GUI View. This is different than how the normal .swapModules() does things, which is mostly a blind swap. - ''' + """ fit = eos.db.getFit(fitID) module = fit.modules[moduleIdx] # Gather modules and convert Cargo item to Module, silently return if not a module try: - cargoP = Module(fit.cargo[cargoIdx].item) + cargoP = eos.types.Module(fit.cargo[cargoIdx].item) cargoP.owner = fit if cargoP.isValidState(State.ACTIVE): cargoP.state = State.ACTIVE except: return - if cargoP.slot != module.slot: # can't swap modules to different racks + if cargoP.slot != module.slot: # can't swap modules to different racks return # remove module that we are trying to move cargo to fit.modules.remove(module) - if not cargoP.fits(fit): #if cargo doesn't fit, rollback and return + if not cargoP.fits(fit): # if cargo doesn't fit, rollback and return fit.modules.insert(moduleIdx, module) return fit.modules.insert(moduleIdx, cargoP) - if not copy: # remove existing cargo if not cloning - fit.cargo.remove(fit.cargo[cargoIdx]) + if not copyMod: # remove existing cargo if not cloning + fit.cargo.remove(fit.cargo[cargoIdx]) - if not module.isEmpty: # if module is placeholder, we don't want to convert/add it - moduleP = Cargo(module.item) + if not module.isEmpty: # if module is placeholder, we don't want to convert/add it + moduleP = eos.types.Cargo(module.item) moduleP.amount = 1 fit.cargo.insert(cargoIdx, moduleP) eos.db.commit() self.recalc(fit) - - def swapModules(self, fitID, src, dst): + @staticmethod + def swapModules(fitID, src, dst): fit = eos.db.getFit(fitID) # Gather modules srcMod = fit.modules[src] @@ -458,16 +477,16 @@ class Fit(object): eos.db.commit() def cloneModule(self, fitID, src, dst): - ''' + """ Clone a module from src to dst This will overwrite dst! Checking for empty module must be done at a higher level - ''' + """ fit = eos.db.getFit(fitID) # Gather modules srcMod = fit.modules[src] - dstMod = fit.modules[dst] # should be a placeholder module + dstMod = fit.modules[dst] # should be a placeholder module new = copy.deepcopy(srcMod) new.owner = fit @@ -479,12 +498,13 @@ class Fit(object): eos.db.commit() self.recalc(fit) - def addCargo(self, fitID, itemID, amount=1, replace = False): - '''Adds cargo via typeID of item. If replace = True, we replace amount with + def addCargo(self, fitID, itemID, amount=1, replace=False): + """ + Adds cargo via typeID of item. If replace = True, we replace amount with given parameter, otherwise we increment - ''' + """ - if fitID == None: + if fitID is None: return False fit = eos.db.getFit(fitID) @@ -525,7 +545,7 @@ class Fit(object): return True def addDrone(self, fitID, itemID): - if fitID == None: + if fitID is None: return False fit = eos.db.getFit(fitID) @@ -551,7 +571,7 @@ class Fit(object): return False def mergeDrones(self, fitID, d1, d2, projected=False): - if fitID == None: + if fitID is None: return False fit = eos.db.getFit(fitID) @@ -569,7 +589,8 @@ class Fit(object): self.recalc(fit) return True - def splitDrones(self, fit, d, amount, l): + @staticmethod + def splitDrones(fit, d, amount, l): total = d.amount active = d.amountActive > 0 d.amount = amount @@ -582,14 +603,14 @@ class Fit(object): eos.db.commit() def splitProjectedDroneStack(self, fitID, d, amount): - if fitID == None: + if fitID is None: return False fit = eos.db.getFit(fitID) self.splitDrones(fit, d, amount, fit.projectedDrones) def splitDroneStack(self, fitID, d, amount): - if fitID == None: + if fitID is None: return False fit = eos.db.getFit(fitID) @@ -650,7 +671,8 @@ class Fit(object): fit.character = self.character = eos.db.getCharacter(charID) self.recalc(fit) - def isAmmo(self, itemID): + @staticmethod + def isAmmo(itemID): return eos.db.getItem(itemID).category.name == "Charge" def setAmmo(self, fitID, ammoID, modules): @@ -666,7 +688,8 @@ class Fit(object): self.recalc(fit) - def getDamagePattern(self, fitID): + @staticmethod + def getDamagePattern(fitID): if fitID is None: return @@ -700,27 +723,30 @@ class Fit(object): fit.damagePattern = dp self.recalc(fit) - def exportFit(self, fitID): - fit = eos.db.getFit(fitID) - return fit.exportEft() + @staticmethod + def exportFit(fitID): + return Port.exportEft(fitID) - def exportEftImps(self, fitID): - fit = eos.db.getFit(fitID) - return fit.exportEftImps() + @staticmethod + def exportEftImps(fitID): + return Port.exportEftImps(fitID) - def exportDna(self, fitID): - fit = eos.db.getFit(fitID) - return fit.exportDna() + @staticmethod + def exportDna(fitID): + return Port.exportDna(fitID) - def exportXml(self, *fitIDs): - fits = map(lambda id: eos.db.getFit(id), fitIDs) - return eos.types.Fit.exportXml(*fits) + @staticmethod + def exportXml(*fitIDs): + fits = map(lambda fitID: eos.db.getFit(fitID), fitIDs) + return Port.exportXml(*fits) - def backupFits(self, path, callback): + @staticmethod + def backupFits(path, callback): thread = FitBackupThread(path, callback) thread.start() - def importFitsThreaded(self, paths, callback): + @staticmethod + def importFitsThreaded(paths, callback): thread = FitImportThread(paths, callback) thread.start() @@ -739,20 +765,21 @@ class Fit(object): except UnicodeDecodeError: srcString = unicode(srcString, "cp1252") - type, fits = eos.types.Fit.importAuto(srcString, filename) + _, fits = Port.importAuto(srcString, filename) for fit in fits: fit.character = self.character fit.damagePattern = self.pattern return fits - def importFitFromBuffer(self, buffer, activeFit=None): - type,fits = eos.types.Fit.importAuto(buffer, activeFit=activeFit) + def importFitFromBuffer(self, bufferStr, activeFit=None): + _, fits = Port.importAuto(bufferStr, activeFit=activeFit) for fit in fits: fit.character = self.character fit.damagePattern = self.pattern return fits - def saveImportedFits(self, fits): + @staticmethod + def saveImportedFits(fits): IDs = [] for fit in fits: eos.db.save(fit) @@ -760,7 +787,8 @@ class Fit(object): return IDs - def checkStates(self, fit, base): + @staticmethod + def checkStates(fit, base): changed = False for mod in fit.modules: if mod != base: @@ -783,7 +811,8 @@ class Fit(object): base.state = proposedState for mod in modules: if mod != base: - mod.state = self.__getProposedState(mod, click, proposedState) + mod.state = self.__getProposedState(mod, click, + proposedState) eos.db.commit() fit = eos.db.getFit(fitID) @@ -804,13 +833,13 @@ class Fit(object): projectedMap = {State.OVERHEATED: State.ACTIVE, State.ACTIVE: State.OFFLINE, State.OFFLINE: State.ACTIVE, - State.ONLINE: State.ACTIVE} # Just in case + State.ONLINE: State.ACTIVE} # Just in case def __getProposedState(self, mod, click, proposedState=None): if mod.slot in (Slot.RIG, Slot.SUBSYSTEM) or mod.isEmpty: return State.ONLINE - currState = state = mod.state + currState = mod.state transitionMap = self.projectedMap if mod.projected else self.localMap if proposedState is not None: state = proposedState @@ -821,7 +850,7 @@ class Fit(object): else: state = transitionMap[currState] if not mod.isValidState(state): - state =- 1 + state = -1 if mod.isValidState(state): return state diff --git a/service/market.py b/service/market.py index f6043b11f..1068c8024 100644 --- a/service/market.py +++ b/service/market.py @@ -25,8 +25,6 @@ import Queue import eos.db import eos.types -from eos.itemMapping import itemMapping - from service.settings import SettingsProvider, ProxySettings try: @@ -153,6 +151,35 @@ class Market(): self.shipBrowserWorkerThread.daemon = True self.shipBrowserWorkerThread.start() + # Item overrides. Key must be str of item name, + # value str of name or int of ID to convert it to + self.ITEMS_OVERRIDE = { + "Aliastra Catalyst": "Catalyst", + "Inner Zone Shipping Catalyst": "Catalyst", + "Intaki Syndicate Catalyst": "Catalyst", + "InterBus Catalyst": "Catalyst", + "Quafe Catalyst": "Catalyst", + "Nefantar Thrasher": "Thrasher", + "Sarum Magnate": "Magnate", + "Sukuuvestaa Heron": "Heron:", + "Inner Zone Shipping Imicus": "Imicus", + "Vherokior Probe": "Probe", + "Miasmos Quafe Ultra Edition": "Miasmos", + "Miasmos Quafe Ultramarine Edition": "Miasmos", + "Miasmos Amastris Edition": "Miasmos", + "Tash-Murkon Magnate": "Magnate", + "Scorpion Ishukone Watch": "Scorpion", + "Incursus Aliastra Edition": "Incursus", + "Merlin Nugoeihuvi Edition": "Merlin", + "Police Pursuit Comet": "Federation Navy Comet", + "Punisher Kador Edition": "Punisher", + "Rifter Krusual Edition": "Rifter", + "Abaddon Kador Edition": "Abaddon", + "Hyperion Aliastra Edition": "Hyperion", + "Maelstrom Krusual Edition": "Maelstrom", + "Rokh Nugoeihuvi Edition": "Rokh", + "Mammoth Nefantar Edition": "Mammoth" } + # Items' group overrides self.customGroups = set() # Limited edition ships @@ -214,7 +241,7 @@ class Market(): "Guristas Shuttle": False} # do not publish anything that we convert - for name, _ in itemMapping.iteritems(): + for name in self.ITEMS_OVERRIDE: self.ITEMS_FORCEPUBLISHED[name] = False # List of groups which are forcibly published @@ -319,6 +346,8 @@ class Market(): item = eos.db.getItem(id, *args, **kwargs) else: raise TypeError("Need Item object, integer, float or string as argument") + if item.name in self.ITEMS_OVERRIDE: + item = self.getItem(self.ITEMS_OVERRIDE[item.name], *args, **kwargs) return item def getGroup(self, identity, *args, **kwargs): diff --git a/service/port.py b/service/port.py new file mode 100644 index 000000000..f76f9bb08 --- /dev/null +++ b/service/port.py @@ -0,0 +1,719 @@ +#=============================================================================== +# Copyright (C) 2010 Diego Duclos +# +# 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 urllib2 +import json + +from eos.types import State, Slot, Module, Cargo, Fit, Ship, Drone, Implant, Booster +import service + +try: + from collections import OrderedDict +except ImportError: + from gui.utils.compat import OrderedDict + + +class Port(object): + """Service which houses all import/export format functions""" + + @classmethod + def importAuto(cls, string, sourceFileName=None, activeFit=None): + # 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 string is from in-game copy of fitting window + # We match " power" instead of "High power" in case a fit has no high modules + if " power" in firstLine and activeFit is not None: + return "FIT", (cls.importFittingWindow(string, activeFit),) + + # If we have ".*", firstLine) + if chatDna: + return "DNA", (cls.importDna(chatDna.group(1)),) + + # If we have a CREST kill link + killLink = re.search("http://public-crest.eveonline.com/killmails/(.*)/", firstLine) + if killLink: + return "CREST", (cls.importCrest(tuple(killLink.group(1).split("/"))),) + + # If we have ".*", firstLine) + if killReport: + return "CREST", (cls.importCrest(tuple(killReport.group(1).split(":"))),) + + # If XML-style start of tag encountered, detect as XML + if re.match("<", firstLine): + return "XML", cls.importXml(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 sourceFileName is not None: + shipName = sourceFileName.rsplit('.')[0] + return "EFT Config", cls.importEftCfg(shipName, string) + + # 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 importFittingWindow(string, activeFit): + sMkt = service.Market.getInstance() + sFit = service.Fit.getInstance() + + activeFit = sFit.getFit(activeFit) + + # if the current fit has mods, do not mess with it. Instead, make new fit + if activeFit.modCount > 0: + fit = Fit() + fit.ship = Ship(sMkt.getItem(activeFit.ship.item.ID)) + fit.name = "%s (copy)" % activeFit.name + else: + fit = activeFit + lines = re.split('[\n\r]+', string) + + droneMap = {} + cargoMap = {} + modules = [] + + for i in range(1, len(lines)): + line = lines[i].strip() + if not line: + continue + + try: + amount, modName = line.split("x ") + amount = int(amount) + item = sMkt.getItem(modName, eager="group.category") + except: + # if no data can be found (old names) + continue + + if item.category.name == "Drone": + if not modName in droneMap: + droneMap[modName] = 0 + droneMap[modName] += amount + elif item.category.name == "Charge": + if not modName in cargoMap: + cargoMap[modName] = 0 + cargoMap[modName] += amount + else: + for _ in xrange(amount): + try: + m = Module(item) + except ValueError: + continue + # If we are importing T3 ship, we must apply subsystems first, then + # calcModAttr() to get the ship slots + if m.slot == Slot.SUBSYSTEM and m.fits(fit): + fit.modules.append(m) + else: + modules.append(m) + + fit.clear() + fit.calculateModifiedAttributes() + + for m in modules: + # we check to see if module fits as a basic sanity check + # if it doesn't then the imported fit is most likely invalid + # (ie: user tried to import Legion fit to a Rifter) + if m.fits(fit): + fit.modules.append(m) + if m.isValidState(State.ACTIVE): + m.state = State.ACTIVE + m.owner = fit # not sure why this is required when it's not for other import methods, but whatever + else: + return + + 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 importCrest(info): + sMkt = service.Market.getInstance() + try: + # @todo: proxy + response = urllib2.urlopen("https://public-crest.eveonline.com/killmails/%s/%s/" % info) + except: + return + + kill = (json.loads(response.read()))['victim'] + + fit = Fit() + fit.ship = Ship(sMkt.getItem(kill['shipType']['name'])) + fit.name = "CREST: %s's %s" % (kill['character']['name'], kill['shipType']['name']) + + # sort based on flag to get proper rack position + items = sorted(kill['items'], key=lambda k: k['flag']) + + # We create a relation between module flag and module position on fit at time of append: + # this allows us to know which module to apply charges to if need be (see below) + flagMap = {} + + # Charges may show up before or after the module. We process modules first, + # storing any charges that are fitted in a dict and noting their flag (module). + charges = {} + + for mod in items: + if mod['flag'] == 5: # throw out cargo + continue + + item = sMkt.getItem(mod['itemType']['name'], eager="group.category") + + if item.category.name == "Drone": + d = Drone(item) + d.amount = mod['quantityDropped'] if 'quantityDropped' in mod else mod['quantityDestroyed'] + fit.drones.append(d) + elif item.category.name == "Charge": + charges[mod['flag']] = item + else: + m = Module(item) + if m.isValidState(State.ACTIVE): + m.state = State.ACTIVE + fit.modules.append(m) + flagMap[mod['flag']] = fit.modules.index(m) + + for flag, item in charges.items(): + # we do not need to verify valid charge as it comes directly from CCP + fit.modules[flagMap[flag]].charge = item + + return fit + + @staticmethod + def importDna(string): + sMkt = service.Market.getInstance() + info = string.split(":") + + f = Fit() + f.ship = Ship(sMkt.getItem(int(info[0]))) + f.name = "{0} - DNA Imported".format(f.ship.item.name) + + 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 == "Charge": + c = Cargo(item) + c.amount = int(amount) + f.cargo.append(c) + else: + for i in xrange(int(amount)): + try: + m = Module(item) + f.modules.append(m) + except: + pass + if m.isValidState(State.ACTIVE): + m.state = State.ACTIVE + + return f + + @staticmethod + def importEft(eftString): + sMkt = service.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) + fit.ship = Ship(ship) + fit.name = fitName + except: + return + + # maintain map of drones and their quantities + droneMap = {} + cargoMap = {} + 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) + continue + + if item.category.name == "Drone": + extraAmount = int(extraAmount) if extraAmount is not None else 1 + if not modName in droneMap: + droneMap[modName] = 0 + droneMap[modName] += extraAmount + if len(modExtra) == 2 and item.category.name != "Drone": + extraAmount = int(extraAmount) if extraAmount is not None else 1 + if not modName in cargoMap: + cargoMap[modName] = 0 + cargoMap[modName] += extraAmount + elif item.category.name == "Implant": + fit.implants.append(Implant(item)) + else: + try: + m = Module(item) + except ValueError: + continue + 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 + + 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): + """Handle import from EFT config store file""" + + # Check if we have such ship in database, bail if we don't + sMkt = service.Market.getInstance() + try: + sMkt.getItem(shipname) + except: + return + + # If client didn't take care of encoding file contents into Unicode, + # do it using fallback encoding ourselves + if isinstance(contents, str): + contents = unicode(contents, "cp1252") + + 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 + f = Fit() + # Strip square brackets and pull out a fit name + f.name = fitLines[0][1:-1] + # Assign ship to fitting + f.ship = Ship(sMkt.getItem(shipname)) + + 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) + + 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: + continue + if droneItem.category.name != "Drone": + continue + # Add drone to the fitting + d = Drone(droneItem) + d.amount = droneAmount + if entityState == "Active": + d.amountActive = droneAmount + elif entityState == "Inactive": + d.amountActive = 0 + f.drones.append(d) + 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: + 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 + f.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: + 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 + f.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: + continue + # Add Cargo to the fitting + c = Cargo(item) + c.amount = cargoAmount + f.cargo.append(c) + 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: + continue + + # Create module and activate it if it's activable + m = Module(modItem) + 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: + pass + # Append module to fit + f.modules.append(m) + # Append fit to list of fits + fits.append(f) + # Skip fit silently if we get an exception + except Exception: + pass + + return fits + + @staticmethod + def importXml(text): + sMkt = service.Market.getInstance() + + doc = xml.dom.minidom.parseString(text.encode("utf-8")) + fittings = doc.getElementsByTagName("fittings").item(0) + fittings = fittings.getElementsByTagName("fitting") + fits = [] + for fitting in fittings: + f = Fit() + f.name = fitting.getAttribute("name") + # Maelstrom + shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value") + try: + f.ship = Ship(sMkt.getItem(shipType)) + except: + continue + hardwares = fitting.getElementsByTagName("hardware") + for hardware in hardwares: + try: + moduleName = hardware.getAttribute("type") + try: + item = sMkt.getItem(moduleName, eager="group.category") + except: + continue + if item: + if item.category.name == "Drone": + d = Drone(item) + d.amount = int(hardware.getAttribute("qty")) + f.drones.append(d) + elif hardware.getAttribute("slot").lower() == "cargo": + # although the eve client only support charges in cargo, third-party programs + # may support items or "refits" in cargo. Support these by blindly adding all + # cargo, not just charges + c = Cargo(item) + c.amount = int(hardware.getAttribute("qty")) + f.cargo.append(c) + else: + try: + m = Module(item) + # When item can't be added to any slot (unknown item or just charge), ignore it + except ValueError: + continue + if m.isValidState(State.ACTIVE): + m.state = State.ACTIVE + + f.modules.append(m) + except KeyboardInterrupt: + continue + fits.append(f) + + return fits + + @staticmethod + def exportEft(fitID): + sFit = service.Fit.getInstance() + fit = sFit.getFit(fitID) + + offineSuffix = " /OFFLINE" + export = "[%s, %s]\n" % (fit.ship.item.name, fit.name) + stuff = {} + for module in fit.modules: + slot = module.slot + if not slot in stuff: + stuff[slot] = [] + curr = module.item.name if module.item else ("[Empty %s slot]" % Slot.getName(slot).capitalize() if slot is not None else "") + if module.charge: + curr += ", %s" % module.charge.name + if module.state == State.OFFLINE: + curr += offineSuffix + curr += "\n" + stuff[slot].append(curr) + + for slotType in [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM]: + data = stuff.get(slotType) + if data is not None: + export += "\n" + for curr in data: + export += curr + + if len(fit.drones) > 0: + export += "\n\n" + 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 export[-1] == "\n": + export = export[:-1] + + return export + + @classmethod + def exportEftImps(cls, fitID): + export = cls.exportEft(fitID) + + sFit = service.Fit.getInstance() + fit = sFit.getFit(fitID) + + if len(fit.implants) > 0: + export += "\n\n\n" + for implant in fit.implants: + export += "%s\n" % implant.item.name + + if export[-1] == "\n": + export = export[:-1] + + return export + + @staticmethod + def exportDna(fitID): + sFit = service.Fit.getInstance() + fit = sFit.getFit(fitID) + + dna = str(fit.shipID) + mods = OrderedDict() + charges = OrderedDict() + for mod in fit.modules: + if not mod.isEmpty: + if not mod.itemID in mods: + mods[mod.itemID] = 0 + mods[mod.itemID] += 1 + + if mod.charge: + if not mod.chargeID in charges: + charges[mod.chargeID] = 0 + # `or 1` because some charges (ie scripts) are without qty + charges[mod.chargeID] += mod.numShots or 1 + + 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 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 not cargo.item.ID 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 + "::" + + @classmethod + def exportXml(cls, *fits): + doc = xml.dom.minidom.Document() + fittings = doc.createElement("fittings") + doc.appendChild(fittings) + for fit in fits: + fitting = doc.createElement("fitting") + fitting.setAttribute("name", fit.name) + fittings.appendChild(fitting) + description = doc.createElement("description") + description.setAttribute("value", "") + fitting.appendChild(description) + shipType = doc.createElement("shipType") + shipType.setAttribute("value", fit.ship.item.name) + fitting.appendChild(shipType) + + charges = {} + slotNum = {} + for module in fit.modules: + if module.isEmpty: + continue + + slot = module.slot + if not slot 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: + if not module.charge.name in charges: + charges[module.charge.name] = 0 + # `or 1` because some charges (ie scripts) are without qty + charges[module.charge.name] += module.numShots 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 cargo in fit.cargo: + if not cargo.item.name in charges: + charges[cargo.item.name] = 0 + charges[cargo.item.name] += cargo.amount + + for name, qty in charges.items(): + hardware = doc.createElement("hardware") + hardware.setAttribute("qty", "%d" % qty) + hardware.setAttribute("slot", "cargo") + hardware.setAttribute("type", name) + fitting.appendChild(hardware) + + return doc.toprettyxml() From b7e2b625624f0e109aa6292b948acb12a1411726 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 23 May 2014 18:58:09 -0400 Subject: [PATCH 3/5] Improved fitting window import; revert addition of staticmethod's in fit service --- service/fit.py | 69 +++++++++++++++++-------------------------------- service/port.py | 7 +++-- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/service/fit.py b/service/fit.py index d37e9dd09..6ca719dc5 100644 --- a/service/fit.py +++ b/service/fit.py @@ -100,8 +100,7 @@ class Fit(object): self.serviceFittingOptions = SettingsProvider.getInstance().getSettings( "pyfaServiceFittingOptions", serviceFittingDefaultOptions) - @staticmethod - def getAllFits(): + def getAllFits(self): fits = eos.db.getFitList() names = [] for fit in fits: @@ -109,8 +108,7 @@ class Fit(object): return names - @staticmethod - def getFitsWithShip(shipID): + def getFitsWithShip(self, shipID): """ Lists fits of shipID, used with shipBrowser """ fits = eos.db.getFitsWithShip(shipID) names = [] @@ -119,8 +117,7 @@ class Fit(object): return names - @staticmethod - def getBoosterFits(): + def getBoosterFits(self): """ Lists fits flagged as booster """ fits = eos.db.getBoosterFits() names = [] @@ -129,8 +126,7 @@ class Fit(object): return names - @staticmethod - def countFitsWithShip(shipID): + def countFitsWithShip(self, shipID): count = eos.db.countFitsWithShip(shipID) return count @@ -143,8 +139,7 @@ class Fit(object): return True return False - @staticmethod - def getModule(fitID, pos): + def getModule(self, fitID, pos): fit = eos.db.getFit(fitID) return fit.modules[pos] @@ -159,14 +154,12 @@ class Fit(object): self.recalc(fit) return fit.ID - @staticmethod - def toggleBoostFit(fitID): + def toggleBoostFit(self, fitID): fit = eos.db.getFit(fitID) fit.booster = not fit.booster eos.db.commit() - @staticmethod - def renameFit(fitID, newName): + def renameFit(self, fitID, newName): fit = eos.db.getFit(fitID) fit.name = newName eos.db.commit() @@ -179,15 +172,13 @@ class Fit(object): eos.db.remove(fit) - @staticmethod - def copyFit(fitID): + def copyFit(self, fitID): fit = eos.db.getFit(fitID) newFit = copy.deepcopy(fit) eos.db.save(newFit) return newFit.ID - @staticmethod - def clearFit(fitID): + def clearFit(self, fitID): if fitID is None: return None @@ -195,8 +186,7 @@ class Fit(object): fit.clear() return fit - @staticmethod - def removeProjectedData(fitID): + def removeProjectedData(self, fitID): """Removes projection relation from ships that have fitID as projection. See GitHub issue #90""" fit = eos.db.getFit(fitID) fits = eos.db.getProjectedFits(fitID) @@ -249,8 +239,7 @@ class Fit(object): fit.inited = True return fit - @staticmethod - def searchFits(name): + def searchFits(self, name): results = eos.db.searchFits(name) fits = [] for fit in results: @@ -461,8 +450,7 @@ class Fit(object): eos.db.commit() self.recalc(fit) - @staticmethod - def swapModules(fitID, src, dst): + def swapModules(self, fitID, src, dst): fit = eos.db.getFit(fitID) # Gather modules srcMod = fit.modules[src] @@ -589,8 +577,7 @@ class Fit(object): self.recalc(fit) return True - @staticmethod - def splitDrones(fit, d, amount, l): + def splitDrones(self, fit, d, amount, l): total = d.amount active = d.amountActive > 0 d.amount = amount @@ -671,8 +658,7 @@ class Fit(object): fit.character = self.character = eos.db.getCharacter(charID) self.recalc(fit) - @staticmethod - def isAmmo(itemID): + def isAmmo(self, itemID): return eos.db.getItem(itemID).category.name == "Charge" def setAmmo(self, fitID, ammoID, modules): @@ -688,8 +674,7 @@ class Fit(object): self.recalc(fit) - @staticmethod - def getDamagePattern(fitID): + def getDamagePattern(self, fitID): if fitID is None: return @@ -723,30 +708,24 @@ class Fit(object): fit.damagePattern = dp self.recalc(fit) - @staticmethod - def exportFit(fitID): + def exportFit(self, fitID): return Port.exportEft(fitID) - @staticmethod - def exportEftImps(fitID): + def exportEftImps(self, fitID): return Port.exportEftImps(fitID) - @staticmethod - def exportDna(fitID): + def exportDna(self, fitID): return Port.exportDna(fitID) - @staticmethod - def exportXml(*fitIDs): + def exportXml(self, *fitIDs): fits = map(lambda fitID: eos.db.getFit(fitID), fitIDs) return Port.exportXml(*fits) - @staticmethod - def backupFits(path, callback): + def backupFits(self, path, callback): thread = FitBackupThread(path, callback) thread.start() - @staticmethod - def importFitsThreaded(paths, callback): + def importFitsThreaded(self, paths, callback): thread = FitImportThread(paths, callback) thread.start() @@ -778,8 +757,7 @@ class Fit(object): fit.damagePattern = self.pattern return fits - @staticmethod - def saveImportedFits(fits): + def saveImportedFits(self, fits): IDs = [] for fit in fits: eos.db.save(fit) @@ -787,8 +765,7 @@ class Fit(object): return IDs - @staticmethod - def checkStates(fit, base): + def checkStates(self, fit, base): changed = False for mod in fit.modules: if mod != base: diff --git a/service/port.py b/service/port.py index f76f9bb08..be72ecade 100644 --- a/service/port.py +++ b/service/port.py @@ -30,6 +30,9 @@ try: except ImportError: from gui.utils.compat import OrderedDict +FIT_WIN_HEADINGS = ["High power", "Medium power", "Low power", "Rig Slot", "Sub System", "Charges"] +EFT_SLOT_ORDER = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM] + class Port(object): """Service which houses all import/export format functions""" @@ -42,7 +45,7 @@ class Port(object): # If string is from in-game copy of fitting window # We match " power" instead of "High power" in case a fit has no high modules - if " power" in firstLine and activeFit is not None: + if firstLine in FIT_WIN_HEADINGS and activeFit is not None: return "FIT", (cls.importFittingWindow(string, activeFit),) # If we have " Date: Tue, 27 May 2014 19:35:51 -0400 Subject: [PATCH 4/5] Fix #106 --- eos/saveddata/module.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index c27f065da..433a43db5 100755 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -564,9 +564,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): #1: It makes sense to run the effect # The effect is either offline # or the effect is passive and the module is in the online state (or higher) + # or the effect is active and the module is in the active state (or higher) # or the effect is overheat and the module is in the overheated state (or higher) #2: the runtimes match + if self.projected or forceProjected: context = "projected", "module" projected = True @@ -575,9 +577,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): projected = False if self.charge is not None: - for effect in self.charge.effects.itervalues(): - if effect.runTime == runTime: - effect.handler(fit, self, ("moduleCharge",)) + # fix for #82 and it's regression #106 + if not projected or (self.projected and not forceProjected): + for effect in self.charge.effects.itervalues(): + if effect.runTime == runTime: + effect.handler(fit, self, ("moduleCharge",)) if self.item: if self.state >= State.OVERHEATED: From 6fc2cfff7170e849ab5f715b86cc69856fd759a8 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Sun, 1 Jun 2014 16:58:42 +0400 Subject: [PATCH 5/5] Unknown for DB items are fetched as None; make sure service doesn't fail in this case --- service/market.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/market.py b/service/market.py index 1068c8024..e7ba80c30 100644 --- a/service/market.py +++ b/service/market.py @@ -346,7 +346,7 @@ class Market(): item = eos.db.getItem(id, *args, **kwargs) else: raise TypeError("Need Item object, integer, float or string as argument") - if item.name in self.ITEMS_OVERRIDE: + if item is not None and item.name in self.ITEMS_OVERRIDE: item = self.getItem(self.ITEMS_OVERRIDE[item.name], *args, **kwargs) return item