From da5aaf2f782b37927ca14dd9402486fd183162a6 Mon Sep 17 00:00:00 2001 From: Ryan Holmes Date: Sat, 23 Sep 2017 19:39:38 -0400 Subject: [PATCH] Feature/evemarketdata (#1297) * Add preliminary support for eve market data * Break out market sources into their own classes and register them onto the price service. Create preference option to select which source user wants. Default to eve central * fix tox stuff --- .../pyfaGeneralPreferences.py | 8 ++ service/fit.py | 1 + service/marketSources/__init__.py | 1 + service/marketSources/evecentral.py | 87 +++++++++++++++++++ service/marketSources/evemarketdata.py | 85 ++++++++++++++++++ service/price.py | 68 ++++----------- 6 files changed, 199 insertions(+), 51 deletions(-) create mode 100644 service/marketSources/__init__.py create mode 100644 service/marketSources/evecentral.py create mode 100644 service/marketSources/evemarketdata.py diff --git a/gui/builtinPreferenceViews/pyfaGeneralPreferences.py b/gui/builtinPreferenceViews/pyfaGeneralPreferences.py index acdfb398b7..6cb6117975 100644 --- a/gui/builtinPreferenceViews/pyfaGeneralPreferences.py +++ b/gui/builtinPreferenceViews/pyfaGeneralPreferences.py @@ -90,7 +90,9 @@ def populatePanel(self, panel): self.stDefaultSystem.Wrap(-1) priceSizer.Add(self.stDefaultSystem, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + self.chPriceSource = wx.Choice(panel, choices=sorted(Price.sources.keys())) self.chPriceSystem = wx.Choice(panel, choices=Price.systemsList.keys()) + priceSizer.Add(self.chPriceSource, 1, wx.ALL | wx.EXPAND, 5) priceSizer.Add(self.chPriceSystem, 1, wx.ALL | wx.EXPAND, 5) mainSizer.Add(priceSizer, 0, wx.ALL | wx.EXPAND, 0) @@ -124,6 +126,7 @@ def populatePanel(self, panel): self.cbGaugeAnimation.SetValue(self.sFit.serviceFittingOptions["enableGaugeAnimation"]) self.cbExportCharges.SetValue(self.sFit.serviceFittingOptions["exportCharges"]) self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"]) + self.chPriceSource.SetStringSelection(self.sFit.serviceFittingOptions["priceSource"]) self.chPriceSystem.SetStringSelection(self.sFit.serviceFittingOptions["priceSystem"]) self.cbShowShipBrowserTooltip.SetValue(self.sFit.serviceFittingOptions["showShipBrowserTooltip"]) self.intDelay.SetValue(self.sFit.serviceFittingOptions["marketSearchDelay"]) @@ -140,6 +143,7 @@ def populatePanel(self, panel): self.cbGaugeAnimation.Bind(wx.EVT_CHECKBOX, self.onCBGaugeAnimation) self.cbExportCharges.Bind(wx.EVT_CHECKBOX, self.onCBExportCharges) self.cbOpenFitInNew.Bind(wx.EVT_CHECKBOX, self.onCBOpenFitInNew) + self.chPriceSource.Bind(wx.EVT_CHOICE, self.onPricesSourceSelection) self.chPriceSystem.Bind(wx.EVT_CHOICE, self.onPriceSelection) self.cbShowShipBrowserTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowShipBrowserTooltip) self.intDelay.Bind(wx.lib.intctrl.EVT_INT, self.onMarketDelayChange) @@ -224,5 +228,9 @@ def onPriceSelection(self, event): wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) event.Skip() + def onPricesSourceSelection(self, event): + source = self.chPriceSource.GetString(self.chPriceSource.GetSelection()) + self.sFit.serviceFittingOptions["priceSource"] = source + PFGeneralPref.register() diff --git a/service/fit.py b/service/fit.py index 8fa36b4d48..c6898384f4 100644 --- a/service/fit.py +++ b/service/fit.py @@ -74,6 +74,7 @@ def __init__(self): "exportCharges": True, "openFitInNew": False, "priceSystem": "Jita", + "priceSource": "eve-central.com", "showShipBrowserTooltip": True, "marketSearchDelay": 250 } diff --git a/service/marketSources/__init__.py b/service/marketSources/__init__.py new file mode 100644 index 0000000000..b093a0010a --- /dev/null +++ b/service/marketSources/__init__.py @@ -0,0 +1 @@ +__all__ = ['evecentral', 'evemarketdata'] \ No newline at end of file diff --git a/service/marketSources/evecentral.py b/service/marketSources/evecentral.py new file mode 100644 index 0000000000..3a17ac957e --- /dev/null +++ b/service/marketSources/evecentral.py @@ -0,0 +1,87 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + +import time +from logbook import Logger +from xml.dom import minidom + +from service.network import Network +from service.price import Price, VALIDITY, TIMEOUT, TimeoutError + + +pyfalog = Logger(__name__) + + +class EveCentral(object): + + name = "eve-central.com" + + def __init__(self, types, system, priceMap): + data = [] + baseurl = "https://eve-central.com/api/marketstat" + data.append(("usesystem", system)) # Use Jita for market + + for typeID in types: # Add all typeID arguments + data.append(("typeid", typeID)) + + # Attempt to send request and process it + try: + network = Network.getInstance() + data = network.request(baseurl, network.PRICES, data) + xml = minidom.parse(data) + types = xml.getElementsByTagName("marketstat").item(0).getElementsByTagName("type") + # Cycle through all types we've got from request + for type_ in types: + # Get data out of each typeID details tree + typeID = int(type_.getAttribute("id")) + sell = type_.getElementsByTagName("sell").item(0) + # If price data wasn't there, set price to zero + try: + percprice = float(sell.getElementsByTagName("percentile").item(0).firstChild.data) + except (TypeError, ValueError): + pyfalog.warning("Failed to get price for: {0}", type_) + percprice = 0 + + # Fill price data + priceobj = priceMap[typeID] + priceobj.price = percprice + priceobj.time = time.time() + VALIDITY + priceobj.failed = None + + # delete price from working dict + del priceMap[typeID] + + # If getting or processing data returned any errors + except TimeoutError: + # Timeout error deserves special treatment + pyfalog.warning("Price fetch timout") + for typeID in priceMap.keys(): + priceobj = priceMap[typeID] + priceobj.time = time.time() + TIMEOUT + priceobj.failed = True + + del priceMap[typeID] + except: + # all other errors will pass and continue onward to the REREQUEST delay + pyfalog.warning("Caught exception in fetchPrices") + pass + pass + + +Price.register(EveCentral) diff --git a/service/marketSources/evemarketdata.py b/service/marketSources/evemarketdata.py new file mode 100644 index 0000000000..0b3c63daca --- /dev/null +++ b/service/marketSources/evemarketdata.py @@ -0,0 +1,85 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + +import time +from logbook import Logger +from xml.dom import minidom + +from service.network import Network +from service.price import Price, VALIDITY, TIMEOUT, TimeoutError + + +pyfalog = Logger(__name__) + + +class EveMarketData(object): + + name = "eve-marketdata.com" + + def __init__(self, types, system, priceMap): + data = [] + baseurl = "https://eve-marketdata.com/api/item_prices.xml" + data.append(("system_id", system)) # Use Jita for market + data.append(("type_ids", ','.join(str(x) for x in types))) + + # Attempt to send request and process it + try: + network = Network.getInstance() + data = network.request(baseurl, network.PRICES, data) + xml = minidom.parse(data) + print (xml.getElementsByTagName("eve").item(0)) + types = xml.getElementsByTagName("eve").item(0).getElementsByTagName("price") + # Cycle through all types we've got from request + for type_ in types: + # Get data out of each typeID details tree + typeID = int(type_.getAttribute("id")) + price = 0 + + try: + price = float(type_.firstChild.data) + except (TypeError, ValueError): + pyfalog.warning("Failed to get price for: {0}", type_) + + # Fill price data + priceobj = priceMap[typeID] + priceobj.price = price + priceobj.time = time.time() + VALIDITY + priceobj.failed = None + + # delete price from working dict + del priceMap[typeID] + + # If getting or processing data returned any errors + except TimeoutError: + # Timeout error deserves special treatment + pyfalog.warning("Price fetch timout") + for typeID in priceMap.keys(): + priceobj = priceMap[typeID] + priceobj.time = time.time() + TIMEOUT + priceobj.failed = True + + del priceMap[typeID] + except: + # all other errors will pass and continue onward to the REREQUEST delay + pyfalog.warning("Caught exception in fetchPrices") + pass + pass + + +Price.register(EveMarketData) diff --git a/service/price.py b/service/price.py index 9c6da235a7..4df9aaa315 100644 --- a/service/price.py +++ b/service/price.py @@ -50,12 +50,18 @@ class Price(object): "Hek": 30002053 } + sources = {} + def __init__(self): # Start price fetcher self.priceWorkerThread = PriceWorkerThread() self.priceWorkerThread.daemon = True self.priceWorkerThread.start() + @classmethod + def register(cls, source): + cls.sources[source.name] = source + @classmethod def getInstance(cls): if cls.instance is None: @@ -92,58 +98,15 @@ def fetchPrices(cls, prices): if len(toRequest) == 0: return - # This will store POST data for eve-central - data = [] - sFit = Fit.getInstance() - # Base request URL - baseurl = "https://eve-central.com/api/marketstat" - data.append(("usesystem", cls.systemsList[sFit.serviceFittingOptions["priceSystem"]])) # Use Jita for market - - for typeID in toRequest: # Add all typeID arguments - data.append(("typeid", typeID)) - - # Attempt to send request and process it - try: - network = Network.getInstance() - data = network.request(baseurl, network.PRICES, data) - xml = minidom.parse(data) - types = xml.getElementsByTagName("marketstat").item(0).getElementsByTagName("type") - # Cycle through all types we've got from request - for type_ in types: - # Get data out of each typeID details tree - typeID = int(type_.getAttribute("id")) - sell = type_.getElementsByTagName("sell").item(0) - # If price data wasn't there, set price to zero - try: - percprice = float(sell.getElementsByTagName("percentile").item(0).firstChild.data) - except (TypeError, ValueError): - pyfalog.warning("Failed to get price for: {0}", type_) - percprice = 0 - - # Fill price data - priceobj = priceMap[typeID] - priceobj.price = percprice - priceobj.time = time.time() + VALIDITY - priceobj.failed = None - - # delete price from working dict - del priceMap[typeID] - - # If getting or processing data returned any errors - except TimeoutError: - # Timeout error deserves special treatment - pyfalog.warning("Price fetch timout") - for typeID in priceMap.keys(): - priceobj = priceMap[typeID] - priceobj.time = time.time() + TIMEOUT - priceobj.failed = True - - del priceMap[typeID] - except: - # all other errors will pass and continue onward to the REREQUEST delay - pyfalog.warning("Caught exception in fetchPrices") - pass + + if len(cls.sources.keys()) == 0: + pyfalog.warn('No price source can be found') + return + + # attempt to find user's selected price source, otherwise get first one + sourceCls = cls.sources.get(sFit.serviceFittingOptions["priceSource"], cls.sources[cls.sources.keys()[0]]) + sourceCls(toRequest, cls.systemsList[sFit.serviceFittingOptions["priceSystem"]], priceMap) # if we get to this point, then we've got an error. Set to REREQUEST delay for typeID in priceMap.keys(): @@ -246,3 +209,6 @@ def setToWait(self, itemID, callback): if itemID not in self.wait: self.wait[itemID] = [] self.wait[itemID].append(callback) + + +from service.marketSources import evecentral, evemarketdata # noqa: E402