diff --git a/config.py b/config.py index a241db5d8..3143e80f0 100644 --- a/config.py +++ b/config.py @@ -19,10 +19,6 @@ expansionName = "Singularity" expansionVersion = "910808" 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 """ 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 2aef192c8..f1d3fbd1d 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,10 @@ 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:])) + + self.labelEMStatus.SetLabel("") + 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))) diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index 5fe6ef954..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 @@ -50,12 +50,17 @@ class Price(ViewColumn): return formatAmount(price, 3, 3, 9, currency=True) def delayedText(self, mod, display, colItem): - def callback(requests): - price = requests[0].price - colItem.SetText(formatAmount(price, 3, 3, 9, currency=True) if price else "") + sMkt = service.Market.getInstance() + def callback(item): + 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) - service.Market.getInstance().getPrices([mod.item.ID], callback) + + sMkt.waitForPrice(mod.item, callback) def getImageId(self, mod): return -1 diff --git a/gui/display.py b/gui/display.py index 0cfc28854..707623581 100644 --- a/gui/display.py +++ b/gui/display.py @@ -250,7 +250,7 @@ class Display(wx.ListCtrl): newText = col.getText(st) if newText is False: col.delayedText(st, self, colItem) - newText = "" + newText = u"\u21bb" newImageId = col.getImageId(st) diff --git a/icons/refresh_small.png b/icons/refresh_small.png new file mode 100644 index 000000000..d3087dfc9 Binary files /dev/null and b/icons/refresh_small.png differ diff --git a/service/market.py b/service/market.py index b4f3ef1b3..327d40d7b 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() @@ -690,10 +703,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): @@ -716,6 +725,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() 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