From dfc0a896c4086e79b51b240b062fb635c68c4a84 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Tue, 12 Feb 2019 12:10:17 +0300 Subject: [PATCH 01/16] Do not commit price right away when creating the object --- eos/gamedata.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eos/gamedata.py b/eos/gamedata.py index f8345ac87..31e6f262e 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -459,7 +459,10 @@ class Item(EqBase): pyfalog.debug("Creating a price for {}".format(self.ID)) self.__price = types_Price(self.ID) eos.db.add(self.__price) - eos.db.commit() + # Commented out by DarkPhoenix: it caused issues when opening stats for item with many + # variations, as each commit takes ~50 ms, for items with 30 variations time to open stats + # window could reach 2 seconds. Hopefully just adding it is sufficient. + # eos.db.commit() else: self.__price = db_price From 8baac1e2079d678047f37f84f65cfb91de66e964 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Tue, 12 Feb 2019 12:35:53 +0300 Subject: [PATCH 02/16] Introduce secondary sort parameter for compare tab --- gui/builtinItemStatsViews/itemCompare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py index 1561ed58b..693f1b252 100644 --- a/gui/builtinItemStatsViews/itemCompare.py +++ b/gui/builtinItemStatsViews/itemCompare.py @@ -28,7 +28,7 @@ class ItemCompare(wx.Panel): self.sortReverse = False self.item = item self.items = sorted(items, - key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0) + key=lambda x: (x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0, x.name)) self.attrs = {} # get a dict of attrName: attrInfo of all unique attributes across all items From fb5d62304cc90ae7de6a30fba52cfe5b359cdaae Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Tue, 12 Feb 2019 12:50:02 +0300 Subject: [PATCH 03/16] Allow sorting by price in variations tab --- gui/builtinItemStatsViews/itemCompare.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py index 693f1b252..c1c16fe06 100644 --- a/gui/builtinItemStatsViews/itemCompare.py +++ b/gui/builtinItemStatsViews/itemCompare.py @@ -8,6 +8,10 @@ from service.attribute import Attribute from gui.utils.numberFormatter import formatAmount +def defaultSort(item): + return (item.attributes['metaLevel'].value if 'metaLevel' in item.attributes else 0, item.name) + + class ItemCompare(wx.Panel): def __init__(self, parent, stuff, item, items, context=None): # Start dealing with Price stuff to get that thread going @@ -27,8 +31,7 @@ class ItemCompare(wx.Panel): self.currentSort = None self.sortReverse = False self.item = item - self.items = sorted(items, - key=lambda x: (x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0, x.name)) + self.items = sorted(items, key=defaultSort) self.attrs = {} # get a dict of attrName: attrInfo of all unique attributes across all items @@ -126,10 +129,15 @@ class ItemCompare(wx.Panel): # starts at 0 while the list has the item name as column 0. attr = str(list(self.attrs.keys())[sort - 1]) func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0 + # Clicked on a column that's not part of our array (price most likely) except IndexError: - # Clicked on a column that's not part of our array (price most likely) - self.sortReverse = False - func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else 0.0 + # Price + if sort == len(self.attrs) + 1: + func = lambda i: i.price.price if i.price.price != 0 else float("Inf") + # Something else + else: + self.sortReverse = False + func = defaultSort self.items = sorted(self.items, key=func, reverse=self.sortReverse) From b0317ea56086516fb31e0fa61b73739ed92ef55c Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Tue, 12 Feb 2019 19:07:59 +0300 Subject: [PATCH 04/16] Fix mistype --- eos/gamedata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eos/gamedata.py b/eos/gamedata.py index 31e6f262e..76ff65a7a 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -471,12 +471,12 @@ class Item(EqBase): @property def isAbyssal(self): if Item.ABYSSAL_TYPES is None: - Item.getAbyssalYypes() + Item.getAbyssalTypes() return self.ID in Item.ABYSSAL_TYPES @classmethod - def getAbyssalYypes(cls): + def getAbyssalTypes(cls): cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes() @property From 591dcffa43d33e634a184c16fd8acaa7f09978e7 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 00:54:56 +0300 Subject: [PATCH 05/16] Do not try to fetch price for items which contain no market group according to CCP data --- eos/gamedata.py | 26 ++++++++++++++++------- gui/builtinItemStatsViews/itemCompare.py | 4 ++-- gui/builtinStatsViews/priceViewFull.py | 14 ++++++------ gui/builtinStatsViews/priceViewMinimal.py | 14 ++++++------ gui/builtinViewColumns/price.py | 12 ++++++++--- service/marketSources/evemarketer.py | 4 ++-- service/price.py | 8 ++++--- 7 files changed, 50 insertions(+), 32 deletions(-) diff --git a/eos/gamedata.py b/eos/gamedata.py index 76ff65a7a..d32edf6e9 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -239,7 +239,7 @@ class Item(EqBase): self.__offensive = None self.__assistive = None self.__overrides = None - self.__price = None + self.__priceObj = None @property def attributes(self): @@ -446,27 +446,37 @@ class Item(EqBase): @property def price(self): + priceObj = self.priceObj + if not priceObj: + return 0 + else: + return priceObj.price + + @property + def priceObj(self): + if not self.marketGroupID: + return None # todo: use `from sqlalchemy import inspect` instead (mac-deprecated doesn't have inspect(), was imp[lemented in 0.8) - if self.__price is not None and getattr(self.__price, '_sa_instance_state', None) and self.__price._sa_instance_state.deleted: + if self.__priceObj is not None and getattr(self.__priceObj, '_sa_instance_state', None) and self.__priceObj._sa_instance_state.deleted: pyfalog.debug("Price data for {} was deleted (probably from a cache reset), resetting object".format(self.ID)) - self.__price = None + self.__priceObj = None - if self.__price is None: + if self.__priceObj is None: db_price = eos.db.getPrice(self.ID) # do not yet have a price in the database for this item, create one if db_price is None: pyfalog.debug("Creating a price for {}".format(self.ID)) - self.__price = types_Price(self.ID) - eos.db.add(self.__price) + self.__priceObj = types_Price(self.ID) + eos.db.add(self.__priceObj) # Commented out by DarkPhoenix: it caused issues when opening stats for item with many # variations, as each commit takes ~50 ms, for items with 30 variations time to open stats # window could reach 2 seconds. Hopefully just adding it is sufficient. # eos.db.commit() else: - self.__price = db_price + self.__priceObj = db_price - return self.__price + return self.__priceObj @property def isAbyssal(self): diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py index c1c16fe06..3981265f1 100644 --- a/gui/builtinItemStatsViews/itemCompare.py +++ b/gui/builtinItemStatsViews/itemCompare.py @@ -133,7 +133,7 @@ class ItemCompare(wx.Panel): except IndexError: # Price if sort == len(self.attrs) + 1: - func = lambda i: i.price.price if i.price.price != 0 else float("Inf") + func = lambda i: i.price if i.price != 0 else float("Inf") # Something else else: self.sortReverse = False @@ -168,7 +168,7 @@ class ItemCompare(wx.Panel): self.paramList.SetItem(i, x + 1, valueUnit) # Add prices - self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True)) + self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price, 3, 3, 9, currency=True) if item.price else "") self.paramList.RefreshRows() self.Layout() diff --git a/gui/builtinStatsViews/priceViewFull.py b/gui/builtinStatsViews/priceViewFull.py index 767ad958f..c2cb05f7d 100644 --- a/gui/builtinStatsViews/priceViewFull.py +++ b/gui/builtinStatsViews/priceViewFull.py @@ -100,32 +100,32 @@ class PriceViewFull(StatsView): implant_price = 0 if fit: - ship_price = fit.ship.item.price.price + ship_price = fit.ship.item.price if fit.modules: for module in fit.modules: if not module.isEmpty: - module_price += module.item.price.price + module_price += module.item.price if fit.drones: for drone in fit.drones: - drone_price += drone.item.price.price * drone.amount + drone_price += drone.item.price * drone.amount if fit.fighters: for fighter in fit.fighters: - fighter_price += fighter.item.price.price * fighter.amountActive + fighter_price += fighter.item.price * fighter.amountActive if fit.cargo: for cargo in fit.cargo: - cargo_price += cargo.item.price.price * cargo.amount + cargo_price += cargo.item.price * cargo.amount if fit.boosters: for booster in fit.boosters: - booster_price += booster.item.price.price + booster_price += booster.item.price if fit.implants: for implant in fit.implants: - implant_price += implant.item.price.price + implant_price += implant.item.price total_price = 0 diff --git a/gui/builtinStatsViews/priceViewMinimal.py b/gui/builtinStatsViews/priceViewMinimal.py index 8d6a5ce83..fb9877690 100644 --- a/gui/builtinStatsViews/priceViewMinimal.py +++ b/gui/builtinStatsViews/priceViewMinimal.py @@ -94,32 +94,32 @@ class PriceViewMinimal(StatsView): implant_price = 0 if fit: - ship_price = fit.ship.item.price.price + ship_price = fit.ship.item.price if fit.modules: for module in fit.modules: if not module.isEmpty: - module_price += module.item.price.price + module_price += module.item.price if fit.drones: for drone in fit.drones: - drone_price += drone.item.price.price * drone.amount + drone_price += drone.item.price * drone.amount if fit.fighters: for fighter in fit.fighters: - fighter_price += fighter.item.price.price * fighter.amountActive + fighter_price += fighter.item.price * fighter.amountActive if fit.cargo: for cargo in fit.cargo: - cargo_price += cargo.item.price.price * cargo.amount + cargo_price += cargo.item.price * cargo.amount if fit.boosters: for booster in fit.boosters: - booster_price += booster.item.price.price + booster_price += booster.item.price if fit.implants: for implant in fit.implants: - implant_price += implant.item.price.price + implant_price += implant.item.price fitting_price = module_price diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index a68614b83..65ebbfb36 100644 --- a/gui/builtinViewColumns/price.py +++ b/gui/builtinViewColumns/price.py @@ -45,13 +45,19 @@ class Price(ViewColumn): if stuff.isEmpty: return "" - price = stuff.item.price + priceObj = stuff.item.priceObj - if not price or not price.isValid: + if not priceObj: + return "" + + if not priceObj.isValid: return False # Fetch actual price as float to not modify its value on Price object - price = price.price + price = priceObj.price + + if price == 0: + return "" if isinstance(stuff, Drone) or isinstance(stuff, Cargo): price *= stuff.amount diff --git a/service/marketSources/evemarketer.py b/service/marketSources/evemarketer.py index a2728ddfb..2418d10c5 100644 --- a/service/marketSources/evemarketer.py +++ b/service/marketSources/evemarketer.py @@ -28,7 +28,7 @@ from service.price import Price, VALIDITY pyfalog = Logger(__name__) -class EveCentral(object): +class EveMarketer(object): name = "evemarketer" @@ -67,4 +67,4 @@ class EveCentral(object): del priceMap[typeID] -Price.register(EveCentral) +Price.register(EveMarketer) diff --git a/service/price.py b/service/price.py index 204db764d..7adb5120a 100644 --- a/service/price.py +++ b/service/price.py @@ -167,15 +167,16 @@ class Price(object): sMkt = Market.getInstance() item = sMkt.getItem(objitem) - return item.price.price + return item.price def getPrices(self, objitems, callback, waitforthread=False): """Get prices for multiple typeIDs""" requests = [] sMkt = Market.getInstance() for objitem in objitems: - item = sMkt.getItem(objitem) - requests.append(item.price) + priceobj = sMkt.getItem(objitem).priceObj + if priceobj: + requests.append(priceobj) def cb(): try: @@ -197,6 +198,7 @@ class Price(object): class PriceWorkerThread(threading.Thread): + def __init__(self): threading.Thread.__init__(self) self.name = "PriceWorker" From e0c389a643e21ca9e74a333149afdb42a055c1e3 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 13:07:54 +0300 Subject: [PATCH 06/16] Do not show (!) for fetches which will always fail (e.g. abyssal mods). Also restore commit as it was a threat synchornization mechanism --- eos/db/migrations/upgrade30.py | 17 +++++++++++++++ eos/db/saveddata/price.py | 5 ++++- eos/gamedata.py | 16 +------------- eos/saveddata/price.py | 25 +++++++++++++++------- gui/builtinItemStatsViews/itemCompare.py | 4 ++-- gui/builtinStatsViews/priceViewFull.py | 14 ++++++------ gui/builtinStatsViews/priceViewMinimal.py | 14 ++++++------ gui/builtinViewColumns/price.py | 16 +++++++------- service/price.py | 26 +++++++++++++---------- 9 files changed, 78 insertions(+), 59 deletions(-) create mode 100644 eos/db/migrations/upgrade30.py diff --git a/eos/db/migrations/upgrade30.py b/eos/db/migrations/upgrade30.py new file mode 100644 index 000000000..7954f2d37 --- /dev/null +++ b/eos/db/migrations/upgrade30.py @@ -0,0 +1,17 @@ +""" +Migration 30 + +- changes to prices table +""" + + +import sqlalchemy + + +def upgrade(saveddata_engine): + try: + saveddata_engine.execute("SELECT status FROM prices LIMIT 1") + except sqlalchemy.exc.DatabaseError: + # Just drop table, table will be re-created by sqlalchemy and + # data will be re-fetched + saveddata_engine.execute("DROP TABLE prices;") diff --git a/eos/db/saveddata/price.py b/eos/db/saveddata/price.py index 8be7f6519..8abd07132 100644 --- a/eos/db/saveddata/price.py +++ b/eos/db/saveddata/price.py @@ -17,17 +17,20 @@ # along with eos. If not, see . # =============================================================================== + from sqlalchemy import Table, Column, Float, Integer from sqlalchemy.orm import mapper from eos.db import saveddata_meta from eos.saveddata.price import Price + prices_table = Table("prices", saveddata_meta, Column("typeID", Integer, primary_key=True), Column("price", Float, default=0.0), Column("time", Integer, nullable=False), - Column("failed", Integer)) + Column("status", Integer, nullable=False)) + mapper(Price, prices_table, properties={ "_Price__price": prices_table.c.price, diff --git a/eos/gamedata.py b/eos/gamedata.py index d32edf6e9..0243eec0f 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -446,17 +446,6 @@ class Item(EqBase): @property def price(self): - priceObj = self.priceObj - if not priceObj: - return 0 - else: - return priceObj.price - - @property - def priceObj(self): - if not self.marketGroupID: - return None - # todo: use `from sqlalchemy import inspect` instead (mac-deprecated doesn't have inspect(), was imp[lemented in 0.8) if self.__priceObj is not None and getattr(self.__priceObj, '_sa_instance_state', None) and self.__priceObj._sa_instance_state.deleted: pyfalog.debug("Price data for {} was deleted (probably from a cache reset), resetting object".format(self.ID)) @@ -469,10 +458,7 @@ class Item(EqBase): pyfalog.debug("Creating a price for {}".format(self.ID)) self.__priceObj = types_Price(self.ID) eos.db.add(self.__priceObj) - # Commented out by DarkPhoenix: it caused issues when opening stats for item with many - # variations, as each commit takes ~50 ms, for items with 30 variations time to open stats - # window could reach 2 seconds. Hopefully just adding it is sufficient. - # eos.db.commit() + eos.db.commit() else: self.__priceObj = db_price diff --git a/eos/saveddata/price.py b/eos/saveddata/price.py index 6618119d0..ee575b70c 100644 --- a/eos/saveddata/price.py +++ b/eos/saveddata/price.py @@ -18,24 +18,30 @@ # along with eos. If not, see . # =============================================================================== -import time -from sqlalchemy.orm import reconstructor +import time +from enum import IntEnum, unique + from logbook import Logger + pyfalog = Logger(__name__) +@unique +class PriceStatus(IntEnum): + notFetched = 0 + success = 1 + fail = 2 + notSupported = 3 + + class Price(object): def __init__(self, typeID): self.typeID = typeID self.time = 0 self.__price = 0 - self.failed = None - - @reconstructor - def init(self): - self.__item = None + self.status = PriceStatus.notFetched @property def isValid(self): @@ -43,7 +49,10 @@ class Price(object): @property def price(self): - return self.__price or 0.0 + if self.status in (PriceStatus.fail, PriceStatus.notSupported): + return 0 + else: + return self.__price or 0 @price.setter def price(self, price): diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py index 3981265f1..97a4b953d 100644 --- a/gui/builtinItemStatsViews/itemCompare.py +++ b/gui/builtinItemStatsViews/itemCompare.py @@ -133,7 +133,7 @@ class ItemCompare(wx.Panel): except IndexError: # Price if sort == len(self.attrs) + 1: - func = lambda i: i.price if i.price != 0 else float("Inf") + func = lambda i: i.price.price if i.price.price != 0 else float("Inf") # Something else else: self.sortReverse = False @@ -168,7 +168,7 @@ class ItemCompare(wx.Panel): self.paramList.SetItem(i, x + 1, valueUnit) # Add prices - self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price, 3, 3, 9, currency=True) if item.price else "") + self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "") self.paramList.RefreshRows() self.Layout() diff --git a/gui/builtinStatsViews/priceViewFull.py b/gui/builtinStatsViews/priceViewFull.py index c2cb05f7d..767ad958f 100644 --- a/gui/builtinStatsViews/priceViewFull.py +++ b/gui/builtinStatsViews/priceViewFull.py @@ -100,32 +100,32 @@ class PriceViewFull(StatsView): implant_price = 0 if fit: - ship_price = fit.ship.item.price + ship_price = fit.ship.item.price.price if fit.modules: for module in fit.modules: if not module.isEmpty: - module_price += module.item.price + module_price += module.item.price.price if fit.drones: for drone in fit.drones: - drone_price += drone.item.price * drone.amount + drone_price += drone.item.price.price * drone.amount if fit.fighters: for fighter in fit.fighters: - fighter_price += fighter.item.price * fighter.amountActive + fighter_price += fighter.item.price.price * fighter.amountActive if fit.cargo: for cargo in fit.cargo: - cargo_price += cargo.item.price * cargo.amount + cargo_price += cargo.item.price.price * cargo.amount if fit.boosters: for booster in fit.boosters: - booster_price += booster.item.price + booster_price += booster.item.price.price if fit.implants: for implant in fit.implants: - implant_price += implant.item.price + implant_price += implant.item.price.price total_price = 0 diff --git a/gui/builtinStatsViews/priceViewMinimal.py b/gui/builtinStatsViews/priceViewMinimal.py index fb9877690..8d6a5ce83 100644 --- a/gui/builtinStatsViews/priceViewMinimal.py +++ b/gui/builtinStatsViews/priceViewMinimal.py @@ -94,32 +94,32 @@ class PriceViewMinimal(StatsView): implant_price = 0 if fit: - ship_price = fit.ship.item.price + ship_price = fit.ship.item.price.price if fit.modules: for module in fit.modules: if not module.isEmpty: - module_price += module.item.price + module_price += module.item.price.price if fit.drones: for drone in fit.drones: - drone_price += drone.item.price * drone.amount + drone_price += drone.item.price.price * drone.amount if fit.fighters: for fighter in fit.fighters: - fighter_price += fighter.item.price * fighter.amountActive + fighter_price += fighter.item.price.price * fighter.amountActive if fit.cargo: for cargo in fit.cargo: - cargo_price += cargo.item.price * cargo.amount + cargo_price += cargo.item.price.price * cargo.amount if fit.boosters: for booster in fit.boosters: - booster_price += booster.item.price + booster_price += booster.item.price.price if fit.implants: for implant in fit.implants: - implant_price += implant.item.price + implant_price += implant.item.price.price fitting_price = module_price diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index 65ebbfb36..56901c172 100644 --- a/gui/builtinViewColumns/price.py +++ b/gui/builtinViewColumns/price.py @@ -22,6 +22,7 @@ import wx from eos.saveddata.cargo import Cargo from eos.saveddata.drone import Drone +from eos.saveddata.price import PriceStatus from service.price import Price as ServicePrice from gui.viewColumn import ViewColumn from gui.bitmap_loader import BitmapLoader @@ -45,10 +46,7 @@ class Price(ViewColumn): if stuff.isEmpty: return "" - priceObj = stuff.item.priceObj - - if not priceObj: - return "" + priceObj = stuff.item.price if not priceObj.isValid: return False @@ -69,10 +67,12 @@ class Price(ViewColumn): def callback(item): price = item[0] - text = formatAmount(price.price, 3, 3, 9, currency=True) if price.price else "" - if price.failed: - text += " (!)" - colItem.SetText(text) + textItems = [] + if price.price: + textItems.append(formatAmount(price.price, 3, 3, 9, currency=True)) + if price.status == PriceStatus.fail: + textItems.append("(!)") + colItem.SetText(" ".join(textItems)) display.SetItem(colItem) diff --git a/service/price.py b/service/price.py index 7adb5120a..3028b74ad 100644 --- a/service/price.py +++ b/service/price.py @@ -26,6 +26,7 @@ import wx from logbook import Logger from eos import db +from eos.saveddata.price import PriceStatus from service.fit import Fit from service.market import Market from service.network import TimeoutError @@ -85,13 +86,18 @@ class Price(object): toRequest = set() # Compose list of items we're going to request - for typeID in priceMap: + for typeID in tuple(priceMap): # Get item object item = db.getItem(typeID) # We're not going to request items only with market group, as eve-central # doesn't provide any data for items not on the market - if item is not None and item.marketGroupID: - toRequest.add(typeID) + if item is None: + continue + if not item.marketGroupID: + priceMap[typeID].status = PriceStatus.notSupported + del priceMap[typeID] + continue + toRequest.add(typeID) # Do not waste our time if all items are not on the market if len(toRequest) == 0: @@ -117,11 +123,10 @@ class Price(object): except TimeoutError: # Timeout error deserves special treatment pyfalog.warning("Price fetch timout") - for typeID in priceMap.keys(): + for typeID in tuple(priceMap): priceobj = priceMap[typeID] priceobj.time = time.time() + TIMEOUT - priceobj.failed = True - + priceobj.status = PriceStatus.fail del priceMap[typeID] except Exception as ex: # something happened, try another source @@ -134,7 +139,7 @@ class Price(object): for typeID in priceMap.keys(): priceobj = priceMap[typeID] priceobj.time = time.time() + REREQUEST - priceobj.failed = True + priceobj.status = PriceStatus.fail @classmethod def fitItemsList(cls, fit): @@ -167,16 +172,15 @@ class Price(object): sMkt = Market.getInstance() item = sMkt.getItem(objitem) - return item.price + return item.price.price def getPrices(self, objitems, callback, waitforthread=False): """Get prices for multiple typeIDs""" requests = [] sMkt = Market.getInstance() for objitem in objitems: - priceobj = sMkt.getItem(objitem).priceObj - if priceobj: - requests.append(priceobj) + item = sMkt.getItem(objitem) + requests.append(item.price) def cb(): try: From cbd1a34c685d55b01646e33ac541d020195b252a Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 13:14:18 +0300 Subject: [PATCH 07/16] Round cpu/pg like EVE does To work around some fits where pyfa shows that items do not fit when they actually do --- eos/modifiedAttributeDict.py | 5 ++++- eos/saveddata/fit.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index d660dd38b..756a9f806 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -215,6 +215,8 @@ class ModifiedAttributeDict(collections.MutableMapping): if force is not None: if cappingValue is not None: force = min(force, cappingValue) + if key in (50, 30, 48, 11): + force = round(force, 2) return force # Grab our values if they're there, otherwise we'll take default values preIncrease = self.__preIncreases.get(key, 0) @@ -268,7 +270,8 @@ class ModifiedAttributeDict(collections.MutableMapping): # Cap value if we have cap defined if cappingValue is not None: val = min(val, cappingValue) - + if key in (50, 30, 48, 11): + val = round(val, 2) return val def __handleSkill(self, skillName): diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 4373bcdd0..f33a78651 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -1016,11 +1016,11 @@ class Fit(object): @property def pgUsed(self): - return self.getItemAttrOnlineSum(self.modules, "power") + return round(self.getItemAttrOnlineSum(self.modules, "power"), 2) @property def cpuUsed(self): - return self.getItemAttrOnlineSum(self.modules, "cpu") + return round(self.getItemAttrOnlineSum(self.modules, "cpu"), 2) @property def droneBandwidthUsed(self): From 6ebc90b8aabce27163cb03a54827d2ddb4a834f3 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 15:30:08 +0300 Subject: [PATCH 08/16] Remove flush as it's part of commit according to sqlalchemy docs --- eos/db/saveddata/queries.py | 1 - 1 file changed, 1 deletion(-) diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index e582eef87..c8da4e3ee 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -542,7 +542,6 @@ def commit(): with sd_lock: try: saveddata_session.commit() - saveddata_session.flush() except Exception as ex: saveddata_session.rollback() exc_info = sys.exc_info() From 3ed949395e421c7e2ace12fcf2395f47dcbddbf3 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 15:56:15 +0300 Subject: [PATCH 09/16] Flush prices rather than commit, it takes much less time and still satisfies our needs --- eos/db/saveddata/queries.py | 12 +++++++++++- eos/gamedata.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index c8da4e3ee..448584420 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -542,7 +542,17 @@ def commit(): with sd_lock: try: saveddata_session.commit() - except Exception as ex: + except Exception: + saveddata_session.rollback() + exc_info = sys.exc_info() + raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) + + +def flush(): + with sd_lock: + try: + saveddata_session.flush() + except Exception: saveddata_session.rollback() exc_info = sys.exc_info() raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) diff --git a/eos/gamedata.py b/eos/gamedata.py index 0243eec0f..5ec2a1e14 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -458,7 +458,7 @@ class Item(EqBase): pyfalog.debug("Creating a price for {}".format(self.ID)) self.__priceObj = types_Price(self.ID) eos.db.add(self.__priceObj) - eos.db.commit() + eos.db.flush() else: self.__priceObj = db_price From 7dd1c638982154f63bef6e0a063dc6d831d31fc7 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 16:19:01 +0300 Subject: [PATCH 10/16] Return 0 price even if price has not been fetched --- eos/saveddata/price.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eos/saveddata/price.py b/eos/saveddata/price.py index ee575b70c..a2a630c30 100644 --- a/eos/saveddata/price.py +++ b/eos/saveddata/price.py @@ -49,7 +49,7 @@ class Price(object): @property def price(self): - if self.status in (PriceStatus.fail, PriceStatus.notSupported): + if self.status != PriceStatus.success: return 0 else: return self.__price or 0 From 7bad3bc376d38345e1905f954c33da2adbf302c6 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 17:07:06 +0300 Subject: [PATCH 11/16] Show 'market group' context menu for mutated items as well --- gui/builtinContextMenus/marketJump.py | 8 +++++++- gui/builtinItemStatsViews/itemMutator.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gui/builtinContextMenus/marketJump.py b/gui/builtinContextMenus/marketJump.py index c630312c1..a5d5e8eeb 100644 --- a/gui/builtinContextMenus/marketJump.py +++ b/gui/builtinContextMenus/marketJump.py @@ -26,7 +26,10 @@ class MarketJump(ContextMenu): sMkt = Market.getInstance() item = getattr(selection[0], "item", selection[0]) + isMutated = getattr(selection[0], "isMutated", False) mktGrp = sMkt.getMarketGroupByItem(item) + if mktGrp is None and isMutated: + mktGrp = sMkt.getMarketGroupByItem(selection[0].baseItem) # 1663 is Special Edition Festival Assets, we don't have root group for it if mktGrp is None or mktGrp.ID == 1663: @@ -43,7 +46,10 @@ class MarketJump(ContextMenu): if srcContext in ("fittingCharge", "projectedCharge"): item = selection[0].charge elif hasattr(selection[0], "item"): - item = selection[0].item + if getattr(selection[0], "isMutated", False): + item = selection[0].baseItem + else: + item = selection[0].item else: item = selection[0] diff --git a/gui/builtinItemStatsViews/itemMutator.py b/gui/builtinItemStatsViews/itemMutator.py index df20f9567..802f8b4f3 100644 --- a/gui/builtinItemStatsViews/itemMutator.py +++ b/gui/builtinItemStatsViews/itemMutator.py @@ -24,6 +24,8 @@ class ItemMutator(wx.Panel): self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() mainSizer = wx.BoxSizer(wx.VERTICAL) + baseItemSizer = wx.BoxSizer(wx.VERTICAL) + self.goodColor = wx.Colour(96, 191, 0) self.badColor = wx.Colour(255, 64, 0) From a4fae73a1e02202457047c70400eaf49e6732331 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 17:12:21 +0300 Subject: [PATCH 12/16] Make "fill with module" context menu entry disablable via preferences --- gui/builtinContextMenus/fillWithModule.py | 2 ++ gui/builtinPreferenceViews/pyfaContextMenuPreferences.py | 8 ++++++++ service/settings.py | 1 + 3 files changed, 11 insertions(+) diff --git a/gui/builtinContextMenus/fillWithModule.py b/gui/builtinContextMenus/fillWithModule.py index 9e32dbc0d..0dd8d0d91 100644 --- a/gui/builtinContextMenus/fillWithModule.py +++ b/gui/builtinContextMenus/fillWithModule.py @@ -13,6 +13,8 @@ class FillWithModule(ContextMenu): self.settings = ContextMenuSettings.getInstance() def display(self, srcContext, selection): + if not self.settings.get('moduleFill'): + return False return srcContext in ("fittingModule") def getText(self, itmContext, selection): diff --git a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py index 458424dce..7c767c5fb 100644 --- a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py +++ b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py @@ -81,6 +81,11 @@ class PFContextMenuPref(PreferenceView): rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5) self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change) + self.rbBox8 = wx.RadioBox(panel, -1, "Fill with module", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS) + self.rbBox8.SetSelection(self.settings.get('moduleFill')) + rbSizerRow3.Add(self.rbBox8, 1, wx.TOP | wx.RIGHT, 5) + self.rbBox8.Bind(wx.EVT_RADIOBOX, self.OnSetting8Change) + mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0) panel.SetSizer(mainSizer) @@ -107,6 +112,9 @@ class PFContextMenuPref(PreferenceView): def OnSetting7Change(self, event): self.settings.set('project', event.GetInt()) + def OnSetting8Change(self, event): + self.settings.set('moduleFill', event.GetInt()) + def getImage(self): return BitmapLoader.getBitmap("settings_menu", "gui") diff --git a/service/settings.py b/service/settings.py index 39f8def9c..df5038d1f 100644 --- a/service/settings.py +++ b/service/settings.py @@ -525,6 +525,7 @@ class ContextMenuSettings(object): "tacticalMode" : 1, "targetResists" : 1, "whProjector" : 1, + "moduleFill" : 1, } self.ContextMenuDefaultSettings = SettingsProvider.getInstance().getSettings("pyfaContextMenuSettings", ContextMenuDefaultSettings) From deee6fd6ab30e350ae7223d4ede35364d0dbbe73 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 17:31:45 +0300 Subject: [PATCH 13/16] Make GTK window even bigger by default, and hide description type if there's no description for the item --- gui/builtinItemStatsViews/attributeSlider.py | 1 - gui/builtinItemStatsViews/itemDescription.py | 1 - gui/itemStats.py | 7 ++++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gui/builtinItemStatsViews/attributeSlider.py b/gui/builtinItemStatsViews/attributeSlider.py index e45fa8f49..9e3585ea9 100644 --- a/gui/builtinItemStatsViews/attributeSlider.py +++ b/gui/builtinItemStatsViews/attributeSlider.py @@ -58,7 +58,6 @@ class AttributeSlider(wx.Panel): self.UserMinValue = minValue self.UserMaxValue = maxValue - print(self.UserMinValue, self.UserMaxValue) self.inverse = inverse diff --git a/gui/builtinItemStatsViews/itemDescription.py b/gui/builtinItemStatsViews/itemDescription.py index 475b88788..9c62dcc78 100644 --- a/gui/builtinItemStatsViews/itemDescription.py +++ b/gui/builtinItemStatsViews/itemDescription.py @@ -15,7 +15,6 @@ class ItemDescription(wx.Panel): fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) self.description = wx.html.HtmlWindow(self) - if not item.description: return diff --git a/gui/itemStats.py b/gui/itemStats.py index 3768780e2..76172dee7 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -91,7 +91,7 @@ class ItemStatsDialog(wx.Dialog): self.SetMinSize((300, 200)) if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room - self.SetSize((580, 500)) + self.SetSize((630, 500)) else: self.SetSize((550, 500)) # self.SetMaxSize((500, -1)) @@ -168,8 +168,9 @@ class ItemStatsContainer(wx.Panel): self.mutator = ItemMutator(self.nbContainer, stuff, item) self.nbContainer.AddPage(self.mutator, "Mutations") - self.desc = ItemDescription(self.nbContainer, stuff, item) - self.nbContainer.AddPage(self.desc, "Description") + if item.description: + self.desc = ItemDescription(self.nbContainer, stuff, item) + self.nbContainer.AddPage(self.desc, "Description") self.params = ItemParams(self.nbContainer, stuff, item, context) self.nbContainer.AddPage(self.params, "Attributes") From fc04a32913c645d0465a2ff7de919cbc98fc95c8 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 18:14:46 +0300 Subject: [PATCH 14/16] Add info about base item to mutations tab --- gui/builtinItemStatsViews/itemMutator.py | 16 ++++++++++++---- gui/itemStats.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/gui/builtinItemStatsViews/itemMutator.py b/gui/builtinItemStatsViews/itemMutator.py index 802f8b4f3..9ceeaeb27 100644 --- a/gui/builtinItemStatsViews/itemMutator.py +++ b/gui/builtinItemStatsViews/itemMutator.py @@ -22,9 +22,20 @@ class ItemMutator(wx.Panel): self.item = item self.timer = None self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() + + font = parent.GetFont() + font.SetWeight(wx.BOLD) + mainSizer = wx.BoxSizer(wx.VERTICAL) - baseItemSizer = wx.BoxSizer(wx.VERTICAL) + sourceItemsSizer = wx.BoxSizer(wx.HORIZONTAL) + sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons"), 0, wx.LEFT, 5) + sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.mutaplasmid.item.iconID, self, "icons"), 0, wx.LEFT, 0) + sourceItemShort = "{} {}".format(stuff.mutaplasmid.item.name.split(" ")[0], stuff.baseItem.name) + sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort) + sourceItemText.SetFont(font) + sourceItemsSizer.Add(sourceItemText, 0, wx.LEFT, 10) + mainSizer.Add(sourceItemsSizer, 0, wx.TOP | wx.BOTTOM | wx.EXPAND, 10) self.goodColor = wx.Colour(96, 191, 0) self.badColor = wx.Colour(255, 64, 0) @@ -60,9 +71,6 @@ class ItemMutator(wx.Panel): headingSizer = wx.BoxSizer(wx.HORIZONTAL) - font = parent.GetFont() - font.SetWeight(wx.BOLD) - headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10) displayName = wx.StaticText(self, wx.ID_ANY, m.attribute.displayName) diff --git a/gui/itemStats.py b/gui/itemStats.py index 76172dee7..50a02f065 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -91,7 +91,7 @@ class ItemStatsDialog(wx.Dialog): self.SetMinSize((300, 200)) if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room - self.SetSize((630, 500)) + self.SetSize((630, 550)) else: self.SetSize((550, 500)) # self.SetMaxSize((500, -1)) From 194ebb96a7e64e9c6cc8a303961130077d3d9ff3 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 18:18:38 +0300 Subject: [PATCH 15/16] Add ruler between base item info and mutation stats --- gui/builtinItemStatsViews/itemMutator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/builtinItemStatsViews/itemMutator.py b/gui/builtinItemStatsViews/itemMutator.py index 9ceeaeb27..3e9edc582 100644 --- a/gui/builtinItemStatsViews/itemMutator.py +++ b/gui/builtinItemStatsViews/itemMutator.py @@ -35,7 +35,9 @@ class ItemMutator(wx.Panel): sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort) sourceItemText.SetFont(font) sourceItemsSizer.Add(sourceItemText, 0, wx.LEFT, 10) - mainSizer.Add(sourceItemsSizer, 0, wx.TOP | wx.BOTTOM | wx.EXPAND, 10) + mainSizer.Add(sourceItemsSizer, 0, wx.TOP | wx.EXPAND, 10) + + mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5) self.goodColor = wx.Colour(96, 191, 0) self.badColor = wx.Colour(255, 64, 0) From 436113dedce62bf11ac7e3760da4a9180c4e4647 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Wed, 13 Feb 2019 18:51:44 +0300 Subject: [PATCH 16/16] Set price object status on successful fetches --- service/marketSources/evemarketdata.py | 5 +++-- service/marketSources/evemarketer.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/service/marketSources/evemarketdata.py b/service/marketSources/evemarketdata.py index bd1f8f3af..15edc92ef 100644 --- a/service/marketSources/evemarketdata.py +++ b/service/marketSources/evemarketdata.py @@ -22,6 +22,7 @@ from xml.dom import minidom from logbook import Logger +from eos.saveddata.price import PriceStatus from service.network import Network from service.price import Price, TIMEOUT, VALIDITY @@ -52,6 +53,7 @@ class EveMarketData(object): price = float(type_.firstChild.data) except (TypeError, ValueError): pyfalog.warning("Failed to get price for: {0}", type_) + continue # Fill price data priceobj = priceMap[typeID] @@ -61,11 +63,10 @@ class EveMarketData(object): if price != 0: priceobj.price = price priceobj.time = time.time() + VALIDITY + priceobj.status = PriceStatus.success else: priceobj.time = time.time() + TIMEOUT - priceobj.failed = None - # delete price from working dict del priceMap[typeID] diff --git a/service/marketSources/evemarketer.py b/service/marketSources/evemarketer.py index 2418d10c5..478de4371 100644 --- a/service/marketSources/evemarketer.py +++ b/service/marketSources/evemarketer.py @@ -22,6 +22,7 @@ from xml.dom import minidom from logbook import Logger +from eos.saveddata.price import PriceStatus from service.network import Network from service.price import Price, VALIDITY @@ -61,7 +62,7 @@ class EveMarketer(object): priceobj = priceMap[typeID] priceobj.price = percprice priceobj.time = time.time() + VALIDITY - priceobj.failed = None + priceobj.status = PriceStatus.success # delete price from working dict del priceMap[typeID]