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" (aaa5a6ae18)

This commit is contained in:
blitzmann
2015-07-02 00:48:32 -04:00
parent 51696c509f
commit 717080b58c
6 changed files with 79 additions and 89 deletions

View File

@@ -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),

View File

@@ -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),

View File

@@ -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)

View File

@@ -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):

View File

@@ -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):

View File

@@ -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