Merge branch 'feature/recent' into dev

Conflicts:
	eos/db/saveddata/character.py
	eos/db/saveddata/fit.py

Implements #983 but utilizing sqlalchemy events to update the fit modified date whenever something is added/changed.
This commit is contained in:
blitzmann
2017-05-07 20:48:41 -04:00
23 changed files with 356 additions and 103 deletions

View File

@@ -27,12 +27,11 @@ from eos.db.gamedata.group import groups_table
from eos.db.util import processEager, processWhere
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData
cache = {}
configVal = getattr(eos.config, "gamedataCache", None)
if configVal is True:
def cachedQuery(amount, *keywords):
def deco(function):
cache = {}
def checkAndReturn(*args, **kwargs):
useCache = kwargs.pop("useCache", True)
cacheKey = []
@@ -98,6 +97,34 @@ def getItem(lookfor, eager=None):
return item
@cachedQuery(1, "lookfor")
def getItems(lookfor, eager=None):
"""
Gets a list of items. Does a bit of cache hackery to get working properly -- cache
is usually based on function calls with the parameters, needed to extract data directly.
Works well enough. Not currently used, but it's here for possible future inclusion
"""
toGet = []
results = []
for id in lookfor:
if (id, None) in cache:
results.append(cache.get((id, None)))
else:
toGet.append(id)
if len(toGet) > 0:
# Get items that aren't currently cached, and store them in the cache
items = gamedata_session.query(Item).filter(Item.ID.in_(toGet)).all()
for item in items:
cache[(item.ID, None)] = item
results += items
# sort the results based on the original indexing
results.sort(key=lambda x: lookfor.index(x.ID))
return results
@cachedQuery(1, "lookfor")
def getAlphaClone(lookfor, eager=None):
if isinstance(lookfor, int):

View File

@@ -20,7 +20,7 @@
from sqlalchemy import Table, Column, ForeignKey, Integer, Boolean, DateTime
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import mapper, relation
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.booster import Booster
@@ -30,8 +30,8 @@ boosters_table = Table("boosters", saveddata_meta,
Column("itemID", Integer),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False),
Column("active", Boolean),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now()),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
)
# Legacy booster side effect code, should disable but a mapper relies on it.

View File

@@ -18,19 +18,24 @@
# ===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
from sqlalchemy.orm import mapper, relation
import datetime
from eos.db import saveddata_meta
from eos.saveddata.cargo import Cargo
from eos.saveddata.fit import Fit
cargo_table = Table("cargo", saveddata_meta,
Column("ID", Integer, primary_key=True),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
Column("itemID", Integer, nullable=False),
Column("amount", Integer, nullable=False),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now()),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
)
mapper(Cargo, cargo_table)
mapper(Cargo, cargo_table,
properties={
"owner": relation(Fit)
}
)

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, String, DateTime, Float
from sqlalchemy.orm import relation, mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.db.saveddata.implant import charImplants_table
@@ -39,8 +39,8 @@ characters_table = Table("characters", saveddata_meta,
Column("alphaCloneID", Integer, nullable=True),
Column("ownerID", ForeignKey("users.ID"), nullable=True),
Column("secStatus", Float, nullable=True, default=0.0),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now()))
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now))
mapper(Character, characters_table,
properties={

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, String, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.crestchar import CrestChar
@@ -29,6 +29,6 @@ crest_table = Table("crest", saveddata_meta,
Column("name", String, nullable=False, unique=True),
Column("refresh_token", String, nullable=False),
# These records aren't updated. Instead, they are dropped and created, hence we don't have a modified field
Column("created", DateTime, nullable=True, default=func.now()))
Column("created", DateTime, nullable=True, default=datetime.datetime.now))
mapper(CrestChar, crest_table)

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, String, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.damagePattern import DamagePattern
@@ -32,8 +32,8 @@ damagePatterns_table = Table("damagePatterns", saveddata_meta,
Column("kineticAmount", Integer),
Column("explosiveAmount", Integer),
Column("ownerID", ForeignKey("users.ID"), nullable=True),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(DamagePattern, damagePatterns_table)

View File

@@ -18,11 +18,12 @@
# ===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
from sqlalchemy.orm import mapper, relation
import datetime
from eos.db import saveddata_meta
from eos.saveddata.drone import Drone
from eos.saveddata.fit import Fit
drones_table = Table("drones", saveddata_meta,
Column("groupID", Integer, primary_key=True),
@@ -31,8 +32,12 @@ drones_table = Table("drones", saveddata_meta,
Column("amount", Integer, nullable=False),
Column("amountActive", Integer, nullable=False),
Column("projected", Boolean, default=False),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(Drone, drones_table)
mapper(Drone, drones_table,
properties={
"owner": relation(Fit)
}
)

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
from sqlalchemy.orm import mapper, relation
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.fighterAbility import FighterAbility
@@ -33,8 +33,8 @@ fighters_table = Table("fighters", saveddata_meta,
Column("active", Boolean, nullable=True),
Column("amount", Integer, nullable=False),
Column("projected", Boolean, default=False),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
fighter_abilities_table = Table("fightersAbilities", saveddata_meta,

View File

@@ -22,7 +22,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql import and_
from sqlalchemy.orm import relation, reconstructor, mapper, relationship
from sqlalchemy import ForeignKey, Column, Integer, String, Table, Boolean, DateTime
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.db import saveddata_session
@@ -59,8 +59,8 @@ fits_table = Table("fits", saveddata_meta,
Column("implantLocation", Integer, nullable=False, default=ImplantLocation.FIT),
Column("notes", String, nullable=True),
Column("ignoreRestrictions", Boolean, default=0),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, default=datetime.datetime.now, onupdate=datetime.datetime.now)
)
projectedFits_table = Table("projectedFits", saveddata_meta,
@@ -68,16 +68,16 @@ projectedFits_table = Table("projectedFits", saveddata_meta,
Column("victimID", ForeignKey("fits.ID"), primary_key=True),
Column("amount", Integer, nullable=False, default=1),
Column("active", Boolean, nullable=False, default=1),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
commandFits_table = Table("commandFits", saveddata_meta,
Column("boosterID", ForeignKey("fits.ID"), primary_key=True),
Column("boostedID", ForeignKey("fits.ID"), primary_key=True),
Column("active", Boolean, nullable=False, default=1),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
@@ -143,49 +143,70 @@ es_Fit._Fit__commandFits = association_proxy(
"booster_fit", # .. and return the booster fit
creator=lambda boosterID, booster_fit: CommandFit(boosterID, booster_fit)
)
# These relationships are broken out so that we can easily access it in the events stuff
# We sometimes don't want particular relationships to cause a fit modified update (eg: projecting
# a fit onto another would 'modify' both fits unless the following relationship is ignored)
projectedFitSourceRel = relationship(
ProjectedFit,
primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID,
backref='source_fit',
collection_class=attribute_mapped_collection('victimID'),
cascade='all, delete, delete-orphan')
boostedOntoRel = relationship(
CommandFit,
primaryjoin=commandFits_table.c.boosterID == fits_table.c.ID,
backref='booster_fit',
collection_class=attribute_mapped_collection('boostedID'),
cascade='all, delete, delete-orphan')
mapper(es_Fit, fits_table,
properties={
"_Fit__modules" : relation(
"_Fit__modules": relation(
Module,
collection_class=HandledModuleList,
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False), # noqa
order_by=modules_table.c.position,
cascade='all, delete, delete-orphan'),
"_Fit__projectedModules" : relation(
"_Fit__projectedModules": relation(
Module,
collection_class=HandledProjectedModList,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)), # noqa
"owner" : relation(
"owner": relation(
User,
backref="fits"),
"itemID" : fits_table.c.shipID,
"shipID" : fits_table.c.shipID,
"_Fit__boosters" : relation(
"itemID": fits_table.c.shipID,
"shipID": fits_table.c.shipID,
"_Fit__boosters": relation(
Booster,
collection_class=HandledImplantBoosterList,
cascade='all, delete, delete-orphan',
backref='owner',
single_parent=True),
"_Fit__drones" : relation(
"_Fit__drones": relation(
Drone,
collection_class=HandledDroneCargoList,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)), # noqa
"_Fit__fighters" : relation(
"_Fit__fighters": relation(
Fighter,
collection_class=HandledDroneCargoList,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == False)), # noqa
"_Fit__cargo" : relation(
"_Fit__cargo": relation(
Cargo,
collection_class=HandledDroneCargoList,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(cargo_table.c.fitID == fits_table.c.ID)),
"_Fit__projectedDrones" : relation(
"_Fit__projectedDrones": relation(
Drone,
collection_class=HandledProjectedDroneList,
cascade='all, delete, delete-orphan',
@@ -197,51 +218,41 @@ mapper(es_Fit, fits_table,
cascade='all, delete, delete-orphan',
single_parent=True,
primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == True)), # noqa
"_Fit__implants" : relation(
"_Fit__implants": relation(
Implant,
collection_class=HandledImplantBoosterList,
cascade='all, delete, delete-orphan',
backref='fit',
backref='owner',
single_parent=True,
primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID,
secondaryjoin=fitImplants_table.c.implantID == Implant.ID,
secondary=fitImplants_table),
"_Fit__character" : relation(
"_Fit__character": relation(
Character,
backref="fits"),
"_Fit__damagePattern" : relation(DamagePattern),
"_Fit__targetResists" : relation(TargetResists),
"projectedOnto" : relationship(
ProjectedFit,
primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID,
backref='source_fit',
collection_class=attribute_mapped_collection('victimID'),
cascade='all, delete, delete-orphan'),
"victimOf" : relationship(
"_Fit__damagePattern": relation(DamagePattern),
"_Fit__targetResists": relation(TargetResists),
"projectedOnto": projectedFitSourceRel,
"victimOf": relationship(
ProjectedFit,
primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID,
backref='victim_fit',
collection_class=attribute_mapped_collection('sourceID'),
cascade='all, delete, delete-orphan'),
"boostedOnto" : relationship(
CommandFit,
primaryjoin=commandFits_table.c.boosterID == fits_table.c.ID,
backref='booster_fit',
collection_class=attribute_mapped_collection('boostedID'),
cascade='all, delete, delete-orphan'),
"boostedOf" : relationship(
"boostedOnto": boostedOntoRel,
"boostedOf": relationship(
CommandFit,
primaryjoin=fits_table.c.ID == commandFits_table.c.boostedID,
backref='boosted_fit',
collection_class=attribute_mapped_collection('boosterID'),
cascade='all, delete, delete-orphan'),
}
)
)
mapper(ProjectedFit, projectedFits_table,
properties={
"_ProjectedFit__amount": projectedFits_table.c.amount,
}
)
)
mapper(CommandFit, commandFits_table)

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.implant import Implant
@@ -28,8 +28,8 @@ implants_table = Table("implants", saveddata_meta,
Column("ID", Integer, primary_key=True),
Column("itemID", Integer),
Column("active", Boolean),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
fitImplants_table = Table("fitImplants", saveddata_meta,

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, String, DateTime
from sqlalchemy.orm import relation, mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.db.saveddata.implant import implantsSetMap_table
@@ -30,8 +30,8 @@ from eos.saveddata.implantSet import ImplantSet
implant_set_table = Table("implantSets", saveddata_meta,
Column("ID", Integer, primary_key=True),
Column("name", String, nullable=False),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(ImplantSet, implant_set_table,

View File

@@ -17,9 +17,9 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime, select
from sqlalchemy.orm import relation, mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.module import Module
@@ -34,9 +34,12 @@ modules_table = Table("modules", saveddata_meta,
Column("state", Integer, CheckConstraint("state >= -1"), CheckConstraint("state <= 2")),
Column("projected", Boolean, default=False, nullable=False),
Column("position", Integer),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now()),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
mapper(Module, modules_table,
properties={"owner": relation(Fit)})

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, Float, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.override import Override
@@ -28,8 +28,8 @@ overrides_table = Table("overrides", saveddata_meta,
Column("itemID", Integer, primary_key=True, index=True),
Column("attrID", Integer, primary_key=True, index=True),
Column("value", Float, nullable=False),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(Override, overrides_table)

View File

@@ -18,6 +18,7 @@
# ===============================================================================
from sqlalchemy.sql import and_
from sqlalchemy import desc, select
from eos.db import saveddata_session, sd_lock
from eos.db.saveddata.fit import projectedFits_table
@@ -242,6 +243,22 @@ def getFitsWithShip(shipID, ownerID=None, where=None, eager=None):
return fits
def getRecentFits(ownerID=None, where=None, eager=None):
eager = processEager(eager)
with sd_lock:
q = select((
Fit.ID,
Fit.shipID,
Fit.name,
Fit.modified,
Fit.created,
Fit.timestamp
)).order_by(desc(Fit.modified), desc(Fit.timestamp)).limit(50)
fits = eos.db.saveddata_session.execute(q).fetchall()
return fits
def getFitsWithModules(typeIDs, eager=None):
"""
Get all the fits that have typeIDs fitted to them

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.character import Skill
@@ -29,8 +29,8 @@ skills_table = Table("characterSkills", saveddata_meta,
Column("characterID", ForeignKey("characters.ID"), primary_key=True, index=True),
Column("itemID", Integer, primary_key=True),
Column("_Skill__level", Integer, nullable=True),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(Skill, skills_table)

View File

@@ -19,7 +19,7 @@
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, String, DateTime
from sqlalchemy.orm import mapper
import sqlalchemy.sql.functions as func
import datetime
from eos.db import saveddata_meta
from eos.saveddata.targetResists import TargetResists
@@ -32,8 +32,8 @@ targetResists_table = Table("targetResists", saveddata_meta,
Column("kineticAmount", Float),
Column("explosiveAmount", Float),
Column("ownerID", ForeignKey("users.ID"), nullable=True),
Column("created", DateTime, nullable=True, default=func.now()),
Column("modified", DateTime, nullable=True, onupdate=func.now())
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(TargetResists, targetResists_table)

86
eos/events.py Normal file
View File

@@ -0,0 +1,86 @@
# Decided to put this in it's own file so that we can easily choose not to import it (thanks to mac-deprecated builds =/)
import datetime
from sqlalchemy.event import listen
from sqlalchemy.orm.collections import InstrumentedList
from eos.db.saveddata.fit import projectedFitSourceRel, boostedOntoRel
from eos.saveddata.fit import Fit
from eos.saveddata.module import Module
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.cargo import Cargo
from eos.saveddata.implant import Implant
from eos.saveddata.booster import Booster
ignored_rels = [
projectedFitSourceRel,
boostedOntoRel
]
def update_fit_modified(target, value, oldvalue, initiator):
if not target.owner:
return
if value != oldvalue:
# some things (like Implants) have a backref to the fit, which actually produces a list.
# In this situation, simply take the 0 index to get to the fit.
# There may be cases in the future in which there are multiple fits, so this should be
# looked at more indepth later
if isinstance(target.owner, InstrumentedList):
parent = target.owner[0]
else:
parent = target.owner
# ensure this is a fit we're dealing with
if isinstance(parent, Fit):
parent.modified = datetime.datetime.now()
def apply_col_listeners(target, context):
# We only want to set these events when the module is first loaded (otherwise events will fire during the initial
# population of data). This runs through all columns and sets up "set" events on each column. We do it with each
# column because the alternative would be to do a before/after_update for the Mapper itself, however we're only
# allowed to change the local attributes during those events as that's inter-flush.
# See http://docs.sqlalchemy.org/en/rel_1_0/orm/session_events.html#mapper-level-events
# @todo replace with `inspect(Module).column_attrs` when mac binaries are updated
manager = getattr(target.__class__, "_sa_class_manager", None)
if manager:
for col in manager.mapper.column_attrs:
listen(col, 'set', update_fit_modified)
def rel_listener(target, value, initiator):
if not target or (isinstance(value, Module) and value.isEmpty):
return
print "{} has had a relationship change :D".format(target)
target.modified = datetime.datetime.now()
def apply_rel_listeners(target, context):
# We only want to see these events when the fit is first loaded (otherwise events will fire during the initial
# population of data). This sets listeners for all the relationships on fits. This allows us to update the fit's
# modified date whenever something is added/removed from fit
# See http://docs.sqlalchemy.org/en/rel_1_0/orm/events.html#sqlalchemy.orm.events.InstanceEvents.load
# todo: when we can, move over to `inspect(es_Fit).relationships` (when mac binaries are updated)
manager = getattr(target.__class__, "_sa_class_manager", None)
if manager:
for rel in manager.mapper.relationships:
if rel in ignored_rels:
continue
listen(rel, 'append', rel_listener)
listen(rel, 'remove', rel_listener)
listen(Fit, 'load', apply_rel_listeners)
listen(Module, 'load', apply_col_listeners)
listen(Drone, 'load', apply_col_listeners)
listen(Fighter, 'load', apply_col_listeners)
listen(Cargo, 'load', apply_col_listeners)
listen(Implant, 'load', apply_col_listeners)
listen(Booster, 'load', apply_col_listeners)

View File

@@ -21,6 +21,7 @@ import time
from copy import deepcopy
from itertools import chain
from math import sqrt, log, asinh
import datetime
from sqlalchemy.orm import validates, reconstructor
@@ -77,6 +78,8 @@ class Fit(object):
self.projected = False
self.name = name
self.timestamp = time.time()
self.created = None
self.modified = None
self.modeID = None
self.build()
@@ -180,6 +183,14 @@ class Fit(object):
self.__mode = mode
self.modeID = mode.item.ID if mode is not None else None
@property
def modifiedCoalesce(self):
"""
This is a property that should get whichever date is available for the fit. @todo: migrate old timestamp data
and ensure created / modified are set in database to get rid of this
"""
return self.modified or self.created or datetime.datetime.fromtimestamp(self.timestamp)
@property
def character(self):
return self.__character if self.__character is not None else Character.getAll0()

View File

@@ -20,6 +20,7 @@
import sys
import os.path
from logbook import Logger
import datetime
import sqlalchemy
# noinspection PyPackageRequirements
@@ -940,7 +941,16 @@ class MainFrame(wx.Frame):
wx.PostEvent(self, FitSelected(fitID=fit.ID))
wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True))
else:
wx.PostEvent(self.shipBrowser, ImportSelected(fits=fits, back=True))
fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name))
results = []
for fit in fits:
results.append((
fit.ID,
fit.name,
fit.modifiedCoalesce,
fit.ship.item
))
wx.PostEvent(self.shipBrowser, ImportSelected(fits=results, back=True))
def closeProgressDialog(self):
# Windows apparently handles ProgressDialogs differently. We can

View File

@@ -337,15 +337,21 @@ class NavigationPanel(SFItem.SFBrowserItem):
self.newBmpH = BitmapLoader.getBitmap("fit_add_small", "gui")
self.resetBmpH = BitmapLoader.getBitmap("freset_small", "gui")
self.switchBmpH = BitmapLoader.getBitmap("fit_switch_view_mode_small", "gui")
self.recentBmpH = BitmapLoader.getBitmap("frecent_small", "gui")
switchImg = BitmapLoader.getImage("fit_switch_view_mode_small", "gui")
switchImg = switchImg.AdjustChannels(1, 1, 1, 0.4)
self.switchBmpD = wx.BitmapFromImage(switchImg)
recentImg = BitmapLoader.getImage("frecent_small", "gui")
recentImg = recentImg.AdjustChannels(1, 1, 1, 0.4)
self.recentBmpD = wx.BitmapFromImage(recentImg)
self.resetBmp = self.AdjustChannels(self.resetBmpH)
self.rewBmp = self.AdjustChannels(self.rewBmpH)
self.searchBmp = self.AdjustChannels(self.searchBmpH)
self.switchBmp = self.AdjustChannels(self.switchBmpH)
self.recentBmp = self.AdjustChannels(self.recentBmpH)
self.newBmp = self.AdjustChannels(self.newBmpH)
self.toolbar.AddButton(self.resetBmp, "Ship groups", clickCallback=self.OnHistoryReset,
@@ -356,6 +362,9 @@ class NavigationPanel(SFItem.SFBrowserItem):
self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, "Hide empty ship groups",
clickCallback=self.ToggleEmptyGroupsView, hoverBitmap=self.switchBmpH,
show=False)
self.btnRecent = self.toolbar.AddButton(self.recentBmpD, "Recent Fits",
clickCallback=self.ToggleRecentShips, hoverBitmap=self.recentBmpH,
show=True)
modifier = "CTRL" if 'wxMac' not in wx.PlatformInfo else "CMD"
self.toolbar.AddButton(self.searchBmp, "Search fittings ({}+F)".format(modifier), clickCallback=self.ToggleSearchBox,
@@ -415,6 +424,27 @@ class NavigationPanel(SFItem.SFBrowserItem):
def OnResize(self, event):
self.Refresh()
def ToggleRecentShips(self, bool = None, emitEvent = True):
# this is so janky. Need to revaluate pretty much entire ship browser. >.<
toggle = bool if bool is not None else not self.shipBrowser.recentFits
if not toggle:
self.shipBrowser.recentFits = False
self.btnRecent.label = "Recent Fits"
self.btnRecent.normalBmp = self.recentBmpD
if emitEvent:
wx.PostEvent(self.shipBrowser, Stage1Selected())
else:
self.shipBrowser.recentFits = True
self.btnRecent.label = "Hide Recent Fits"
self.btnRecent.normalBmp = self.recentBmp
if emitEvent:
sFit = Fit.getInstance()
fits = sFit.getRecentFits()
wx.PostEvent(self.shipBrowser, ImportSelected(fits=fits, back=True, recent=True))
def ToggleEmptyGroupsView(self):
if self.shipBrowser.filterShipsWithNoFits:
self.shipBrowser.filterShipsWithNoFits = False
@@ -453,11 +483,13 @@ class NavigationPanel(SFItem.SFBrowserItem):
wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID))
def OnHistoryReset(self):
self.ToggleRecentShips(False, False)
if self.shipBrowser.browseHist:
self.shipBrowser.browseHist = []
self.gotoStage(1, 0)
def OnHistoryBack(self):
self.ToggleRecentShips(False, False)
if len(self.shipBrowser.browseHist) > 0:
stage, data = self.shipBrowser.browseHist.pop()
self.gotoStage(stage, data)
@@ -537,6 +569,7 @@ class NavigationPanel(SFItem.SFBrowserItem):
self.bkBitmap.mFactor = mFactor
def gotoStage(self, stage, data=None):
self.shipBrowser.recentFits = False
if stage == 1:
wx.PostEvent(self.Parent, Stage1Selected())
elif stage == 2:
@@ -572,6 +605,7 @@ class ShipBrowser(wx.Panel):
self._stage3ShipName = ""
self.fitIDMustEditName = -1
self.filterShipsWithNoFits = False
self.recentFits = False
self.racesFilter = {}
@@ -628,7 +662,8 @@ class ShipBrowser(wx.Panel):
def RefreshList(self, event):
stage = self.GetActiveStage()
if stage == 3 or stage == 4:
if stage in (3, 4, 5):
self.lpane.RefreshList(True)
event.Skip()
@@ -671,6 +706,7 @@ class ShipBrowser(wx.Panel):
return self.racesFilter[race]
def stage1(self, event):
self.navpanel.ToggleRecentShips(False, False)
self._lastStage = self._activeStage
self._activeStage = 1
self.lastdata = 0
@@ -726,6 +762,7 @@ class ShipBrowser(wx.Panel):
def stage2Callback(self, data):
if self.GetActiveStage() != 2:
return
self.navpanel.ToggleRecentShips(False, False)
categoryID = self._stage2Data
ships = list(data[1])
@@ -811,7 +848,7 @@ class ShipBrowser(wx.Panel):
return info[1]
def stage3(self, event):
self.navpanel.ToggleRecentShips(False, False)
self.lpane.ShowLoading(False)
# If back is False, do not append to history. This could be us calling
@@ -920,6 +957,10 @@ class ShipBrowser(wx.Panel):
self.Layout()
def importStage(self, event):
"""
The import stage handles both displaying fits after importing as well as displaying recent fits. todo: need to
reconcile these two better into a more uniform function, right now hacked together to get working
"""
self.lpane.ShowLoading(False)
self.navpanel.ShowNewFitButton(False)
@@ -933,29 +974,26 @@ class ShipBrowser(wx.Panel):
fits = event.fits
# sort by ship name, then fit name
fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name))
self.lastdata = fits
self.lpane.Freeze()
self.lpane.RemoveAllChildren()
if fits:
for fit in fits:
shipTrait = fit.ship.item.traits.traitText if (fit.ship.item.traits is not None) else ""
# empty string if no traits
shipItem = fit[3]
shipTrait = shipItem.traits.traitText if (shipItem.traits is not None) else ""
self.lpane.AddWidget(FitItem(
self.lpane,
fit.ID,
fit[0],
(
fit.ship.item.name,
shipItem.name,
shipTrait,
fit.name,
fit.booster,
fit.timestamp,
fit[1],
False,
fit[2]
),
fit.ship.item.ID,
shipItem.ID,
))
self.lpane.RefreshList(doFocus=False)
self.lpane.Thaw()
@@ -1473,6 +1511,7 @@ class FitItem(SFItem.SFBrowserItem):
self.shipFittingInfo = shipFittingInfo
self.shipName, self.shipTrait, self.fitName, self.fitBooster, self.timestamp = shipFittingInfo
self.shipTrait = re.sub("<.*?>", " ", self.shipTrait)
# see GH issue #62
@@ -1553,10 +1592,13 @@ class FitItem(SFItem.SFBrowserItem):
# self.animCount = 0
# =====================================================================
"""
# Remove this bit as the time stuff is non-functional (works... but not exactly sure what it's meant to do)
self.selTimerID = wx.NewId()
self.selTimer = wx.Timer(self, self.selTimerID)
self.selTimer.Start(100)
"""
self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu)
self.Bind(wx.EVT_MIDDLE_UP, self.OpenNewTab)
@@ -1645,6 +1687,7 @@ class FitItem(SFItem.SFBrowserItem):
def OnTimer(self, event):
# @todo: figure out what exactly this is supposed to accomplish
if self.selTimerID == event.GetId():
ctimestamp = time.time()
interval = 5
@@ -1726,7 +1769,6 @@ class FitItem(SFItem.SFBrowserItem):
self.fitName = fitName
sFit.renameFit(self.fitID, self.fitName)
wx.PostEvent(self.mainFrame, FitRenamed(fitID=self.fitID))
self.Refresh()
else:
self.tcFitName.SetValue(self.fitName)
@@ -1893,8 +1935,8 @@ class FitItem(SFItem.SFBrowserItem):
mdc.SetFont(self.fontNormal)
fitDate = time.localtime(self.timestamp)
fitLocalDate = "%d/%02d/%02d %02d:%02d" % (fitDate[0], fitDate[1], fitDate[2], fitDate[3], fitDate[4])
fitDate = self.timestamp.strftime("%m/%d/%Y %H:%M")
fitLocalDate = fitDate #"%d/%02d/%02d %02d:%02d" % (fitDate[0], fitDate[1], fitDate[2], fitDate[3], fitDate[4])
pfdate = drawUtils.GetPartialText(mdc, fitLocalDate,
self.toolbarx - self.textStartx - self.padding * 2 - self.thoverw)
@@ -1947,6 +1989,15 @@ class FitItem(SFItem.SFBrowserItem):
state = SFItem.SB_ITEM_NORMAL
return state
def Refresh(self):
activeFit = self.mainFrame.getActiveFit()
if activeFit == self.fitID:
sFit = Fit.getInstance()
fit = sFit.getFit(activeFit)
self.timestamp = fit.modifiedCoalesce
SFItem.SFBrowserItem.Refresh(self)
def RenderBackground(self):
rect = self.GetRect()

BIN
imgs/gui/frecent_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

View File

@@ -337,6 +337,7 @@ if __name__ == "__main__":
else:
saVersion = sqlalchemy.__version__
saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion)
config.saVersion = (int(saMatch.group(1)), int(saMatch.group(2)), int(saMatch.group(4)))
if saMatch:
saMajor = int(saMatch.group(1))
saMinor = int(saMatch.group(2))
@@ -362,6 +363,12 @@ if __name__ == "__main__":
raise PreCheckException("Cannot import requests. You can download requests from https://pypi.python.org/pypi/requests.")
import eos.db
if config.saVersion[0] > 0 or config.saVersion[1] >= 7:
# <0.7 doesn't have support for events ;_; (mac-deprecated)
config.sa_events = True
import eos.events
# noinspection PyUnresolvedReferences
import service.prefetch # noqa: F401

View File

@@ -20,6 +20,7 @@
import copy
from logbook import Logger
from time import time
import datetime
import eos.db
from eos.saveddata.booster import Booster as es_Booster
@@ -93,10 +94,24 @@ class Fit(object):
fits = eos.db.getFitsWithShip(shipID)
names = []
for fit in fits:
names.append((fit.ID, fit.name, fit.booster, fit.timestamp))
names.append((fit.ID, fit.name, fit.booster, fit.modified or fit.created or datetime.datetime.fromtimestamp(fit.timestamp)))
return names
@staticmethod
def getRecentFits():
""" Fetches recently modified fits, used with shipBrowser """
pyfalog.debug("Fetching recent fits")
fits = eos.db.getRecentFits()
returnInfo = []
for fit in fits:
item = eos.db.getItem(fit[1])
returnInfo.append((fit[0], fit[2], fit[3] or fit[4] or datetime.datetime.fromtimestamp(fit[5]), item))
# ID name timestamps
return returnInfo
@staticmethod
def getFitsWithModules(typeIDs):
""" Lists fits flagged as booster """
@@ -258,10 +273,15 @@ class Fit(object):
pyfalog.debug("Searching for fit: {0}", 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))
fit.ID,
fit.name,
fit.ship.item.ID,
fit.ship.item.name,
fit.booster,
fit.modifiedCoalesce))
return fits
def addImplant(self, fitID, itemID, recalc=True):