Files
pyfa/service/price.py
blitzmann 40ce7b7c0b Merge branch 'master' into test-3
# Conflicts:
#	.gitignore
#	eos/saveddata/character.py
#	service/network.py
#	service/price.py
2017-12-10 22:36:15 -05:00

238 lines
7.5 KiB
Python

# =============================================================================
# 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 <http://www.gnu.org/licenses/>.
# =============================================================================
import time
import threading
import queue
from xml.dom import minidom
from logbook import Logger
import wx
from eos import db
from service.network import Network, TimeoutError
from service.fit import Fit
from service.market import Market
pyfalog = Logger(__name__)
VALIDITY = 24 * 60 * 60 # Price validity period, 24 hours
REREQUEST = 4 * 60 * 60 # Re-request delay for failed fetches, 4 hours
TIMEOUT = 15 * 60 # Network timeout delay for connection issues, 15 minutes
class Price(object):
instance = None
systemsList = {
"Jita": 30000142,
"Amarr": 30002187,
"Dodixie": 30002659,
"Rens": 30002510,
"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:
cls.instance = Price()
return cls.instance
@classmethod
def fetchPrices(cls, prices):
"""Fetch all prices passed to this method"""
# Dictionary for our price objects
priceMap = {}
# Check all provided price objects, and add invalid ones to dictionary
for price in prices:
if not price.isValid:
priceMap[price.typeID] = price
if len(priceMap) == 0:
return
# Set of items which are still to be requested from this service
toRequest = set()
# Compose list of items we're going to request
for typeID in 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)
# Do not waste our time if all items are not on the market
if len(toRequest) == 0:
return
sFit = Fit.getInstance()
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
sourcesToTry = list(cls.sources.keys())
curr = sFit.serviceFittingOptions["priceSource"] if sFit.serviceFittingOptions["priceSource"] in sourcesToTry else sourcesToTry[0]
while len(sourcesToTry) > 0:
sourcesToTry.remove(curr)
try:
sourceCls = cls.sources.get(curr)
sourceCls(toRequest, cls.systemsList[sFit.serviceFittingOptions["priceSystem"]], priceMap)
break
# 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 Exception as ex:
# something happened, try another source
pyfalog.warn('Failed to fetch prices from price source {}: {}'.format(curr, ex, sourcesToTry[0]))
if len(sourcesToTry) > 0:
pyfalog.warn('Trying {}'.format(sourcesToTry[0]))
curr = sourcesToTry[0]
# if we get to this point, then we've got an error in all of our sources. Set to REREQUEST delay
for typeID in priceMap.keys():
priceobj = priceMap[typeID]
priceobj.time = time.time() + REREQUEST
priceobj.failed = True
@classmethod
def fitItemsList(cls, fit):
# Compose a list of all the data we need & request it
fit_items = [fit.ship.item]
for mod in fit.modules:
if not mod.isEmpty:
fit_items.append(mod.item)
for drone in fit.drones:
fit_items.append(drone.item)
for fighter in fit.fighters:
fit_items.append(fighter.item)
for cargo in fit.cargo:
fit_items.append(cargo.item)
for boosters in fit.boosters:
fit_items.append(boosters.item)
for implants in fit.implants:
fit_items.append(implants.item)
return list(set(fit_items))
def getPriceNow(self, objitem):
"""Get price for provided typeID"""
sMkt = Market.getInstance()
item = sMkt.getItem(objitem)
return item.price.price
def getPrices(self, objitems, callback, waitforthread=False):
"""Get prices for multiple typeIDs"""
requests = []
for objitem in objitems:
sMkt = Market.getInstance()
item = sMkt.getItem(objitem)
requests.append(item.price)
def cb():
try:
callback(requests)
except Exception as e:
pyfalog.critical("Callback failed.")
pyfalog.critical(e)
db.commit()
if waitforthread:
self.priceWorkerThread.setToWait(requests, cb)
else:
self.priceWorkerThread.trigger(requests, cb)
def clearPriceCache(self):
pyfalog.debug("Clearing Prices")
db.clearPrices()
class PriceWorkerThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.name = "PriceWorker"
self.queue = queue.Queue()
self.wait = {}
pyfalog.debug("Initialize PriceWorkerThread.")
def run(self):
queue = self.queue
while True:
# Grab our data
callback, requests = queue.get()
# Grab prices, this is the time-consuming part
if len(requests) > 0:
Price.fetchPrices(requests)
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)
from service.marketSources import evecentral, evemarketdata # noqa: E402