From 9e96aac04d437030bcbea40f6d52c50f264ac01c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 4 Jun 2015 14:10:27 -0500 Subject: [PATCH 1/5] Fix situation in which module prices are fetched individually (which the price column). Instead, have them wait in a queue that is processed when the entire fit is called and calculated (with the price pane). Also adds a little refresh icon to know that prices are updating and it's not just blank (might change) --- gui/builtinViewColumns/price.py | 9 ++++++--- gui/display.py | 6 +++--- icons/refresh_small.png | Bin 0 -> 506 bytes service/market.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 icons/refresh_small.png diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index 5fe6ef954..be387c8d5 100644 --- a/gui/builtinViewColumns/price.py +++ b/gui/builtinViewColumns/price.py @@ -50,12 +50,15 @@ class Price(ViewColumn): return formatAmount(price, 3, 3, 9, currency=True) def delayedText(self, mod, display, colItem): - def callback(requests): - price = requests[0].price + sMkt = service.Market.getInstance() + def callback(item): + price = sMkt.getPriceNow(item.ID).price colItem.SetText(formatAmount(price, 3, 3, 9, currency=True) if price else "") + colItem.SetImage(-1) display.SetItem(colItem) - service.Market.getInstance().getPrices([mod.item.ID], callback) + sMkt.waitForPrice(mod.item, callback) + return self.fittingView.imageList.GetImageIndex("refresh_small", "icons") def getImageId(self, mod): return -1 diff --git a/gui/display.py b/gui/display.py index 0cfc28854..cd08a8d1e 100644 --- a/gui/display.py +++ b/gui/display.py @@ -249,10 +249,10 @@ class Display(wx.ListCtrl): oldImageId = colItem.GetImage() newText = col.getText(st) if newText is False: - col.delayedText(st, self, colItem) + newImageId = col.delayedText(st, self, colItem) newText = "" - - newImageId = col.getImageId(st) + else: + newImageId = col.getImageId(st) colItem.SetText(newText) colItem.SetImage(newImageId) diff --git a/icons/refresh_small.png b/icons/refresh_small.png new file mode 100644 index 0000000000000000000000000000000000000000..d3087dfc920b1705cdebc5beeba8a62047c99b68 GIT binary patch literal 506 zcmV-LrYk6ae86R!cq$1)rW52v1)b7{)|O}AG6Uw?DO|Ft)k{$F)%(f{RF=l?I+ zlJnnhy4xL`1{54hojLt{-~Wv_SN(53T=hS3efa;Fl|lb2w&(vZ*_{2~XR7ONye`Pu zoA?0e-~T}W{*PZ9b_gaOFw5hT_Y~(tZoT$Qj_p>QLT-Dpnng!_d8sIiCa_~9WpM}{jZm=`Cltb^#NWN0R6i=Yh}{^)FrY1 zts6}Ln^hV9kC-0#zxPDb|Kx?y|5Z~IXW}#f=--za%M<=jKim6%%IU8E6Hm4O?>pJ@ zzvo2be~`FJvceg~cv%O$F0gC1*cmrB?0?kE;Q#uCTK~1P)&8reDgReaQaGRxhpHK8 wAOih+0O;RKWM?MbJPl^eOx0CX$&G|C05z+&w|oy)!1^@s6 literal 0 HcmV?d00001 diff --git a/service/market.py b/service/market.py index 0ea234aaf..951f31456 100644 --- a/service/market.py +++ b/service/market.py @@ -71,6 +71,7 @@ class ShipBrowserWorkerThread(threading.Thread): class PriceWorkerThread(threading.Thread): def run(self): self.queue = Queue.Queue() + self.wait = {} self.processUpdates() def processUpdates(self): @@ -86,9 +87,21 @@ class PriceWorkerThread(threading.Thread): wx.CallAfter(callback) queue.task_done() + # After we fetch prices, go through the list of waiting items and call their callbacks + for price in requests: + callbacks = self.wait.pop(price.typeID, None) + if callbacks: + for callback in callbacks: + wx.CallAfter(callback) + def trigger(self, prices, callbacks): self.queue.put((callbacks, prices)) + def setToWait(self, itemID, callback): + if itemID not in self.wait: + self.wait[itemID] = [] + self.wait[itemID].append(callback) + class SearchWorkerThread(threading.Thread): def run(self): self.cv = threading.Condition() @@ -719,6 +732,21 @@ class Market(): self.priceWorkerThread.trigger(requests, cb) + def waitForPrice(self, item, callback): + """ + Wait for prices to be fetched and callback when finished. This is used with the column prices for modules. + Instead of calling them individually, we set them to wait until the entire fit price is called and calculated + (see GH #290) + """ + + def cb(): + try: + callback(item) + except: + pass + + self.priceWorkerThread.setToWait(item.ID, cb) + def clearPriceCache(self): self.priceCache.clear() deleted_rows = eos.db.clearPrices() From 53c9169043e86b380946fcdce9f173f41575e03c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 5 Jun 2015 15:39:10 -0500 Subject: [PATCH 2/5] Simplified price pane. Will show pricing update label and will only clear it when prices are done. Removed all timer code as it makes it overly complicated and I suspect half of it didn't work as intended anyway --- gui/builtinStatsViews/priceViewFull.py | 56 ++++---------------------- 1 file changed, 7 insertions(+), 49 deletions(-) diff --git a/gui/builtinStatsViews/priceViewFull.py b/gui/builtinStatsViews/priceViewFull.py index 2aef192c8..eba4c134b 100644 --- a/gui/builtinStatsViews/priceViewFull.py +++ b/gui/builtinStatsViews/priceViewFull.py @@ -30,37 +30,13 @@ class PriceViewFull(StatsView): def __init__(self, parent): StatsView.__init__(self) self.parent = parent - self._timerId = wx.NewId() - self._timer = None - self.parent.Bind(wx.EVT_TIMER, self.OnTimer) - self._timerRunsBeforeUpdate = 60 - self._timerRuns = 0 - self._timerIdUpdate = wx.NewId() - self._timerUpdate = None self._cachedShip = 0 self._cachedFittings = 0 self._cachedTotal = 0 - def OnTimer(self, event): - if self._timerId == event.GetId(): - if self._timerRuns >= self._timerRunsBeforeUpdate: - self._timerRuns = 0 - self._timer.Stop() - self.refreshPanel(self.fit) - else: - self.labelEMStatus.SetLabel("Prices update retry in: %d seconds" %(self._timerRunsBeforeUpdate - self._timerRuns)) - self._timerRuns += 1 - if self._timerIdUpdate == event.GetId(): - self._timerUpdate.Stop() - self.labelEMStatus.SetLabel("") - def getHeaderText(self, fit): return "Price" - def getTextExtentW(self, text): - width, height = self.parent.GetTextExtent(text) - return width - def populatePanel(self, contentPanel, headerPanel): contentSizer = contentPanel.GetSizer() self.panel = contentPanel @@ -111,22 +87,11 @@ class PriceViewFull(StatsView): for cargo in fit.cargo: for _ in xrange(cargo.amount): typeIDs.append(cargo.itemID) - if self._timer: - if self._timer.IsRunning(): - self._timer.Stop() + sMkt = service.Market.getInstance() sMkt.getPrices(typeIDs, self.processPrices) self.labelEMStatus.SetLabel("Updating prices...") - if not self._timerUpdate: - self._timerUpdate = wx.Timer(self.parent, self._timerIdUpdate) - if self._timerUpdate: - if not self._timerUpdate.IsRunning(): - self._timerUpdate.Start(1000) - else: - if self._timer: - if self._timer.IsRunning(): - self._timer.Stop() self.labelEMStatus.SetLabel("") self.labelPriceShip.SetLabel("0.0 ISK") self.labelPriceFittings.SetLabel("0.0 ISK") @@ -136,20 +101,13 @@ class PriceViewFull(StatsView): def processPrices(self, prices): shipPrice = prices[0].price - if shipPrice == None: - if not self._timer: - self._timer = wx.Timer(self.parent, self._timerId) - self._timer.Start(1000) - self._timerRuns = 0 - else: - if self._timer: - self._timer.Stop() - - self.labelEMStatus.SetLabel("") - - if shipPrice == None: - shipPrice = 0 modPrice = sum(map(lambda p: p.price or 0, prices[1:])) + + if shipPrice is not None: + self.labelEMStatus.SetLabel("") + else: + shipPrice = 0 + if self._cachedShip != shipPrice: self.labelPriceShip.SetLabel("%s ISK" % formatAmount(shipPrice, 3, 3, 9, currency=True)) self.labelPriceShip.SetToolTip(wx.ToolTip(locale.format('%.2f', shipPrice, 1))) From 6cc6fd9468d30bf309ab38596b46cd39e9efa98c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 6 Jun 2015 22:42:42 -0500 Subject: [PATCH 3/5] Instead of icon, use unicode refresh. Minor issues with image and GUI flickering --- gui/builtinViewColumns/price.py | 4 +--- gui/display.py | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index be387c8d5..df88d0893 100644 --- a/gui/builtinViewColumns/price.py +++ b/gui/builtinViewColumns/price.py @@ -54,11 +54,9 @@ class Price(ViewColumn): def callback(item): price = sMkt.getPriceNow(item.ID).price colItem.SetText(formatAmount(price, 3, 3, 9, currency=True) if price else "") - colItem.SetImage(-1) - display.SetItem(colItem) + display.SetItem(colItem) sMkt.waitForPrice(mod.item, callback) - return self.fittingView.imageList.GetImageIndex("refresh_small", "icons") def getImageId(self, mod): return -1 diff --git a/gui/display.py b/gui/display.py index cd08a8d1e..707623581 100644 --- a/gui/display.py +++ b/gui/display.py @@ -249,10 +249,10 @@ class Display(wx.ListCtrl): oldImageId = colItem.GetImage() newText = col.getText(st) if newText is False: - newImageId = col.delayedText(st, self, colItem) - newText = "" - else: - newImageId = col.getImageId(st) + col.delayedText(st, self, colItem) + newText = u"\u21bb" + + newImageId = col.getImageId(st) colItem.SetText(newText) colItem.SetImage(newImageId) From 87e5929cb1be7d8185cc6f4fd37a22f8847f675c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 2 Jul 2015 19:35:53 -0400 Subject: [PATCH 4/5] DB migration is triggered by number of `upgrade` files found, rather than number in config.py. This allows us to remove the db version variable in config.py and not worry about it. --- config.py | 4 ---- eos/db/migration.py | 18 +++++++++++------- eos/db/migrations/upgrade8.py | 7 ++----- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/config.py b/config.py index 32c24962f..8254f2714 100644 --- a/config.py +++ b/config.py @@ -19,10 +19,6 @@ expansionName = "Carnyx" expansionVersion = "1.0" evemonMinVersion = "4081" -# Database version (int ONLY) -# Increment every time we need to flag for user database upgrade/modification -dbversion = 7 - pyfaPath = None savePath = None staticPath = None diff --git a/eos/db/migration.py b/eos/db/migration.py index 92224c34c..dcf08134c 100644 --- a/eos/db/migration.py +++ b/eos/db/migration.py @@ -1,32 +1,36 @@ import config import shutil import time +import os def getVersion(db): cursor = db.execute('PRAGMA user_version') return cursor.fetchone()[0] def update(saveddata_engine): - currversion = getVersion(saveddata_engine) + dbVersion = getVersion(saveddata_engine) - if currversion == config.dbversion: + files = os.listdir(os.path.join(os.path.dirname(__file__), "migrations")) + appVersion = len([f for f in files if f.startswith("upgrade")]) + + if dbVersion == appVersion: return - if currversion < config.dbversion: + if dbVersion < appVersion: # Automatically backup database toFile = "%s/saveddata_migration_%d-%d_%s.db"%( config.savePath, - currversion, - config.dbversion, + dbVersion, + appVersion, time.strftime("%Y%m%d_%H%M%S")) shutil.copyfile(config.saveDB, toFile) - for version in xrange(currversion, config.dbversion): + for version in xrange(dbVersion, appVersion): module = __import__('eos.db.migrations.upgrade%d'%(version+1), fromlist=True) upgrade = getattr(module, "upgrade", False) if upgrade: upgrade(saveddata_engine) # when all is said and done, set version to current - saveddata_engine.execute('PRAGMA user_version = %d'%config.dbversion) + saveddata_engine.execute('PRAGMA user_version = %d'%appVersion) diff --git a/eos/db/migrations/upgrade8.py b/eos/db/migrations/upgrade8.py index 6097c3799..9d2c04321 100644 --- a/eos/db/migrations/upgrade8.py +++ b/eos/db/migrations/upgrade8.py @@ -1,13 +1,10 @@ """ -Migration 4 +Migration 8 -- Converts modules based on Proteus Module Tiericide +- Converts modules based on Carnyx Module Tiericide Some modules have been unpublished (and unpublished module attributes are removed from database), which causes pyfa to crash. We therefore replace these modules with their new replacements - - Based on http://community.eveonline.com/news/patch-notes/patch-notes-for-proteus/ - and output of itemDiff.py """ From 874cf4ef0a30bd2a6d58fc1831bc918ffb5a373e Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 3 Jul 2015 02:37:52 -0400 Subject: [PATCH 5/5] Use old price information if update fails. Add "(!)" to show that price is out of date --- eos/saveddata/price.py | 1 + gui/builtinStatsViews/priceViewFull.py | 5 +---- gui/builtinViewColumns/price.py | 12 ++++++++---- service/market.py | 4 ---- service/price.py | 6 ++---- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/eos/saveddata/price.py b/eos/saveddata/price.py index 4c7de879c..ea9b0ac92 100644 --- a/eos/saveddata/price.py +++ b/eos/saveddata/price.py @@ -26,6 +26,7 @@ class Price(object): def __init__(self, typeID): self.typeID = typeID self.time = 0 + self.price = 0 self.failed = None self.__item = None diff --git a/gui/builtinStatsViews/priceViewFull.py b/gui/builtinStatsViews/priceViewFull.py index eba4c134b..f1d3fbd1d 100644 --- a/gui/builtinStatsViews/priceViewFull.py +++ b/gui/builtinStatsViews/priceViewFull.py @@ -103,10 +103,7 @@ class PriceViewFull(StatsView): shipPrice = prices[0].price modPrice = sum(map(lambda p: p.price or 0, prices[1:])) - if shipPrice is not None: - self.labelEMStatus.SetLabel("") - else: - shipPrice = 0 + self.labelEMStatus.SetLabel("") if self._cachedShip != shipPrice: self.labelPriceShip.SetLabel("%s ISK" % formatAmount(shipPrice, 3, 3, 9, currency=True)) diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index df88d0893..17c7ade3c 100644 --- a/gui/builtinViewColumns/price.py +++ b/gui/builtinViewColumns/price.py @@ -39,7 +39,7 @@ class Price(ViewColumn): sMkt = service.Market.getInstance() price = sMkt.getPriceNow(stuff.item.ID) - if not price or not price.price: + if not price or not price.price or not price.isValid: return False price = price.price # Set new price variable with what we need @@ -52,9 +52,13 @@ class Price(ViewColumn): def delayedText(self, mod, display, colItem): sMkt = service.Market.getInstance() def callback(item): - price = sMkt.getPriceNow(item.ID).price - colItem.SetText(formatAmount(price, 3, 3, 9, currency=True) if price else "") - display.SetItem(colItem) + price = sMkt.getPriceNow(item.ID) + text = formatAmount(price.price, 3, 3, 9, currency=True) if price.price else "" + if price.failed: text += " (!)" + colItem.SetText(text) + + display.SetItem(colItem) + sMkt.waitForPrice(mod.item, callback) diff --git a/service/market.py b/service/market.py index 951f31456..ef4cf387a 100644 --- a/service/market.py +++ b/service/market.py @@ -706,10 +706,6 @@ class Market(): self.priceCache[typeID] = price - if not price.isValid: - # if the price has expired - price.price = None - return price def getPricesNow(self, typeIDs): diff --git a/service/price.py b/service/price.py index aefd1b14f..78e3e289b 100644 --- a/service/price.py +++ b/service/price.py @@ -71,7 +71,6 @@ class Price(): # Attempt to send request and process it try: - len(priceMap) network = service.Network.getInstance() data = network.request(baseurl, network.PRICES, data) xml = minidom.parse(data) @@ -102,7 +101,7 @@ class Price(): for typeID in priceMap.keys(): priceobj = priceMap[typeID] priceobj.time = time.time() + TIMEOUT - priceobj.failed = None + priceobj.failed = True del priceMap[typeID] except: # all other errors will pass and continue onward to the REREQUEST delay @@ -111,6 +110,5 @@ class Price(): # if we get to this point, then we've got an error. Set to REREQUEST delay for typeID in priceMap.keys(): priceobj = priceMap[typeID] - priceobj.price = 0 priceobj.time = time.time() + REREQUEST - priceobj.failed = None + priceobj.failed = True