# ============================================================================= # 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 copy import itertools import json import logging import threading from codecs import open from xml.etree import ElementTree from xml.dom import minidom import gzip # noinspection PyPackageRequirements import wx import config import eos.db from service.eveapi import EVEAPIConnection, ParseXML from eos.saveddata.implant import Implant as es_Implant from eos.saveddata.character import Character as es_Character from eos.saveddata.module import Slot as es_Slot, Module as es_Module from eos.saveddata.fighter import Fighter as es_Fighter logger = logging.getLogger(__name__) class CharacterImportThread(threading.Thread): def __init__(self, paths, callback): threading.Thread.__init__(self) self.name = "CharacterImport" self.paths = paths self.callback = callback def run(self): paths = self.paths sCharacter = Character.getInstance() all5_character = es_Character("All 5", 5) all_skill_ids = [] for skill in all5_character.skills: # Parse out the skill item IDs to make searching it easier later on all_skill_ids.append(skill.itemID) for path in paths: try: # we try to parse api XML data first with open(path, mode='r') as charFile: sheet = ParseXML(charFile) char = sCharacter.new(sheet.name + " (imported)") sCharacter.apiUpdateCharSheet(char.ID, sheet.skills) except: # if it's not api XML data, try this # this is a horrible logic flow, but whatever try: charFile = open(path, mode='r').read() doc = minidom.parseString(charFile) if doc.documentElement.tagName not in ("SerializableCCPCharacter", "SerializableUriCharacter"): logger.error("Incorrect EVEMon XML sheet") raise RuntimeError("Incorrect EVEMon XML sheet") name = doc.getElementsByTagName("name")[0].firstChild.nodeValue skill_els = doc.getElementsByTagName("skill") skills = [] for skill in skill_els: if int(skill.getAttribute("typeID")) in all_skill_ids and (0 <= int(skill.getAttribute("level")) <= 5): skills.append({ "typeID": int(skill.getAttribute("typeID")), "level": int(skill.getAttribute("level")), }) else: logger.error("Attempted to import unknown skill %s (ID: %s) (Level: %s)", skill.getAttribute("name"), skill.getAttribute("typeID"), skill.getAttribute("level"), ) char = sCharacter.new(name + " (EVEMon)") sCharacter.apiUpdateCharSheet(char.ID, skills) except Exception, e: logger.error("Exception on character import:") logger.error(e) continue wx.CallAfter(self.callback) class SkillBackupThread(threading.Thread): def __init__(self, path, saveFmt, activeFit, callback): threading.Thread.__init__(self) self.name = "SkillBackup" self.path = path self.saveFmt = saveFmt self.activeFit = activeFit self.callback = callback def run(self): path = self.path sCharacter = Character.getInstance() if self.saveFmt == "xml" or self.saveFmt == "emp": backupData = sCharacter.exportXml() else: backupData = sCharacter.exportText() if self.saveFmt == "emp": with gzip.open(path, mode='wb') as backupFile: backupFile.write(backupData) else: with open(path, mode='w', encoding='utf-8') as backupFile: backupFile.write(backupData) wx.CallAfter(self.callback) class Character(object): instance = None skillReqsDict = {} @classmethod def getInstance(cls): if cls.instance is None: cls.instance = Character() return cls.instance def __init__(self): # Simply initializes default characters in case they aren't in the database yet self.all0() self.all5() def exportText(self): data = "Pyfa exported plan for \"" + self.skillReqsDict['charname'] + "\"\n" data += "=" * 79 + "\n" data += "\n" item = "" for s in self.skillReqsDict['skills']: if item == "" or not item == s["item"]: item = s["item"] data += "-" * 79 + "\n" data += "Skills required for {}:\n".format(item) data += "{}{}: {}\n".format(" " * s["indent"], s["skill"], int(s["level"])) data += "-" * 79 + "\n" return data def exportXml(self): root = ElementTree.Element("plan") root.attrib["name"] = "Pyfa exported plan for " + self.skillReqsDict['charname'] root.attrib["revision"] = config.evemonMinVersion sorts = ElementTree.SubElement(root, "sorting") sorts.attrib["criteria"] = "None" sorts.attrib["order"] = "None" sorts.attrib["groupByPriority"] = "false" skillsSeen = set() for s in self.skillReqsDict['skills']: skillKey = str(s["skillID"]) + "::" + s["skill"] + "::" + str(int(s["level"])) if skillKey in skillsSeen: pass # Duplicate skills confuse EVEMon else: skillsSeen.add(skillKey) entry = ElementTree.SubElement(root, "entry") entry.attrib["skillID"] = str(s["skillID"]) entry.attrib["skill"] = s["skill"] entry.attrib["level"] = str(int(s["level"])) entry.attrib["priority"] = "3" entry.attrib["type"] = "Prerequisite" notes = ElementTree.SubElement(entry, "notes") notes.text = entry.attrib["skill"] # tree = ElementTree.ElementTree(root) data = ElementTree.tostring(root, 'utf-8') prettydata = minidom.parseString(data).toprettyxml(indent=" ") return prettydata def backupSkills(self, path, saveFmt, activeFit, callback): thread = SkillBackupThread(path, saveFmt, activeFit, callback) thread.start() def importCharacter(self, path, callback): thread = CharacterImportThread(path, callback) thread.start() def all0(self): return es_Character.getAll0() def all0ID(self): return self.all0().ID def all5(self): return es_Character.getAll5() def all5ID(self): return self.all5().ID def getAlphaCloneList(self): return eos.db.getAlphaCloneList() def getCharacterList(self): return eos.db.getCharacterList() def getCharacter(self, charID): char = eos.db.getCharacter(charID) return char def saveCharacter(self, charID): """Save edited skills""" if charID == self.all5ID() or charID == self.all0ID(): return char = eos.db.getCharacter(charID) char.saveLevels() def saveCharacterAs(self, charID, newName): """Save edited skills as a new character""" char = eos.db.getCharacter(charID) newChar = copy.deepcopy(char) newChar.name = newName eos.db.save(newChar) # revert old char char.revertLevels() def revertCharacter(self, charID): """Rollback edited skills""" char = eos.db.getCharacter(charID) char.revertLevels() def getSkillGroups(self): cat = eos.db.getCategory(16) groups = [] for grp in cat.groups: if grp.published: groups.append((grp.ID, grp.name)) return groups def getSkills(self, groupID): group = eos.db.getGroup(groupID) skills = [] for skill in group.items: if skill.published is True: skills.append((skill.ID, skill.name)) return skills def setAlphaClone(self, char, cloneID): char.alphaCloneID = cloneID eos.db.commit() def getSkillDescription(self, itemID): return eos.db.getItem(itemID).description def getGroupDescription(self, groupID): return eos.db.getMarketGroup(groupID).description def getSkillLevel(self, charID, skillID): skill = eos.db.getCharacter(charID).getSkill(skillID) return (skill.level if skill.learned else "Not learned", skill.isDirty) def getDirtySkills(self, charID): return eos.db.getCharacter(charID).dirtySkills def getCharName(self, charID): return eos.db.getCharacter(charID).name def new(self, name="New Character"): char = es_Character(name) eos.db.save(char) return char def rename(self, char, newName): if char.name in ("All 0", "All 5"): logger.info("Cannot rename built in characters.") else: char.name = newName eos.db.commit() def copy(self, char): newChar = copy.deepcopy(char) eos.db.save(newChar) return newChar def delete(self, char): eos.db.remove(char) def getApiDetails(self, charID): char = eos.db.getCharacter(charID) if char.chars is not None: chars = json.loads(char.chars) else: chars = None return (char.apiID or "", char.apiKey or "", char.defaultChar or "", chars or []) def apiEnabled(self, charID): id_, key, default, _ = self.getApiDetails(charID) return id_ is not "" and key is not "" and default is not "" def apiCharList(self, charID, userID, apiKey): char = eos.db.getCharacter(charID) char.apiID = userID char.apiKey = apiKey api = EVEAPIConnection() auth = api.auth(keyID=userID, vCode=apiKey) apiResult = auth.account.Characters() charList = map(lambda c: unicode(c.name), apiResult.characters) char.chars = json.dumps(charList) return charList def apiFetch(self, charID, charName): dbChar = eos.db.getCharacter(charID) dbChar.defaultChar = charName api = EVEAPIConnection() auth = api.auth(keyID=dbChar.apiID, vCode=dbChar.apiKey) apiResult = auth.account.Characters() charID = None for char in apiResult.characters: if char.name == charName: charID = char.characterID if charID is None: return sheet = auth.character(charID).CharacterSheet() dbChar.apiUpdateCharSheet(sheet.skills) eos.db.commit() def apiUpdateCharSheet(self, charID, skills): char = eos.db.getCharacter(charID) char.apiUpdateCharSheet(skills) eos.db.commit() def changeLevel(self, charID, skillID, level, persist=False): char = eos.db.getCharacter(charID) skill = char.getSkill(skillID) if isinstance(level, basestring) or level > 5 or level < 0: skill.level = None else: skill.level = level if persist: skill.saveLevel() eos.db.commit() def revertLevel(self, charID, skillID): char = eos.db.getCharacter(charID) skill = char.getSkill(skillID) skill.revert() def saveSkill(self, charID, skillID): char = eos.db.getCharacter(charID) skill = char.getSkill(skillID) skill.saveLevel() def addImplant(self, charID, itemID): char = eos.db.getCharacter(charID) if char.ro: logger.error("Trying to add implant to read-only character") return implant = es_Implant(eos.db.getItem(itemID)) char.implants.append(implant) eos.db.commit() def removeImplant(self, charID, implant): char = eos.db.getCharacter(charID) char.implants.remove(implant) eos.db.commit() def getImplants(self, charID): char = eos.db.getCharacter(charID) return char.implants def checkRequirements(self, fit): # toCheck = [] reqs = {} for thing in itertools.chain(fit.modules, fit.drones, fit.fighters, (fit.ship,)): if isinstance(thing, es_Module) and thing.slot == es_Slot.RIG: continue for attr in ("item", "charge"): if attr == "charge" and isinstance(thing, es_Fighter): # Fighter Bombers are automatically charged with micro bombs. # These have skill requirements attached, but aren't used in EVE. continue subThing = getattr(thing, attr, None) subReqs = {} if subThing is not None: if isinstance(thing, es_Fighter) and attr == "charge": continue self._checkRequirements(fit, fit.character, subThing, subReqs) if subReqs: reqs[subThing] = subReqs return reqs def _checkRequirements(self, fit, char, subThing, reqs): for req, level in subThing.requiredSkills.iteritems(): name = req.name ID = req.ID info = reqs.get(name) currLevel, subs = info if info is not None else 0, {} if level > currLevel and (char is None or char.getSkill(req).level < level): reqs[name] = (level, ID, subs) self._checkRequirements(fit, char, req, subs) return reqs