From 717080b58cb3defc3408df015285fd79b543e176 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 2 Jul 2015 00:48:32 -0400 Subject: [PATCH] Handle invalid implants and boosters. Uses a different method to ensure implant and booster slot is not duplicated. Still need to modify existing databases to remove Booster table constraint. Reverts a previous commit: "Gracefully handle invalid boosters in database (both itemIDs that don't exist as well as non-booster items). Implants need a little more work" (aaa5a6ae18336afa3cf72e15daf030d8345e6b01) --- eos/db/saveddata/booster.py | 2 +- eos/db/saveddata/fit.py | 2 +- eos/effectHandlerHelpers.py | 58 ++++++++++++----------------------- eos/saveddata/booster.py | 60 ++++++++++++++++++++----------------- eos/saveddata/implant.py | 44 ++++++++++++++++----------- service/fit.py | 2 -- 6 files changed, 79 insertions(+), 89 deletions(-) diff --git a/eos/db/saveddata/booster.py b/eos/db/saveddata/booster.py index 459bb76b5..a31904b12 100644 --- a/eos/db/saveddata/booster.py +++ b/eos/db/saveddata/booster.py @@ -29,7 +29,7 @@ boosters_table = Table("boosters", saveddata_meta, Column("itemID", Integer), Column("fitID", Integer, ForeignKey("fits.ID"), nullable = False), Column("active", Boolean), - UniqueConstraint("itemID", "fitID")) + ) activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta, Column("boosterID", ForeignKey("boosters.ID"), primary_key = True), diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 478a576a6..0677728d8 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -60,7 +60,7 @@ mapper(Fit, fits_table, primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)), "_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True, primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)), - "_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True, + "_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', backref='fit', single_parent=True, primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID, secondaryjoin = fitImplants_table.c.implantID == Implant.ID, secondary = fitImplants_table), diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py index be669a24a..1311dcf6a 100644 --- a/eos/effectHandlerHelpers.py +++ b/eos/effectHandlerHelpers.py @@ -164,57 +164,37 @@ class HandledDroneCargoList(HandledList): for o in self.find(item): return o - def append(self, obj): - HandledList.append(self, obj) + def append(self, thing): + HandledList.append(self, thing) - if obj.isInvalid: + if thing.isInvalid: # we must flag it as modified, otherwise it will not be removed from the database - flag_modified(obj, "itemID") - self.remove(obj) + flag_modified(thing, "itemID") + self.remove(thing) class HandledImplantBoosterList(HandledList): - def __init__(self): - self.__slotCache = {} + def append(self, thing): + if thing.isInvalid: + HandledList.append(self, thing) + self.remove(thing) - def append(self, implant): - try: - if self.__slotCache.has_key(implant.slot): - raise ValueError("Implant/Booster slot already in use, remove the old one first or set replace = True") - self.__slotCache[implant.slot] = implant - HandledList.append(self, implant) - except: - # if anything goes wrong, simply remove the item - eos.db.remove(implant) + # if needed, remove booster that was occupying slot + oldObj = next((m for m in self if m.slot == thing.slot), None) + if oldObj: + self.remove(oldObj) - def remove(self, implant): - HandledList.remove(self, implant) - del self.__slotCache[implant.slot] - # While we deleted this implant, in edge case seems like not all references - # to it are removed and object still lives in session; forcibly remove it, - # or otherwise when adding the same booster twice booster's table (typeID, fitID) - # constraint will report database integrity error - # TODO: make a proper fix, probably by adjusting fit-boosters sqlalchemy relationships - eos.db.remove(implant) + HandledList.append(self, thing) - def freeSlot(self, slot): - if hasattr(slot, "slot"): - slot = slot.slot - - try: - implant = self.__slotCache[slot] - except KeyError: - return False - try: - self.remove(implant) - except ValueError: - return False - return True + def remove(self, thing): + # We must flag it as modified, otherwise it not be removed from the database + flag_modified(thing, "itemID") + HandledList.remove(self, thing) class HandledProjectedModList(HandledList): def append(self, proj): if proj.isInvalid: # we must include it before we remove it. doing it this way ensures - # rows and relationships in databse are removed as well + # rows and relationships in database are removed as well HandledList.append(self, proj) self.remove(proj) diff --git a/eos/saveddata/booster.py b/eos/saveddata/booster.py index d2125404f..b9b9a5d95 100644 --- a/eos/saveddata/booster.py +++ b/eos/saveddata/booster.py @@ -20,39 +20,48 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.effectHandlerHelpers import HandledItem from sqlalchemy.orm import reconstructor, validates +import eos.db class Booster(HandledItem, ItemAttrShortcut): def __init__(self, item): - self.__slot = self.__calculateSlot(item) - self.itemID = item.ID self.__item = item + self.__invalid = False + self.itemID = item.ID if item is not None else None self.active = True self.build() @reconstructor def init(self): + """Initialize a booster from the database and validate""" self.__item = None + self.__invalid = False + + if self.itemID: + # if item does not exist, set invalid + item = eos.db.getItem(self.itemID) + if item is None: + self.__invalid = True + self.__item = item + + self.build() def build(self): - self.__itemModifiedAttributes = ModifiedAttributeDict() - self.__itemModifiedAttributes.original = self.__item.attributes - self.__sideEffects = [] - for effect in self.item.effects.itervalues(): - if effect.isType("boosterSideEffect"): - s = SideEffect(self) - s.effect = effect - s.active = effect.ID in self.__activeSideEffectIDs - self.__sideEffects.append(s) + if self.__item and self.__item.group.name != "Booster": + self.__invalid = True - def __fetchItemInfo(self): - import eos.db - item = eos.db.getItem(self.itemID) - if item: - self.__item = item + self.__itemModifiedAttributes = ModifiedAttributeDict() + self.__sideEffects = [] + + if self.__item: + self.__itemModifiedAttributes.original = self.__item.attributes self.__slot = self.__calculateSlot(self.__item) - self.build() - else: - raise ValueError("Invalid item as Booster:", self.itemID) + + for effect in self.__item.effects.itervalues(): + if effect.isType("boosterSideEffect"): + s = SideEffect(self) + s.effect = effect + s.active = effect.ID in self.__activeSideEffectIDs + self.__sideEffects.append(s) def iterSideEffects(self): return self.__sideEffects.__iter__() @@ -66,23 +75,18 @@ class Booster(HandledItem, ItemAttrShortcut): @property def itemModifiedAttributes(self): - if self.__item is None: - self.__fetchItemInfo() - return self.__itemModifiedAttributes @property - def slot(self): - if self.__item is None: - self.__fetchItemInfo() + def isInvalid(self): + return self.__invalid + @property + def slot(self): return self.__slot @property def item(self): - if self.__item is None: - self.__fetchItemInfo() - return self.__item def __calculateSlot(self, item): diff --git a/eos/saveddata/implant.py b/eos/saveddata/implant.py index 036eabbc3..ed7002f31 100644 --- a/eos/saveddata/implant.py +++ b/eos/saveddata/implant.py @@ -20,46 +20,54 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut from eos.effectHandlerHelpers import HandledItem from sqlalchemy.orm import validates, reconstructor +import eos.db class Implant(HandledItem, ItemAttrShortcut): def __init__(self, item): - self.__slot = self.__calculateSlot(item) self.__item = item - self.itemID = item.ID + self.__invalid = False + self.itemID = item.ID if item is not None else None self.active = True - self.__itemModifiedAttributes = ModifiedAttributeDict() - self.__itemModifiedAttributes.original = self.item.attributes + self.build() @reconstructor def init(self): self.__item = None + self.__invalid = False + + if self.itemID: + # if item does not exist, set invalid + item = eos.db.getItem(self.itemID) + if item is None: + self.__invalid = True + self.__item = item + + self.build() + + def build(self): + if self.__item and self.__item.category.name != "Implant": + self.__invalid = True - def __fetchItemInfo(self): - import eos.db - self.__item = eos.db.getItem(self.itemID) self.__itemModifiedAttributes = ModifiedAttributeDict() - self.__itemModifiedAttributes.original = self.__item.attributes - self.__slot = self.__calculateSlot(self.__item) + + if self.__item: + self.__itemModifiedAttributes.original = self.__item.attributes + self.__slot = self.__calculateSlot(self.__item) @property def itemModifiedAttributes(self): - if self.__item is None: - self.__fetchItemInfo() - return self.__itemModifiedAttributes @property - def slot(self): - if self.__item is None: - self.__fetchItemInfo() + def isInvalid(self): + return self.__invalid + @property + def slot(self): return self.__slot @property def item(self): - if self.__item is None: - self.__fetchItemInfo() - return self.__item def __calculateSlot(self, item): diff --git a/service/fit.py b/service/fit.py index 5360a376a..f6c1f362d 100644 --- a/service/fit.py +++ b/service/fit.py @@ -274,7 +274,6 @@ class Fit(object): except ValueError: return False - fit.implants.freeSlot(implant) fit.implants.append(implant) self.recalc(fit) return True @@ -300,7 +299,6 @@ class Fit(object): except ValueError: return False - fit.boosters.freeSlot(booster) fit.boosters.append(booster) self.recalc(fit) return True