Merge branch 'master' into singularity
This commit is contained in:
@@ -2,5 +2,9 @@
|
||||
# Modules from group: ECM (44 of 44)
|
||||
# Drones named like: EC (3 of 3)
|
||||
type = "projected", "active"
|
||||
def handler(fit, container, context):
|
||||
pass
|
||||
def handler(fit, module, context):
|
||||
if "projected" in context:
|
||||
# jam formula: 1 - (1- (jammer str/ship str))^(# of jam mods with same str))
|
||||
strModifier = 1 - module.getModifiedItemAttr("scan{0}StrengthBonus".format(fit.scanType))/fit.scanStrength
|
||||
|
||||
fit.ecmProjectedStr *= strModifier
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
# Modules from group: ECM Burst (7 of 7)
|
||||
type = "overheat"
|
||||
def handler(fit, module, context):
|
||||
for scanType in ("Gravimetric", "Magnetometric", "Radar", "Ladar"):
|
||||
module.boostItemAttr("scan{0}StrengthBonus".format(scanType),
|
||||
module.getModifiedItemAttr("overloadECMStrengthBonus"),
|
||||
stackingPenalties = True)
|
||||
if "projected" not in context:
|
||||
for scanType in ("Gravimetric", "Magnetometric", "Radar", "Ladar"):
|
||||
module.boostItemAttr("scan{0}StrengthBonus".format(scanType),
|
||||
module.getModifiedItemAttr("overloadECMStrengthBonus"),
|
||||
stackingPenalties = True)
|
||||
|
||||
@@ -195,13 +195,13 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
affs = self.__affectedBy[attributeName]
|
||||
# If there's no set for current fit in dictionary, create it
|
||||
if self.fit not in affs:
|
||||
affs[self.fit] = set()
|
||||
# Reassign alias to set
|
||||
affs[self.fit] = []
|
||||
# Reassign alias to list
|
||||
affs = affs[self.fit]
|
||||
# Get modifier which helps to compose 'Affected by' map
|
||||
modifier = self.fit.getModifier()
|
||||
# Add current affliction to set
|
||||
affs.add((modifier, operation, bonus, used))
|
||||
# Add current affliction to list
|
||||
affs.append((modifier, operation, bonus, used))
|
||||
|
||||
def preAssign(self, attributeName, value):
|
||||
"""Overwrites original value of the entity with given one, allowing further modification"""
|
||||
|
||||
@@ -18,12 +18,9 @@
|
||||
#===============================================================================
|
||||
|
||||
|
||||
import urllib2
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
|
||||
from eos.effectHandlerHelpers import HandledItem
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
import sqlalchemy.orm.exc as exc
|
||||
from eos import eveapi
|
||||
import eos
|
||||
|
||||
class Character(object):
|
||||
@@ -108,27 +105,6 @@ class Character(object):
|
||||
for skill in self.__skills:
|
||||
self.__skillIdMap[skill.itemID] = skill
|
||||
|
||||
def apiCharList(self, proxy=None):
|
||||
api = eveapi.EVEAPIConnection(proxy=proxy)
|
||||
auth = api.auth(keyID=self.apiID, vCode=self.apiKey)
|
||||
apiResult = auth.account.Characters()
|
||||
return map(lambda c: unicode(c.name), apiResult.characters)
|
||||
|
||||
def apiFetch(self, charName, proxy=None):
|
||||
api = eveapi.EVEAPIConnection(proxy=proxy)
|
||||
auth = api.auth(keyID=self.apiID, vCode=self.apiKey)
|
||||
apiResult = auth.account.Characters()
|
||||
charID = None
|
||||
for char in apiResult.characters:
|
||||
if char.name == charName:
|
||||
charID = char.characterID
|
||||
|
||||
if charID == None:
|
||||
return
|
||||
|
||||
sheet = auth.character(charID).CharacterSheet()
|
||||
self.apiUpdateCharSheet(sheet)
|
||||
|
||||
def apiUpdateCharSheet(self, sheet):
|
||||
del self.__skills[:]
|
||||
self.__skillIdMap.clear()
|
||||
|
||||
@@ -65,6 +65,7 @@ class Fit(object):
|
||||
self.boostsFits = set()
|
||||
self.gangBoosts = None
|
||||
self.timestamp = time.time()
|
||||
self.ecmProjectedStr = 1
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
@@ -93,6 +94,7 @@ class Fit(object):
|
||||
self.fleet = None
|
||||
self.boostsFits = set()
|
||||
self.gangBoosts = None
|
||||
self.ecmProjectedStr = 1
|
||||
self.extraAttributes = ModifiedAttributeDict(self)
|
||||
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
|
||||
self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
|
||||
@@ -226,6 +228,10 @@ class Fit(object):
|
||||
|
||||
return type
|
||||
|
||||
@property
|
||||
def jamChance(self):
|
||||
return (1-self.ecmProjectedStr)*100
|
||||
|
||||
@property
|
||||
def alignTime(self):
|
||||
agility = self.ship.getModifiedItemAttr("agility")
|
||||
@@ -269,6 +275,7 @@ class Fit(object):
|
||||
self.__capState = None
|
||||
self.__capUsed = None
|
||||
self.__capRecharge = None
|
||||
self.ecmProjectedStr = 1
|
||||
del self.__calculatedTargets[:]
|
||||
del self.__extraDrains[:]
|
||||
|
||||
@@ -341,7 +348,7 @@ class Fit(object):
|
||||
else:
|
||||
c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules,
|
||||
self.projectedDrones, self.projectedModules)
|
||||
|
||||
|
||||
if self.gangBoosts is not None:
|
||||
contextMap = {Skill: "skill",
|
||||
Ship: "ship",
|
||||
@@ -366,7 +373,7 @@ class Fit(object):
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
for item in c:
|
||||
# Registering the item about to affect the fit allows us to track "Affected By" relations correctly
|
||||
if item is not None:
|
||||
@@ -375,7 +382,7 @@ class Fit(object):
|
||||
if forceProjected is True:
|
||||
targetFit.register(item)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
|
||||
|
||||
for fit in self.projectedFits:
|
||||
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
|
||||
|
||||
@@ -19,18 +19,9 @@
|
||||
#===============================================================================
|
||||
|
||||
import time
|
||||
import urllib2
|
||||
from xml.dom import minidom
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
import eos.db
|
||||
|
||||
class Price(object):
|
||||
# Price validity period, 24 hours
|
||||
VALIDITY = 24*60*60
|
||||
# Re-request delay for failed fetches, 4 hours
|
||||
REREQUEST = 4*60*60
|
||||
|
||||
def __init__(self, typeID):
|
||||
self.typeID = typeID
|
||||
@@ -42,307 +33,6 @@ class Price(object):
|
||||
def init(self):
|
||||
self.__item = None
|
||||
|
||||
def isValid(self, rqtime=time.time()):
|
||||
updateAge = rqtime - self.time
|
||||
# Mark price as invalid if it is expired
|
||||
validity = updateAge <= self.VALIDITY
|
||||
# Price is considered as valid, if it's expired but we had failed
|
||||
# fetch attempt recently
|
||||
if validity is False and self.failed is not None:
|
||||
failedAge = rqtime - self.failed
|
||||
validity = failedAge <= self.REREQUEST
|
||||
# If it's already invalid, it can't get any better
|
||||
if validity is False:
|
||||
return validity
|
||||
# If failed timestamp refers to future relatively to current
|
||||
# system clock, mark price as invalid
|
||||
if self.failed > rqtime:
|
||||
return False
|
||||
# Do the same for last updated timestamp
|
||||
if self.time > rqtime:
|
||||
return False
|
||||
return validity
|
||||
|
||||
@classmethod
|
||||
def fetchPrices(cls, prices, proxy=None):
|
||||
"""Fetch all prices passed to this method"""
|
||||
# Set time of the request
|
||||
# We have to pass this time to all of our used methods and validity checks
|
||||
# Using time of check instead can make extremely rare edge-case bugs to appear
|
||||
# (e.g. when item price is already considered as outdated, but c0rp fetch is still
|
||||
# valid, just because their update time has been set using slightly older timestamp)
|
||||
rqtime = time.time()
|
||||
# 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(rqtime=rqtime):
|
||||
priceMap[price.typeID] = price
|
||||
# List our price service methods
|
||||
services = ((cls.fetchEveCentral, (priceMap,), {"rqtime": rqtime, "proxy": proxy}),
|
||||
(cls.fetchC0rporation, (priceMap,), {"rqtime": rqtime, "proxy": proxy}))
|
||||
# Cycle through services
|
||||
for svc, args, kwargs in services:
|
||||
# Stop cycling if we don't need price data anymore
|
||||
if len(priceMap) == 0:
|
||||
break
|
||||
# Request prices and get some feedback
|
||||
noData, abortedData = svc(*args, **kwargs)
|
||||
# Mark items with some failure occurred during fetching
|
||||
for typeID in abortedData:
|
||||
priceMap[typeID].failed = rqtime
|
||||
# Clear map from the fetched and failed items, leaving only items
|
||||
# for which we've got no data
|
||||
toRemove = set()
|
||||
for typeID in priceMap:
|
||||
if typeID not in noData:
|
||||
toRemove.add(typeID)
|
||||
for typeID in toRemove:
|
||||
del priceMap[typeID]
|
||||
# After we've checked all possible services, assign zero price for items
|
||||
# which were not found on any service to avoid re-fetches during validity
|
||||
# period
|
||||
for typeID in priceMap:
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.price = 0
|
||||
priceobj.time = rqtime
|
||||
priceobj.failed = None
|
||||
|
||||
@classmethod
|
||||
def fetchEveCentral(cls, priceMap, rqtime=time.time(), proxy=None):
|
||||
"""Use Eve-Central price service provider"""
|
||||
# This set will contain typeIDs which were requested but no data has been fetched for them
|
||||
noData = set()
|
||||
# This set will contain items for which data fetch was aborted due to technical reasons
|
||||
abortedData = set()
|
||||
# Set of items which are still to be requested from this service
|
||||
toRequestSvc = set()
|
||||
# Compose list of items we're going to request
|
||||
for typeID in priceMap:
|
||||
# Get item object
|
||||
item = eos.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
|
||||
# Items w/o market group will be added to noData in the very end
|
||||
if item.marketGroupID:
|
||||
toRequestSvc.add(typeID)
|
||||
# Do not waste our time if all items are not on the market
|
||||
if len(toRequestSvc) == 0:
|
||||
noData.update(priceMap.iterkeys())
|
||||
return (noData, abortedData)
|
||||
# This set will contain typeIDs for which we've got useful data
|
||||
fetchedTypeIDs = set()
|
||||
# Base request URL
|
||||
baseurl = "http://api.eve-central.com/api/marketstat"
|
||||
# Area limitation list
|
||||
areas = ("usesystem=30000142", # Jita
|
||||
None) # Global
|
||||
# Fetch prices from Jita market, if no data was available - check global data
|
||||
for area in areas:
|
||||
# Append area limitations to base URL
|
||||
areaurl = "{0}&{1}".format(baseurl, area) if area else baseurl
|
||||
# Set which contains IDs of items which we will fetch for given area
|
||||
toRequestArea = toRequestSvc.difference(fetchedTypeIDs).difference(abortedData)
|
||||
# As length of URL is limited, make a loop to make sure we request all data
|
||||
while(len(toRequestArea) > 0):
|
||||
# Set of items we're requesting during this cycle
|
||||
requestedThisUrl = set()
|
||||
# Always start composing our URL from area-limited URL
|
||||
requrl = areaurl
|
||||
# Generate final URL, making sure it isn't longer than 255 characters
|
||||
for typeID in toRequestArea:
|
||||
# Try to add new typeID argument
|
||||
newrequrl = "{0}&typeid={1}".format(requrl, typeID)
|
||||
# If we didn't exceed our limits
|
||||
if len(newrequrl) <= 255:
|
||||
# Accept new URL
|
||||
requrl = newrequrl
|
||||
# Fill the set for the utility needs
|
||||
requestedThisUrl.add(typeID)
|
||||
# Use previously generated URL if new is out of bounds
|
||||
else:
|
||||
break
|
||||
# Do not request same items from the same area
|
||||
toRequestArea.difference_update(requestedThisUrl)
|
||||
# Replace first ampersand with question mark to separate arguments
|
||||
# from URL itself
|
||||
requrl = requrl.replace("&", "?", 1)
|
||||
# Make the request object
|
||||
request = urllib2.Request(requrl, headers={"User-Agent" : "eos"})
|
||||
# Attempt to send request and process it
|
||||
try:
|
||||
if proxy is not None:
|
||||
proxyHandler = urllib2.ProxyHandler({"http": proxy})
|
||||
opener = urllib2.build_opener(proxyHandler)
|
||||
urllib2.install_opener(opener)
|
||||
data = urllib2.urlopen(request)
|
||||
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):
|
||||
percprice = 0
|
||||
# Eve-central returns zero price if there was no data, thus modify price
|
||||
# object only if we've got non-zero price
|
||||
if percprice:
|
||||
# Add item id to list of fetched items
|
||||
fetchedTypeIDs.add(typeID)
|
||||
# Fill price data
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.price = percprice
|
||||
priceobj.time = rqtime
|
||||
priceobj.failed = None
|
||||
# If getting or processing data returned any errors
|
||||
except:
|
||||
# Consider fetch as aborted
|
||||
abortedData.update(requestedThisUrl)
|
||||
# Get actual list of items for which we didn't get data; it includes all requested items
|
||||
# (even those which didn't pass filter), excluding items which had problems during fetching
|
||||
# and items for which we've successfully fetched price
|
||||
noData.update(set(priceMap.iterkeys()).difference(fetchedTypeIDs).difference(abortedData))
|
||||
# And return it for future use
|
||||
return (noData, abortedData)
|
||||
|
||||
@classmethod
|
||||
def fetchC0rporation(cls, priceMap, rqtime=time.time(), proxy=None):
|
||||
"""Use c0rporation.com price service provider"""
|
||||
# it must be here, otherwise eos doesn't load miscData in time
|
||||
from eos.types import MiscData
|
||||
# Set-container for requested items w/o any data returned
|
||||
noData = set()
|
||||
# Container for items which had errors during fetching
|
||||
abortedData = set()
|
||||
# Set with types for which we've got data
|
||||
fetchedTypeIDs = set()
|
||||
# Container for prices we'll re-request from eve central.
|
||||
eveCentralUpdate = {}
|
||||
# Check when we updated prices last time
|
||||
fieldName = "priceC0rpTime"
|
||||
lastUpdatedField = eos.db.getMiscData(fieldName)
|
||||
# If this field isn't available, create and add it to session
|
||||
if lastUpdatedField is None:
|
||||
lastUpdatedField = MiscData(fieldName)
|
||||
eos.db.add(lastUpdatedField)
|
||||
# Convert field value to float, assigning it zero on any errors
|
||||
try:
|
||||
lastUpdated = float(lastUpdatedField.fieldValue)
|
||||
except (TypeError, ValueError):
|
||||
lastUpdated = 0
|
||||
# Get age of price
|
||||
updateAge = rqtime - lastUpdated
|
||||
# Using timestamp we've got, check if fetch results are still valid and make
|
||||
# sure system clock hasn't been changed to past
|
||||
c0rpValidityUpd = updateAge <= cls.VALIDITY and lastUpdated <= rqtime
|
||||
# If prices should be valid according to miscdata last update timestamp,
|
||||
# but method was requested to provide prices for some items, we can
|
||||
# safely assume that these items are not on the XML (to be more accurate,
|
||||
# on its previously fetched version), because all items which are valid
|
||||
# (and they are valid only when xml is valid) should be filtered out before
|
||||
# passing them to this method
|
||||
if c0rpValidityUpd is True:
|
||||
noData.update(set(priceMap.iterkeys()))
|
||||
return (noData, abortedData)
|
||||
# Check when price fetching failed last time
|
||||
fieldName = "priceC0rpFailed"
|
||||
# If it doesn't exist, add this one to the session too
|
||||
lastFailedField = eos.db.getMiscData(fieldName)
|
||||
if lastFailedField is None:
|
||||
lastFailedField = MiscData(fieldName)
|
||||
eos.db.add(lastFailedField)
|
||||
# Convert field value to float, assigning it none on any errors
|
||||
try:
|
||||
lastFailed = float(lastFailedField.fieldValue)
|
||||
except (TypeError, ValueError):
|
||||
lastFailed = None
|
||||
# If we had failed fetch attempt at some point
|
||||
if lastFailed is not None:
|
||||
failedAge = rqtime - lastFailed
|
||||
# Check if we should refetch data now or not (we do not want to do anything until
|
||||
# refetch timeout is reached or we have failed timestamp referencing some future time)
|
||||
c0rpValidityFail = failedAge <= cls.REREQUEST and lastFailed <= rqtime
|
||||
# If it seems we're not willing to fetch any data
|
||||
if c0rpValidityFail is True:
|
||||
# Consider all requested items as aborted. As we don't store list of items
|
||||
# provided by this service, this will include anything passed to this service,
|
||||
# even items which are usually not included in xml
|
||||
abortedData.update(set(priceMap.iterkeys()))
|
||||
return (noData, abortedData)
|
||||
# Our request URL
|
||||
requrl = "http://prices.c0rporation.com/faction.xml"
|
||||
# Generate request
|
||||
request = urllib2.Request(requrl, headers={"User-Agent" : "eos"})
|
||||
# Attempt to send request and process returned data
|
||||
try:
|
||||
if proxy is not None:
|
||||
proxyHandler = urllib2.ProxyHandler({"http": proxy})
|
||||
opener = urllib2.build_opener(proxyHandler)
|
||||
urllib2.install_opener(opener)
|
||||
data = urllib2.urlopen(request)
|
||||
# Parse the data we've got
|
||||
xml = minidom.parse(data)
|
||||
rowsets = xml.getElementsByTagName("rowset")
|
||||
for rowset in rowsets:
|
||||
rows = rowset.getElementsByTagName("row")
|
||||
# Go through all given data rows; as we don't want to request and process whole xml
|
||||
# for each price request, we need to process it in one single run
|
||||
for row in rows:
|
||||
typeID = int(row.getAttribute("typeID"))
|
||||
# Median price field may be absent or empty, assign 0 in this case
|
||||
try:
|
||||
medprice = float(row.getAttribute("median"))
|
||||
except (TypeError, ValueError):
|
||||
medprice = 0
|
||||
# Process price only if it's non-zero
|
||||
if medprice:
|
||||
# Add current typeID to the set of fetched types
|
||||
fetchedTypeIDs.add(typeID)
|
||||
# If we have given typeID in the map we've got, pull price object out of it
|
||||
if typeID in priceMap:
|
||||
priceobj = priceMap[typeID]
|
||||
# If we don't, request it from database
|
||||
else:
|
||||
priceobj = eos.db.getPrice(typeID)
|
||||
# If everything failed
|
||||
if priceobj is None:
|
||||
# Create price object ourselves
|
||||
priceobj = Price(typeID)
|
||||
# And let database know that we'd like to keep it
|
||||
eos.db.add(priceobj)
|
||||
# Finally, fill object with data
|
||||
priceobj.price = medprice
|
||||
priceobj.time = rqtime
|
||||
priceobj.failed = None
|
||||
# Check if item has market group assigned
|
||||
item = eos.db.getItem(typeID)
|
||||
if item is not None and item.marketGroupID:
|
||||
eveCentralUpdate[typeID] = priceobj
|
||||
# If any items need to be re-requested from EVE-Central, do so
|
||||
# We need to do this because c0rp returns prices for lot of items;
|
||||
# if returned price is one of requested, it's fetched from eve-central
|
||||
# first, which is okay; if it's not, price from c0rp will be written
|
||||
# which will prevent further updates from eve-central. As we consider
|
||||
# eve-central as more accurate source, ask to update prices for all
|
||||
# items we got
|
||||
if eveCentralUpdate:
|
||||
# We do not need any feedback from it, we just want it to update
|
||||
# prices
|
||||
cls.fetchEveCentral(eveCentralUpdate, rqtime=rqtime, proxy=proxy)
|
||||
# Save current time for the future use
|
||||
lastUpdatedField.fieldValue = rqtime
|
||||
# Clear the last failed field
|
||||
lastFailedField.fieldValue = None
|
||||
# Find which items were requested but no data has been returned
|
||||
noData.update(set(priceMap.iterkeys()).difference(fetchedTypeIDs))
|
||||
# If we failed somewhere during fetching or processing
|
||||
except:
|
||||
# Consider all items as aborted
|
||||
abortedData.update(set(priceMap.iterkeys()))
|
||||
# And whole fetch too
|
||||
lastFailedField.fieldValue = rqtime
|
||||
return (noData, abortedData)
|
||||
@property
|
||||
def isValid(self):
|
||||
return self.time >= time.time()
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__all__ = ["moduleAmmoPicker", "itemStats", "damagePattern", "marketJump", "droneSplit", "itemRemove",
|
||||
"droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo"]
|
||||
"droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo", "shipJump"]
|
||||
|
||||
@@ -18,10 +18,10 @@ class ItemRemove(ContextMenu):
|
||||
srcContext = fullContext[0]
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = sFit.getFit(fitID)
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
idx = sFit.drones.index(selection[0])
|
||||
sFit.removeDrone(fitID, idx, numDronesToRemove=sFit.drones[idx].amount)
|
||||
idx = fit.drones.index(selection[0])
|
||||
sFit.removeDrone(fitID, idx, numDronesToRemove=fit.drones[idx].amount)
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
@@ -19,20 +19,20 @@ class ItemRemove(ContextMenu):
|
||||
srcContext = fullContext[0]
|
||||
sFit = service.Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = sFit.getFit(fitID)
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
if srcContext == "fittingModule":
|
||||
for module in selection:
|
||||
if module is not None:
|
||||
sFit.removeModule(fitID,sFit.modules.index(module))
|
||||
sFit.removeModule(fitID,fit.modules.index(module))
|
||||
elif srcContext in ("fittingCharge" , "projectedCharge"):
|
||||
sFit.setAmmo(fitID, None, selection)
|
||||
elif srcContext == "droneItem":
|
||||
sFit.removeDrone(fitID, sFit.drones.index(selection[0]))
|
||||
sFit.removeDrone(fitID, fit.drones.index(selection[0]))
|
||||
elif srcContext == "implantItem":
|
||||
sFit.removeImplant(fitID, sFit.implants.index(selection[0]))
|
||||
sFit.removeImplant(fitID, fit.implants.index(selection[0]))
|
||||
elif srcContext == "boosterItem":
|
||||
sFit.removeBooster(fitID, sFit.boosters.index(selection[0]))
|
||||
sFit.removeBooster(fitID, fit.boosters.index(selection[0]))
|
||||
else:
|
||||
sFit.removeProjected(fitID, selection[0])
|
||||
|
||||
|
||||
29
gui/builtinContextMenus/shipJump.py
Normal file
29
gui/builtinContextMenus/shipJump.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import wx
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import service
|
||||
from gui.shipBrowser import Stage3Selected
|
||||
|
||||
class ShipJump(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
validContexts = ("fittingShip")
|
||||
if not srcContext in validContexts:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Open in Ship Browser"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = service.Fit.getInstance()
|
||||
stuff = sFit.getFit(fitID).ship
|
||||
groupID = stuff.item.group.ID
|
||||
|
||||
self.mainFrame.notebookBrowsers.SetSelection(1)
|
||||
wx.PostEvent(self.mainFrame.shipBrowser,Stage3Selected(shipID=stuff.item.ID, back=groupID))
|
||||
|
||||
ShipJump.register()
|
||||
@@ -1 +1 @@
|
||||
__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaProxyPreferences"]
|
||||
__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences"]
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import wx
|
||||
import service
|
||||
import urllib2
|
||||
|
||||
from gui.preferenceView import PreferenceView
|
||||
from gui import bitmapLoader
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import wx
|
||||
import service
|
||||
import urllib2
|
||||
import os
|
||||
|
||||
from gui.preferenceView import PreferenceView
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
import wx
|
||||
import service
|
||||
import urllib2
|
||||
|
||||
from gui.preferenceView import PreferenceView
|
||||
from gui import bitmapLoader
|
||||
|
||||
import gui.mainFrame
|
||||
import service
|
||||
import gui.globalEvents as GE
|
||||
|
||||
|
||||
class PFProxyPref ( PreferenceView):
|
||||
title = "Proxy"
|
||||
class PFNetworkPref ( PreferenceView):
|
||||
title = "Network"
|
||||
|
||||
def populatePanel( self, panel ):
|
||||
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.proxySettings = service.settings.ProxySettings.getInstance()
|
||||
self.settings = service.settings.NetworkSettings.getInstance()
|
||||
self.network = service.Network.getInstance()
|
||||
self.dirtySettings = False
|
||||
|
||||
self.nMode = self.proxySettings.getMode()
|
||||
self.nAddr = self.proxySettings.getAddress()
|
||||
self.nPort = self.proxySettings.getPort()
|
||||
self.nType = self.proxySettings.getType()
|
||||
|
||||
mainSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
self.stTitle = wx.StaticText( panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
@@ -35,6 +27,49 @@ class PFProxyPref ( PreferenceView):
|
||||
self.m_staticline1 = wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
mainSizer.Add( self.m_staticline1, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5 )
|
||||
|
||||
self.cbEnableNetwork = wx.CheckBox( panel, wx.ID_ANY, u"Enable Network", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
mainSizer.Add( self.cbEnableNetwork, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
subSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
self.cbEve = wx.CheckBox( panel, wx.ID_ANY, u"EVE Servers (API && CREST import)", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
subSizer.Add( self.cbEve, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.cbPricing = wx.CheckBox( panel, wx.ID_ANY, u"Pricing updates", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
subSizer.Add( self.cbPricing, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.cbPyfaUpdate = wx.CheckBox( panel, wx.ID_ANY, u"Pyfa Update checks", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
subSizer.Add( self.cbPyfaUpdate, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
mainSizer.Add( subSizer, 0, wx.LEFT|wx.EXPAND, 30 )
|
||||
|
||||
proxyTitle = wx.StaticText( panel, wx.ID_ANY, "Proxy settings", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
proxyTitle.Wrap( -1 )
|
||||
proxyTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) )
|
||||
|
||||
mainSizer.Add( proxyTitle, 0, wx.ALL, 5 )
|
||||
mainSizer.Add( wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND, 5 )
|
||||
|
||||
self.cbEnableNetwork.SetValue(self.settings.isEnabled(self.network.ENABLED))
|
||||
self.cbEve.SetValue(self.settings.isEnabled(self.network.EVE))
|
||||
self.cbPricing.SetValue(self.settings.isEnabled(self.network.PRICES))
|
||||
self.cbPyfaUpdate.SetValue(self.settings.isEnabled(self.network.UPDATE))
|
||||
|
||||
self.cbEnableNetwork.Bind(wx.EVT_CHECKBOX, self.OnCBEnableChange)
|
||||
self.cbEve.Bind(wx.EVT_CHECKBOX, self.OnCBEveChange)
|
||||
self.cbPricing.Bind(wx.EVT_CHECKBOX, self.OnCBPricingChange)
|
||||
self.cbPyfaUpdate.Bind(wx.EVT_CHECKBOX, self.OnCBUpdateChange)
|
||||
|
||||
self.toggleNetworks(self.cbEnableNetwork.GetValue())
|
||||
|
||||
#---------------
|
||||
# Proxy
|
||||
#---------------
|
||||
|
||||
self.nMode = self.settings.getMode()
|
||||
self.nAddr = self.settings.getAddress()
|
||||
self.nPort = self.settings.getPort()
|
||||
self.nType = self.settings.getType()
|
||||
|
||||
ptypeSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
self.stPType = wx.StaticText( panel, wx.ID_ANY, u"Mode:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
@@ -83,13 +118,13 @@ class PFProxyPref ( PreferenceView):
|
||||
btnSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||
btnSizer.AddSpacer( ( 0, 0), 1, wx.EXPAND, 5 )
|
||||
|
||||
self.btnApply = wx.Button( panel, wx.ID_ANY, u"Apply", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.btnApply = wx.Button( panel, wx.ID_ANY, u"Apply Proxy Settings", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
|
||||
btnSizer.Add( self.btnApply, 0, wx.ALL, 5 )
|
||||
|
||||
mainSizer.Add(btnSizer, 0, wx.EXPAND,5)
|
||||
|
||||
proxy = self.proxySettings.autodetect()
|
||||
proxy = self.settings.autodetect()
|
||||
|
||||
if proxy is not None:
|
||||
addr,port = proxy
|
||||
@@ -117,6 +152,24 @@ class PFProxyPref ( PreferenceView):
|
||||
panel.SetSizer( mainSizer )
|
||||
panel.Layout()
|
||||
|
||||
def toggleNetworks(self, toggle):
|
||||
self.cbEve.Enable(toggle)
|
||||
self.cbPricing.Enable(toggle)
|
||||
self.cbPyfaUpdate.Enable(toggle)
|
||||
|
||||
def OnCBEnableChange(self, event):
|
||||
self.settings.toggleAccess(self.network.ENABLED, self.cbEnableNetwork.GetValue())
|
||||
self.toggleNetworks(self.cbEnableNetwork.GetValue())
|
||||
|
||||
def OnCBUpdateChange(self, event):
|
||||
self.settings.toggleAccess(self.network.UPDATE, self.cbPyfaUpdate.GetValue())
|
||||
|
||||
def OnCBPricingChange(self, event):
|
||||
self.settings.toggleAccess(self.network.PRICES, self.cbPricing.GetValue())
|
||||
|
||||
def OnCBEveChange(self, event):
|
||||
self.settings.toggleAccess(self.network.EVE, self.cbEve.GetValue())
|
||||
|
||||
def OnEditPSAddrText(self, event):
|
||||
self.nAddr = self.editProxySettingsAddr.GetValue()
|
||||
self.dirtySettings = True
|
||||
@@ -133,10 +186,10 @@ class PFProxyPref ( PreferenceView):
|
||||
self.SaveSettings()
|
||||
|
||||
def SaveSettings(self):
|
||||
self.proxySettings.setMode(self.nMode)
|
||||
self.proxySettings.setAddress(self.nAddr)
|
||||
self.proxySettings.setPort(self.nPort)
|
||||
self.proxySettings.setType(self.nType)
|
||||
self.settings.setMode(self.nMode)
|
||||
self.settings.setAddress(self.nAddr)
|
||||
self.settings.setPort(self.nPort)
|
||||
self.settings.setType(self.nType)
|
||||
|
||||
def UpdateApplyButtonState(self):
|
||||
if self.dirtySettings:
|
||||
@@ -172,4 +225,4 @@ class PFProxyPref ( PreferenceView):
|
||||
def getImage(self):
|
||||
return bitmapLoader.getBitmap("prefs_proxy", "icons")
|
||||
|
||||
PFProxyPref.register()
|
||||
PFNetworkPref.register()
|
||||
@@ -12,8 +12,9 @@ import gui.globalEvents as GE
|
||||
class PFUpdatePref (PreferenceView):
|
||||
title = "Updates"
|
||||
desc = "Pyfa can automatically check and notify you of new releases. "+\
|
||||
"These options will allow you to choose what kind of updates, "+\
|
||||
"if any, you wish to receive notifications for."
|
||||
"This feature is toggled in the Network settings. "+\
|
||||
"Here, you may allow pre-release notifications and view "+\
|
||||
"suppressed release notifications, if any."
|
||||
|
||||
def populatePanel( self, panel ):
|
||||
self.UpdateSettings = service.settings.UpdateSettings.getInstance()
|
||||
@@ -35,18 +36,12 @@ class PFUpdatePref (PreferenceView):
|
||||
self.stDesc.Wrap(dlgWidth - 50)
|
||||
mainSizer.Add( self.stDesc, 0, wx.ALL, 5 )
|
||||
|
||||
self.suppressAll = wx.CheckBox( panel, wx.ID_ANY, u"Don't check for updates", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.suppressPrerelease = wx.CheckBox( panel, wx.ID_ANY, u"Allow pre-release notifications", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
|
||||
mainSizer.Add( self.suppressAll, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
mainSizer.Add( self.suppressPrerelease, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.suppressAll.Bind(wx.EVT_CHECKBOX, self.OnSuppressAllStateChange)
|
||||
self.suppressPrerelease.Bind(wx.EVT_CHECKBOX, self.OnPrereleaseStateChange)
|
||||
|
||||
self.suppressAll.SetValue(self.UpdateSettings.get('all'))
|
||||
self.suppressPrerelease.SetValue(not self.UpdateSettings.get('prerelease'))
|
||||
|
||||
mainSizer.Add( self.suppressPrerelease, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
if (self.UpdateSettings.get('version')):
|
||||
self.versionSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
@@ -83,22 +78,9 @@ class PFUpdatePref (PreferenceView):
|
||||
self.versionSizer.Add( actionSizer, 0, wx.EXPAND, 5 )
|
||||
mainSizer.Add( self.versionSizer, 0, wx.EXPAND, 5 )
|
||||
|
||||
self.ToggleSuppressAll(self.suppressAll.IsChecked())
|
||||
|
||||
panel.SetSizer( mainSizer )
|
||||
panel.Layout()
|
||||
|
||||
def ToggleSuppressAll(self, bool):
|
||||
''' Toggles other inputs on/off depending on value of SuppressAll '''
|
||||
if bool:
|
||||
self.suppressPrerelease.Disable()
|
||||
else:
|
||||
self.suppressPrerelease.Enable()
|
||||
|
||||
def OnSuppressAllStateChange(self, event):
|
||||
self.UpdateSettings.set('all', self.suppressAll.IsChecked())
|
||||
self.ToggleSuppressAll(self.suppressAll.IsChecked())
|
||||
|
||||
def OnPrereleaseStateChange(self, event):
|
||||
self.UpdateSettings.set('prerelease', not self.suppressPrerelease.IsChecked())
|
||||
|
||||
@@ -119,4 +101,4 @@ class PFUpdatePref (PreferenceView):
|
||||
def getImage(self):
|
||||
return bitmapLoader.getBitmap("prefs_update", "icons")
|
||||
|
||||
PFUpdatePref.register()
|
||||
PFUpdatePref.register()
|
||||
|
||||
@@ -189,12 +189,15 @@ class TargetingMiscViewFull(StatsView):
|
||||
right = "%s [%d]" % (size, radius)
|
||||
lockTime += "%5s\t%s\n" % (left,right)
|
||||
label.SetToolTip(wx.ToolTip(lockTime))
|
||||
elif labelName == "labelSensorStr":
|
||||
label.SetToolTip(wx.ToolTip("Type: %s - %.1f" % (fit.scanType, mainValue)))
|
||||
elif labelName == "labelFullSigRadius":
|
||||
label.SetToolTip(wx.ToolTip("Probe Size: %.3f" % (fit.probeSize or 0) ))
|
||||
elif labelName == "labelFullWarpSpeed":
|
||||
label.SetToolTip(wx.ToolTip("Max Warp Distance: %.1f AU" % fit.maxWarpDistance))
|
||||
elif labelName == "labelSensorStr":
|
||||
if fit.jamChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s" % (fit.scanType)))
|
||||
elif labelName == "labelFullAlignTime":
|
||||
label.SetToolTip(wx.ToolTip("%.3f" % mainValue))
|
||||
elif labelName == "labelFullCargo":
|
||||
@@ -214,6 +217,14 @@ class TargetingMiscViewFull(StatsView):
|
||||
label.SetToolTip(wx.ToolTip("Max Warp Distance: %.1f AU" % fit.maxWarpDistance))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip(""))
|
||||
elif labelName == "labelSensorStr":
|
||||
if fit:
|
||||
if fit.jamChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s" % (fit.scanType)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip(""))
|
||||
elif labelName == "labelFullCargo":
|
||||
if fit:
|
||||
cachedCargo = self._cachedValues[counter]
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#===============================================================================
|
||||
|
||||
import wx
|
||||
import bitmapLoader
|
||||
|
||||
import gui.mainFrame
|
||||
import wx.lib.newevent
|
||||
import wx.gizmos
|
||||
@@ -27,7 +27,6 @@ import service
|
||||
import gui.display as d
|
||||
from gui.contextMenu import ContextMenu
|
||||
from wx.lib.buttons import GenBitmapButton
|
||||
import sys
|
||||
import gui.globalEvents as GE
|
||||
|
||||
class CharacterEditor(wx.Frame):
|
||||
@@ -638,22 +637,25 @@ class APIView (wx.Panel):
|
||||
return
|
||||
|
||||
sChar = service.Character.getInstance()
|
||||
list = sChar.charList(self.Parent.Parent.getActiveCharacter(), self.inputID.GetLineText(0), self.inputKey.GetLineText(0))
|
||||
try:
|
||||
list = sChar.charList(self.Parent.Parent.getActiveCharacter(), self.inputID.GetLineText(0), self.inputKey.GetLineText(0))
|
||||
except service.network.AuthenticationError, e:
|
||||
self.stStatus.SetLabel("Authentication failure. Please check keyID and vCode combination.")
|
||||
except service.network.TimeoutError, e:
|
||||
self.stStatus.SetLabel("Request timed out. Please check network connectivity and/or proxy settings.")
|
||||
except Exception, e:
|
||||
self.stStatus.SetLabel("Error:\n%s"%e.message)
|
||||
else:
|
||||
self.charChoice.Clear()
|
||||
for charName in list:
|
||||
i = self.charChoice.Append(charName)
|
||||
|
||||
if not list:
|
||||
self.stStatus.SetLabel("Unable to fetch characters list from EVE API!")
|
||||
return
|
||||
self.btnFetchSkills.Enable(True)
|
||||
self.charChoice.Enable(True)
|
||||
|
||||
self.charChoice.Clear()
|
||||
for charName in list:
|
||||
i = self.charChoice.Append(charName)
|
||||
self.Layout()
|
||||
|
||||
self.btnFetchSkills.Enable(True)
|
||||
self.charChoice.Enable(True)
|
||||
|
||||
self.Layout()
|
||||
|
||||
self.charChoice.SetSelection(0)
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
def fetchSkills(self, event):
|
||||
charName = self.charChoice.GetString(self.charChoice.GetSelection())
|
||||
@@ -662,5 +664,5 @@ class APIView (wx.Panel):
|
||||
sChar = service.Character.getInstance()
|
||||
sChar.apiFetch(self.Parent.Parent.getActiveCharacter(), charName)
|
||||
self.stStatus.SetLabel("Successfully fetched %s\'s skills from EVE API." % charName)
|
||||
except:
|
||||
self.stStatus.SetLabel("Unable to retrieve %s\'s skills!" % charName)
|
||||
except Exception, e:
|
||||
self.stStatus.SetLabel("Unable to retrieve %s\'s skills. Error message:\n%s" % (charName, e))
|
||||
|
||||
@@ -35,12 +35,24 @@ except ImportError:
|
||||
|
||||
class ItemStatsDialog(wx.Dialog):
|
||||
counter = 0
|
||||
def __init__(self, victim, fullContext=None, pos = wx.DefaultPosition, size = wx.DefaultSize, maximized = False):
|
||||
wx.Dialog.__init__(self,
|
||||
gui.mainFrame.MainFrame.getInstance(),
|
||||
wx.ID_ANY, title="Item stats", pos = pos, size = size,
|
||||
style = wx.CAPTION | wx.CLOSE_BOX | wx.MINIMIZE_BOX |
|
||||
wx.MAXIMIZE_BOX | wx.RESIZE_BORDER| wx.SYSTEM_MENU)
|
||||
def __init__(
|
||||
self,
|
||||
victim,
|
||||
fullContext=None,
|
||||
pos=wx.DefaultPosition,
|
||||
size=wx.DefaultSize,
|
||||
maximized = False
|
||||
):
|
||||
|
||||
wx.Dialog.__init__(
|
||||
self,
|
||||
gui.mainFrame.MainFrame.getInstance(),
|
||||
wx.ID_ANY,
|
||||
title="Item stats",
|
||||
pos=pos,
|
||||
size=size,
|
||||
style=wx.CAPTION | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER| wx.SYSTEM_MENU
|
||||
)
|
||||
|
||||
empty = getattr(victim, "isEmpty", False)
|
||||
|
||||
@@ -67,10 +79,13 @@ class ItemStatsDialog(wx.Dialog):
|
||||
if itemImg is not None:
|
||||
self.SetIcon(wx.IconFromBitmap(itemImg))
|
||||
self.SetTitle("%s: %s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name))
|
||||
|
||||
|
||||
self.SetMinSize((300, 200))
|
||||
self.SetSize((500, 300))
|
||||
self.SetMaxSize((500, -1))
|
||||
if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room
|
||||
self.SetSize((530, 300))
|
||||
else:
|
||||
self.SetSize((500, 300))
|
||||
#self.SetMaxSize((500, -1))
|
||||
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.container = ItemStatsContainer(self, victim, item, itmContext)
|
||||
self.mainSizer.Add(self.container, 1, wx.EXPAND)
|
||||
@@ -281,6 +296,17 @@ class ItemParams (wx.Panel):
|
||||
self.toggleView = 1
|
||||
self.stuff = stuff
|
||||
self.item = item
|
||||
self.attrInfo = {}
|
||||
self.attrValues = {}
|
||||
if self.stuff is None:
|
||||
self.attrInfo.update(self.item.attributes)
|
||||
self.attrValues.update(self.item.attributes)
|
||||
elif self.stuff.item == self.item:
|
||||
self.attrInfo.update(self.stuff.item.attributes)
|
||||
self.attrValues.update(self.stuff.itemModifiedAttributes)
|
||||
else:
|
||||
self.attrInfo.update(self.stuff.charge.attributes)
|
||||
self.attrValues.update(self.stuff.chargeModifiedAttributes)
|
||||
|
||||
self.m_staticline = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
mainSizer.Add( self.m_staticline, 0, wx.EXPAND)
|
||||
@@ -326,23 +352,17 @@ class ItemParams (wx.Panel):
|
||||
self.paramList.setResizeColumn(1)
|
||||
self.imageList = wx.ImageList(16, 16)
|
||||
self.paramList.SetImageList(self.imageList,wx.IMAGE_LIST_SMALL)
|
||||
if self.stuff is None or self.stuff.item == self.item:
|
||||
attrs = self.stuff.itemModifiedAttributes if self.stuff is not None else self.item.attributes
|
||||
attrsInfo = self.item.attributes if self.stuff is None else self.stuff.item.attributes
|
||||
else:
|
||||
attrs = self.stuff.chargeModifiedAttributes if self.stuff is not None else self.item.attributes
|
||||
attrsInfo = self.item.attributes if self.stuff is None else self.stuff.charge.attributes
|
||||
|
||||
names = list(attrs.iterkeys())
|
||||
names = list(self.attrValues.iterkeys())
|
||||
names.sort()
|
||||
|
||||
idNameMap = {}
|
||||
idCount = 0
|
||||
for name in names:
|
||||
info = attrsInfo.get(name)
|
||||
info = self.attrInfo.get(name)
|
||||
|
||||
|
||||
att = attrs[name]
|
||||
att = self.attrValues[name]
|
||||
val = getattr(att, "value", None)
|
||||
value = val if val is not None else att
|
||||
|
||||
@@ -612,7 +632,6 @@ class ItemAffectedBy (wx.Panel):
|
||||
self.imageList = wx.ImageList(16, 16)
|
||||
self.affectedBy.SetImageList(self.imageList)
|
||||
|
||||
|
||||
cont = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
|
||||
things = {}
|
||||
|
||||
@@ -620,20 +639,26 @@ class ItemAffectedBy (wx.Panel):
|
||||
# if value is 0 or there has been no change from original to modified, return
|
||||
if cont[attrName] == (cont.getOriginal(attrName) or 0):
|
||||
continue
|
||||
|
||||
for fit, afflictors in cont.getAfflictions(attrName).iteritems():
|
||||
for afflictor, modifier, amount, used in afflictors:
|
||||
if not used or afflictor.item is None:
|
||||
continue
|
||||
|
||||
if afflictor.item.name not in things:
|
||||
things[afflictor.item.name] = [type(afflictor), set(), set()]
|
||||
things[afflictor.item.name] = [type(afflictor), set(), []]
|
||||
|
||||
info = things[afflictor.item.name]
|
||||
info[1].add(afflictor)
|
||||
info[2].add((attrName, modifier, amount))
|
||||
# If info[1] > 1, there are two separate modules working.
|
||||
# Check to make sure we only include the modifier once
|
||||
# See GH issue 154
|
||||
if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]:
|
||||
continue
|
||||
info[2].append((attrName, modifier, amount))
|
||||
|
||||
order = things.keys()
|
||||
order.sort(key=lambda x: (self.ORDER.index(things[x][0]), x))
|
||||
|
||||
for itemName in order:
|
||||
info = things[itemName]
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
def OnHistoryReset(self):
|
||||
if self.shipBrowser.browseHist:
|
||||
self.shipBrowser.browseHist = []
|
||||
self.gotoStage(1,0)
|
||||
self.gotoStage(1,0)
|
||||
|
||||
def OnHistoryBack(self):
|
||||
if len(self.shipBrowser.browseHist) > 0:
|
||||
@@ -671,6 +671,7 @@ class ShipBrowser(wx.Panel):
|
||||
self._lastStage = self._activeStage
|
||||
self._activeStage = 1
|
||||
self.lastdata = 0
|
||||
self.browseHist = [(1,0)]
|
||||
|
||||
self.navpanel.ShowNewFitButton(False)
|
||||
self.navpanel.ShowSwitchEmptyGroupsButton(False)
|
||||
@@ -774,8 +775,8 @@ class ShipBrowser(wx.Panel):
|
||||
def stage2(self, event):
|
||||
back = event.back
|
||||
|
||||
if not back:
|
||||
self.browseHist.append( (1,0) )
|
||||
#if not back:
|
||||
# self.browseHist.append( (1,0) )
|
||||
|
||||
self._lastStage = self._activeStage
|
||||
self._activeStage = 2
|
||||
@@ -807,6 +808,8 @@ class ShipBrowser(wx.Panel):
|
||||
elif event.back == -1:
|
||||
if len(self.navpanel.recentSearches)>0:
|
||||
self.browseHist.append((4, self.navpanel.lastSearch))
|
||||
elif event.back > 0:
|
||||
self.browseHist.append( (2,event.back) )
|
||||
|
||||
shipID = event.shipID
|
||||
self.lastdata = shipID
|
||||
|
||||
@@ -35,9 +35,9 @@ class exportHtmlThread(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
# wait 1 second just in case a lot of modifications get made
|
||||
time.sleep(1);
|
||||
time.sleep(1)
|
||||
if self.stopRunning:
|
||||
return;
|
||||
return
|
||||
|
||||
sMkt = service.Market.getInstance()
|
||||
sFit = service.Fit.getInstance()
|
||||
@@ -190,8 +190,8 @@ class exportHtmlThread(threading.Thread):
|
||||
|
||||
try:
|
||||
FILE = open(settings.getPath(), "w")
|
||||
FILE.write(HTML.encode('utf-8'));
|
||||
FILE.close();
|
||||
FILE.write(HTML.encode('utf-8'))
|
||||
FILE.close()
|
||||
except IOError:
|
||||
print "Failed to write to " + settings.getPath()
|
||||
pass
|
||||
|
||||
@@ -6,3 +6,6 @@ from service.damagePattern import DamagePattern
|
||||
from service.settings import SettingsProvider
|
||||
from service.fleet import Fleet
|
||||
from service.update import Update
|
||||
from service.price import Price
|
||||
from service.network import Network
|
||||
from service.eveapi import EVEAPIConnection, ParseXML
|
||||
|
||||
@@ -17,25 +17,23 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
import eos.db
|
||||
import eos.types
|
||||
import copy
|
||||
import service
|
||||
import itertools
|
||||
from eos import eveapi
|
||||
import config
|
||||
import json
|
||||
import os.path
|
||||
import locale
|
||||
import threading
|
||||
import wx
|
||||
from codecs import open
|
||||
|
||||
from xml.etree import ElementTree
|
||||
from xml.dom import minidom
|
||||
|
||||
import gzip
|
||||
|
||||
import wx
|
||||
|
||||
import eos.db
|
||||
import eos.types
|
||||
import service
|
||||
import config
|
||||
|
||||
|
||||
class CharacterImportThread(threading.Thread):
|
||||
def __init__(self, paths, callback):
|
||||
threading.Thread.__init__(self)
|
||||
@@ -47,7 +45,7 @@ class CharacterImportThread(threading.Thread):
|
||||
sCharacter = Character.getInstance()
|
||||
for path in paths:
|
||||
with open(path, mode='r') as charFile:
|
||||
sheet = eveapi.ParseXML(charFile)
|
||||
sheet = service.ParseXML(charFile)
|
||||
charID = sCharacter.new()
|
||||
sCharacter.rename(charID, sheet.name+" (imported)")
|
||||
sCharacter.apiUpdateCharSheet(charID, sheet)
|
||||
@@ -67,7 +65,7 @@ class SkillBackupThread(threading.Thread):
|
||||
sCharacter = Character.getInstance()
|
||||
sFit = service.Fit.getInstance()
|
||||
fit = sFit.getFit(self.activeFit)
|
||||
backupData = "";
|
||||
backupData = ""
|
||||
if self.saveFmt == "xml" or self.saveFmt == "emp":
|
||||
backupData = sCharacter.exportXml()
|
||||
else:
|
||||
@@ -240,19 +238,36 @@ class Character(object):
|
||||
|
||||
def charList(self, charID, userID, apiKey):
|
||||
char = eos.db.getCharacter(charID)
|
||||
try:
|
||||
char.apiID = userID
|
||||
char.apiKey = apiKey
|
||||
charList = char.apiCharList(proxy = service.settings.ProxySettings.getInstance().getProxySettings())
|
||||
char.chars = json.dumps(charList)
|
||||
return charList
|
||||
except:
|
||||
return None
|
||||
|
||||
char.apiID = userID
|
||||
char.apiKey = apiKey
|
||||
|
||||
api = service.EVEAPIConnection()
|
||||
auth = api.auth(keyID=userID, vCode=apiKey)
|
||||
apiResult = auth.account.Characters()
|
||||
charList = map(lambda c: unicode(c.name), apiResult.characters)
|
||||
|
||||
char.chars = json.dumps(charList)
|
||||
return charList
|
||||
|
||||
def apiFetch(self, charID, charName):
|
||||
char = eos.db.getCharacter(charID)
|
||||
char.defaultChar = charName
|
||||
char.apiFetch(charName, proxy = service.settings.ProxySettings.getInstance().getProxySettings())
|
||||
dbChar = eos.db.getCharacter(charID)
|
||||
dbChar.defaultChar = charName
|
||||
|
||||
api = service.EVEAPIConnection()
|
||||
auth = api.auth(keyID=dbChar.apiID, vCode=dbChar.apiKey)
|
||||
apiResult = auth.account.Characters()
|
||||
charID = None
|
||||
for char in apiResult.characters:
|
||||
if char.name == charName:
|
||||
charID = char.characterID
|
||||
|
||||
if charID == None:
|
||||
return
|
||||
|
||||
sheet = auth.character(charID).CharacterSheet()
|
||||
|
||||
dbChar.apiUpdateCharSheet(sheet)
|
||||
eos.db.commit()
|
||||
|
||||
def apiUpdateCharSheet(self, charID, sheet):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# eveapi - EVE Online API access
|
||||
#
|
||||
# Copyright (c)2007 Jamie "Entity" van den Berge <entity@vapor.com>
|
||||
# Copyright (c)2007-2014 Jamie "Entity" van den Berge <jamie@hlekkir.com>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation
|
||||
@@ -25,8 +25,47 @@
|
||||
# OTHER DEALINGS IN THE SOFTWARE
|
||||
#
|
||||
#-----------------------------------------------------------------------------
|
||||
# Version: 1.1.9-2 - 30 September 2011
|
||||
# - merge workaround provided by Entity to make it work with http proxies
|
||||
#
|
||||
# Version: 1.3.0 - 27 May 2014
|
||||
# - Added set_user_agent() module-level function to set the User-Agent header
|
||||
# to be used for any requests by the library. If this function is not used,
|
||||
# a warning will be thrown for every API request.
|
||||
#
|
||||
# Version: 1.2.9 - 14 September 2013
|
||||
# - Updated error handling: Raise an AuthenticationError in case
|
||||
# the API returns HTTP Status Code 403 - Forbidden
|
||||
#
|
||||
# Version: 1.2.8 - 9 August 2013
|
||||
# - the XML value cast function (_autocast) can now be changed globally to a
|
||||
# custom one using the set_cast_func(func) module-level function.
|
||||
#
|
||||
# Version: 1.2.7 - 3 September 2012
|
||||
# - Added get() method to Row object.
|
||||
#
|
||||
# Version: 1.2.6 - 29 August 2012
|
||||
# - Added finer error handling + added setup.py to allow distributing eveapi
|
||||
# through pypi.
|
||||
#
|
||||
# Version: 1.2.5 - 1 August 2012
|
||||
# - Row objects now have __hasattr__ and __contains__ methods
|
||||
#
|
||||
# Version: 1.2.4 - 12 April 2012
|
||||
# - API version of XML response now available as _meta.version
|
||||
#
|
||||
# Version: 1.2.3 - 10 April 2012
|
||||
# - fix for tags of the form <tag attr=bla ... />
|
||||
#
|
||||
# Version: 1.2.2 - 27 February 2012
|
||||
# - fix for the workaround in 1.2.1.
|
||||
#
|
||||
# Version: 1.2.1 - 23 February 2012
|
||||
# - added workaround for row tags missing attributes that were defined
|
||||
# in their rowset (this should fix ContractItems)
|
||||
#
|
||||
# Version: 1.2.0 - 18 February 2012
|
||||
# - fix handling of empty XML tags.
|
||||
# - improved proxy support a bit.
|
||||
#
|
||||
# Version: 1.1.9 - 2 September 2011
|
||||
# - added workaround for row tags with attributes that were not defined
|
||||
# in their rowset (this should fix AssetList)
|
||||
@@ -108,26 +147,68 @@
|
||||
#
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import httplib
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# This eveapi has been modified for pyfa.
|
||||
#
|
||||
# Specifically, the entire network request/response has been substituted for
|
||||
# pyfa's own implementation in service.network
|
||||
#
|
||||
# Additionally, various other parts have been changed to support urllib2
|
||||
# responses instead of httplib
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
import urlparse
|
||||
import urllib
|
||||
import copy
|
||||
|
||||
from xml.parsers import expat
|
||||
from time import strptime
|
||||
from calendar import timegm
|
||||
|
||||
import service
|
||||
|
||||
proxy = None
|
||||
proxySSL = False
|
||||
|
||||
_default_useragent = "eveapi.py/1.3"
|
||||
_useragent = None # use set_user_agent() to set this.
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def set_cast_func(func):
|
||||
"""Sets an alternative value casting function for the XML parser.
|
||||
The function must have 2 arguments; key and value. It should return a
|
||||
value or object of the type appropriate for the given attribute name/key.
|
||||
func may be None and will cause the default _autocast function to be used.
|
||||
"""
|
||||
global _castfunc
|
||||
_castfunc = _autocast if func is None else func
|
||||
|
||||
def set_user_agent(user_agent_string):
|
||||
"""Sets a User-Agent for any requests sent by the library."""
|
||||
global _useragent
|
||||
_useragent = user_agent_string
|
||||
|
||||
|
||||
class Error(StandardError):
|
||||
def __init__(self, code, message):
|
||||
self.code = code
|
||||
self.args = (message.rstrip("."),)
|
||||
def __unicode__(self):
|
||||
return u'%s [code=%s]' % (self.args[0], self.code)
|
||||
|
||||
class RequestError(Error):
|
||||
pass
|
||||
|
||||
class AuthenticationError(Error):
|
||||
pass
|
||||
|
||||
class ServerError(Error):
|
||||
pass
|
||||
|
||||
|
||||
def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None):
|
||||
def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None, proxySSL=False):
|
||||
# Creates an API object through which you can call remote functions.
|
||||
#
|
||||
# The following optional arguments may be provided:
|
||||
@@ -137,6 +218,8 @@ def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None):
|
||||
# proxy - (host,port) specifying a proxy server through which to request
|
||||
# the API pages. Specifying a proxy overrides default proxy.
|
||||
#
|
||||
# proxySSL - True if the proxy requires SSL, False otherwise.
|
||||
#
|
||||
# cacheHandler - an object which must support the following interface:
|
||||
#
|
||||
# retrieve(host, path, params)
|
||||
@@ -173,6 +256,7 @@ def EVEAPIConnection(url="api.eveonline.com", cacheHandler=None, proxy=None):
|
||||
ctx._scheme = p.scheme
|
||||
ctx._host = p.netloc
|
||||
ctx._proxy = proxy or globals()["proxy"]
|
||||
ctx._proxySSL = proxySSL or globals()["proxySSL"]
|
||||
return ctx
|
||||
|
||||
|
||||
@@ -197,7 +281,14 @@ def _ParseXML(response, fromContext, storeFunc):
|
||||
|
||||
error = getattr(obj, "error", False)
|
||||
if error:
|
||||
raise Error(error.code, error.data)
|
||||
if error.code >= 500:
|
||||
raise ServerError(error.code, error.data)
|
||||
elif error.code >= 200:
|
||||
raise AuthenticationError(error.code, error.data)
|
||||
elif error.code >= 100:
|
||||
raise RequestError(error.code, error.data)
|
||||
else:
|
||||
raise Error(error.code, error.data)
|
||||
|
||||
result = getattr(obj, "result", False)
|
||||
if not result:
|
||||
@@ -304,36 +395,11 @@ class _RootContext(_Context):
|
||||
response = None
|
||||
|
||||
if response is None:
|
||||
if self._scheme == "https":
|
||||
connectionclass = httplib.HTTPSConnection
|
||||
else:
|
||||
connectionclass = httplib.HTTPConnection
|
||||
network = service.Network.getInstance()
|
||||
|
||||
if self._proxy is None:
|
||||
if self._scheme == "https":
|
||||
connectionclass = httplib.HTTPSConnection
|
||||
else:
|
||||
connectionclass = httplib.HTTPConnection
|
||||
req = self._scheme+'://'+self._host+path
|
||||
|
||||
http = connectionclass(self._host)
|
||||
if kw:
|
||||
http.request("POST", path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})
|
||||
else:
|
||||
http.request("GET", path)
|
||||
else:
|
||||
connectionclass = httplib.HTTPConnection
|
||||
http = connectionclass(*self._proxy)
|
||||
if kw:
|
||||
http.request("POST", self._scheme+'://'+self._host+path, urllib.urlencode(kw), {"Content-type": "application/x-www-form-urlencoded"})
|
||||
else:
|
||||
http.request("GET", self._scheme+'://'+self._host+path)
|
||||
|
||||
response = http.getresponse()
|
||||
if response.status != 200:
|
||||
if response.status == httplib.NOT_FOUND:
|
||||
raise AttributeError("'%s' not available on API server (404 Not Found)" % path)
|
||||
else:
|
||||
raise RuntimeError("'%s' request failed (%d %s)" % (path, response.status, response.reason))
|
||||
response = network.request(req, network.EVE, kw)
|
||||
|
||||
if cache:
|
||||
store = True
|
||||
@@ -348,7 +414,7 @@ class _RootContext(_Context):
|
||||
# implementor is handling fallbacks...
|
||||
try:
|
||||
return _ParseXML(response, True, store and (lambda obj: cache.store(self._host, path, kw, response, obj)))
|
||||
except Error, reason:
|
||||
except Error, e:
|
||||
response = retrieve_fallback(self._host, path, kw, reason=e)
|
||||
if response is not None:
|
||||
return response
|
||||
@@ -386,6 +452,7 @@ def _autocast(key, value):
|
||||
# couldn't cast. return string unchanged.
|
||||
return value
|
||||
|
||||
_castfunc = _autocast
|
||||
|
||||
|
||||
class _Parser(object):
|
||||
@@ -460,20 +527,42 @@ class _Parser(object):
|
||||
# really assume the rest of the xml is going to be what we expect.
|
||||
if name != "eveapi":
|
||||
raise RuntimeError("Invalid API response")
|
||||
try:
|
||||
this.version = attributes[attributes.index("version")+1]
|
||||
except KeyError:
|
||||
raise RuntimeError("Invalid API response")
|
||||
self.root = this
|
||||
|
||||
if isinstance(self.container, Rowset) and (self.container.__catch == this._name):
|
||||
# <hack>
|
||||
# - check for missing columns attribute (see above)
|
||||
# - check for missing columns attribute (see above).
|
||||
# - check for missing row attributes.
|
||||
# - check for extra attributes that were not defined in the rowset,
|
||||
# such as rawQuantity in the assets lists.
|
||||
# In either case the tag is assumed to be correct and the rowset's
|
||||
# columns are overwritten with the tag's version.
|
||||
if not self.container._cols or (len(attributes)/2 > len(self.container._cols)):
|
||||
self.container._cols = attributes[0::2]
|
||||
# columns are overwritten with the tag's version, if required.
|
||||
numAttr = len(attributes)/2
|
||||
numCols = len(self.container._cols)
|
||||
if numAttr < numCols and (attributes[-2] == self.container._cols[-1]):
|
||||
# the row data is missing attributes that were defined in the rowset.
|
||||
# missing attributes' values will be set to None.
|
||||
fixed = []
|
||||
row_idx = 0; hdr_idx = 0; numAttr*=2
|
||||
for col in self.container._cols:
|
||||
if col == attributes[row_idx]:
|
||||
fixed.append(_castfunc(col, attributes[row_idx+1]))
|
||||
row_idx += 2
|
||||
else:
|
||||
fixed.append(None)
|
||||
hdr_idx += 1
|
||||
self.container.append(fixed)
|
||||
else:
|
||||
if not self.container._cols or (numAttr > numCols):
|
||||
# the row data contains more attributes than were defined.
|
||||
self.container._cols = attributes[0::2]
|
||||
self.container.append([_castfunc(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)])
|
||||
# </hack>
|
||||
|
||||
self.container.append([_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)])
|
||||
this._isrow = True
|
||||
this._attributes = this._attributes2 = None
|
||||
else:
|
||||
@@ -481,10 +570,11 @@ class _Parser(object):
|
||||
this._attributes = attributes
|
||||
this._attributes2 = []
|
||||
|
||||
self.container = this
|
||||
|
||||
self.container = self._last = this
|
||||
self.has_cdata = False
|
||||
|
||||
def tag_cdata(self, data):
|
||||
self.has_cdata = True
|
||||
if self._cdata:
|
||||
# unset cdata flag to indicate it's been handled.
|
||||
self._cdata = False
|
||||
@@ -493,7 +583,7 @@ class _Parser(object):
|
||||
return
|
||||
|
||||
this = self.container
|
||||
data = _autocast(this._name, data)
|
||||
data = _castfunc(this._name, data)
|
||||
|
||||
if this._isrow:
|
||||
# sigh. anonymous data inside rows makes Entity cry.
|
||||
@@ -518,6 +608,7 @@ class _Parser(object):
|
||||
|
||||
def tag_end(self, name):
|
||||
this = self.container
|
||||
|
||||
if this is self.root:
|
||||
del this._attributes
|
||||
#this.__dict__.pop("_attributes", None)
|
||||
@@ -553,13 +644,26 @@ class _Parser(object):
|
||||
# really happen, but it doesn't hurt to handle this case!
|
||||
sibling = getattr(self.container, this._name, None)
|
||||
if sibling is None:
|
||||
self.container._attributes2.append(this._name)
|
||||
setattr(self.container, this._name, this)
|
||||
if (not self.has_cdata) and (self._last is this) and (name != "rowset"):
|
||||
if attributes:
|
||||
# tag of the form <tag attribute=bla ... />
|
||||
e = Element()
|
||||
e._name = this._name
|
||||
setattr(self.container, this._name, e)
|
||||
for i in xrange(0, len(attributes), 2):
|
||||
setattr(e, attributes[i], attributes[i+1])
|
||||
else:
|
||||
# tag of the form: <tag />, treat as empty string.
|
||||
setattr(self.container, this._name, "")
|
||||
else:
|
||||
self.container._attributes2.append(this._name)
|
||||
setattr(self.container, this._name, this)
|
||||
|
||||
# Note: there aren't supposed to be any NON-rowset tags containing
|
||||
# multiples of some tag or attribute. Code below handles this case.
|
||||
elif isinstance(sibling, Rowset):
|
||||
# its doppelganger is a rowset, append this as a row to that.
|
||||
row = [_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]
|
||||
row = [_castfunc(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]
|
||||
row.extend([getattr(this, col) for col in attributes2])
|
||||
sibling.append(row)
|
||||
elif isinstance(sibling, Element):
|
||||
@@ -568,7 +672,7 @@ class _Parser(object):
|
||||
# into a Rowset, adding the sibling element and this one.
|
||||
rs = Rowset()
|
||||
rs.__catch = rs._name = this._name
|
||||
row = [_autocast(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]+[getattr(this, col) for col in attributes2]
|
||||
row = [_castfunc(attributes[i], attributes[i+1]) for i in xrange(0, len(attributes), 2)]+[getattr(this, col) for col in attributes2]
|
||||
rs.append(row)
|
||||
row = [getattr(sibling, attributes[i]) for i in xrange(0, len(attributes), 2)]+[getattr(sibling, col) for col in attributes2]
|
||||
rs.append(row)
|
||||
@@ -581,7 +685,7 @@ class _Parser(object):
|
||||
|
||||
# Now fix up the attributes and be done with it.
|
||||
for i in xrange(0, len(attributes), 2):
|
||||
this.__dict__[attributes[i]] = _autocast(attributes[i], attributes[i+1])
|
||||
this.__dict__[attributes[i]] = _castfunc(attributes[i], attributes[i+1])
|
||||
|
||||
return
|
||||
|
||||
@@ -630,6 +734,18 @@ class Row(object):
|
||||
raise TypeError("Incompatible comparison type")
|
||||
return cmp(self._cols, other._cols) or cmp(self._row, other._row)
|
||||
|
||||
def __hasattr__(self, this):
|
||||
if this in self._cols:
|
||||
return self._cols.index(this) < len(self._row)
|
||||
return False
|
||||
|
||||
__contains__ = __hasattr__
|
||||
|
||||
def get(self, this, default=None):
|
||||
if (this in self._cols) and (self._cols.index(this) < len(self._row)):
|
||||
return self._row[self._cols.index(this)]
|
||||
return default
|
||||
|
||||
def __getattr__(self, this):
|
||||
try:
|
||||
return self._row[self._cols.index(this)]
|
||||
@@ -25,7 +25,8 @@ import Queue
|
||||
|
||||
import eos.db
|
||||
import eos.types
|
||||
from service.settings import SettingsProvider, ProxySettings
|
||||
from service.settings import SettingsProvider, NetworkSettings
|
||||
import service
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
@@ -78,10 +79,7 @@ class PriceWorkerThread(threading.Thread):
|
||||
|
||||
# Grab prices, this is the time-consuming part
|
||||
if len(requests) > 0:
|
||||
proxy = ProxySettings.getInstance().getProxySettings()
|
||||
if proxy is not None:
|
||||
proxy = "{0}:{1}".format(*proxy)
|
||||
eos.types.Price.fetchPrices(requests, proxy=proxy)
|
||||
service.Price.fetchPrices(requests)
|
||||
|
||||
wx.CallAfter(callback)
|
||||
queue.task_done()
|
||||
|
||||
96
service/network.py
Normal file
96
service/network.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2014 Ryan Holmes
|
||||
#
|
||||
# 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/>.
|
||||
#===============================================================================
|
||||
|
||||
from service.settings import NetworkSettings
|
||||
import urllib2
|
||||
import urllib
|
||||
import config
|
||||
import socket
|
||||
|
||||
# network timeout, otherwise pyfa hangs for a long while if no internet connection
|
||||
timeout = 3
|
||||
socket.setdefaulttimeout(timeout)
|
||||
|
||||
class Error(StandardError):
|
||||
def __init__(self, msg=None):
|
||||
self.message = msg
|
||||
|
||||
class RequestError(StandardError):
|
||||
pass
|
||||
|
||||
class AuthenticationError(StandardError):
|
||||
pass
|
||||
|
||||
class ServerError(StandardError):
|
||||
pass
|
||||
|
||||
class TimeoutError(StandardError):
|
||||
pass
|
||||
|
||||
class Network():
|
||||
# Request constants - every request must supply this, as it is checked if
|
||||
# enabled or not via settings
|
||||
ENABLED = 1
|
||||
EVE = 2 # Mostly API, but also covers CREST requests
|
||||
PRICES = 4
|
||||
UPDATE = 8
|
||||
|
||||
_instance = None
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if cls._instance == None:
|
||||
cls._instance = Network()
|
||||
|
||||
return cls._instance
|
||||
|
||||
def request(self, url, type, data=None):
|
||||
# URL is required to be https as of right now
|
||||
#print "Starting request: %s\n\tType: %s\n\tPost Data: %s"%(url,type,data)
|
||||
|
||||
# Make sure request is enabled
|
||||
access = NetworkSettings.getInstance().getAccess()
|
||||
|
||||
if not self.ENABLED & access or not type & access:
|
||||
raise Error("Access not enabled - please enable in Preferences > Network")
|
||||
|
||||
# Set up some things for the request
|
||||
versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)
|
||||
headers={"User-Agent" : "pyfa {0} (Python-urllib2)".format(versionString)}
|
||||
|
||||
proxy = NetworkSettings.getInstance().getProxySettings()
|
||||
if proxy is not None:
|
||||
proxy = urllib2.ProxyHandler({'https': "{0}:{1}".format(*proxy)})
|
||||
opener = urllib2.build_opener(proxy)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
request = urllib2.Request(url, headers=headers, data=urllib.urlencode(data) if data else None)
|
||||
try:
|
||||
return urllib2.urlopen(request)
|
||||
except urllib2.HTTPError, error:
|
||||
if error.code == 404:
|
||||
raise RequestError()
|
||||
elif error.code == 403:
|
||||
raise AuthenticationError()
|
||||
elif error.code >= 500:
|
||||
raise ServerError()
|
||||
except urllib2.URLError, error:
|
||||
if "timed out" in error.reason:
|
||||
raise TimeoutError()
|
||||
else:
|
||||
raise Error(error)
|
||||
@@ -1,5 +1,5 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
# Copyright (C) 2014 Ryan Holmes
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
import re
|
||||
import xml.dom
|
||||
import urllib2
|
||||
import json
|
||||
|
||||
from eos.types import State, Slot, Module, Cargo, Fit, Ship, Drone, Implant, Booster
|
||||
@@ -166,9 +165,9 @@ class Port(object):
|
||||
@staticmethod
|
||||
def importCrest(info):
|
||||
sMkt = service.Market.getInstance()
|
||||
network = service.Network.getInstance()
|
||||
try:
|
||||
# @todo: proxy
|
||||
response = urllib2.urlopen("https://public-crest.eveonline.com/killmails/%s/%s/" % info)
|
||||
response = network.request("https://public-crest.eveonline.com/killmails/%s/%s/" % info, network.EVE)
|
||||
except:
|
||||
return
|
||||
|
||||
@@ -638,7 +637,7 @@ class Port(object):
|
||||
|
||||
for subsystem in sorted(subsystems, key=lambda mod: mod.getModifiedItemAttr("subSystemSlot")):
|
||||
dna += ":{0};1".format(subsystem.itemID)
|
||||
|
||||
|
||||
for drone in fit.drones:
|
||||
dna += ":{0};{1}".format(drone.itemID, drone.amount)
|
||||
|
||||
@@ -688,10 +687,10 @@ class Port(object):
|
||||
else:
|
||||
if not slot in slotNum:
|
||||
slotNum[slot] = 0
|
||||
|
||||
|
||||
slotId = slotNum[slot]
|
||||
slotNum[slot] += 1
|
||||
|
||||
|
||||
hardware = doc.createElement("hardware")
|
||||
hardware.setAttribute("type", module.item.name)
|
||||
slotName = Slot.getName(slot).lower()
|
||||
|
||||
116
service/price.py
Normal file
116
service/price.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#===============================================================================
|
||||
# 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 service
|
||||
import eos.db
|
||||
import eos.types
|
||||
import time
|
||||
from xml.dom import minidom
|
||||
|
||||
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():
|
||||
|
||||
@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 = eos.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.marketGroupID:
|
||||
toRequest.add(typeID)
|
||||
|
||||
# Do not waste our time if all items are not on the market
|
||||
if len(toRequest) == 0:
|
||||
return
|
||||
|
||||
# This will store POST data for eve-central
|
||||
data = []
|
||||
|
||||
# Base request URL
|
||||
baseurl = "https://api.eve-central.com/api/marketstat"
|
||||
data.append(("usesystem", 30000142)) # Use Jita for market
|
||||
|
||||
for typeID in toRequest: # Add all typeID arguments
|
||||
data.append(("typeid", typeID))
|
||||
|
||||
# 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)
|
||||
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):
|
||||
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 service.network.TimeoutError, e:
|
||||
# Timeout error deserves special treatment
|
||||
for typeID in priceMap.keys():
|
||||
priceobj = priceMap[typeID]
|
||||
priceobj.time = time.time() + TIMEOUT
|
||||
priceobj.failed = None
|
||||
del priceMap[typeID]
|
||||
except:
|
||||
# all other errors will pass and continue onward to the REREQUEST delay
|
||||
pass
|
||||
|
||||
# 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
|
||||
@@ -111,13 +111,12 @@ class Settings():
|
||||
return self.info.items()
|
||||
|
||||
|
||||
|
||||
class ProxySettings():
|
||||
class NetworkSettings():
|
||||
_instance = None
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if cls._instance == None:
|
||||
cls._instance = ProxySettings()
|
||||
cls._instance = NetworkSettings()
|
||||
|
||||
return cls._instance
|
||||
|
||||
@@ -127,33 +126,52 @@ class ProxySettings():
|
||||
# 0 - No proxy
|
||||
# 1 - Auto-detected proxy settings
|
||||
# 2 - Manual proxy settings
|
||||
serviceProxyDefaultSettings = {"mode": 1, "type": "https", "address": "", "port": ""}
|
||||
serviceNetworkDefaultSettings = {"mode": 1, "type": "https", "address": "", "port": "", "access": 15}
|
||||
|
||||
self.serviceProxySettings = SettingsProvider.getInstance().getSettings("pyfaServiceProxySettings", serviceProxyDefaultSettings)
|
||||
self.serviceNetworkSettings = SettingsProvider.getInstance().getSettings("pyfaServiceNetworkSettings", serviceNetworkDefaultSettings)
|
||||
|
||||
def isEnabled(self, type):
|
||||
if type & self.serviceNetworkSettings["access"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def toggleAccess(self, type, toggle=True):
|
||||
bitfield = self.serviceNetworkSettings["access"]
|
||||
|
||||
if toggle: # Turn bit on
|
||||
self.serviceNetworkSettings["access"] = type | bitfield
|
||||
else: # Turn bit off
|
||||
self.serviceNetworkSettings["access"] = ~type & bitfield
|
||||
|
||||
def getMode(self):
|
||||
return self.serviceProxySettings["mode"]
|
||||
return self.serviceNetworkSettings["mode"]
|
||||
|
||||
def getAddress(self):
|
||||
return self.serviceProxySettings["address"]
|
||||
return self.serviceNetworkSettings["address"]
|
||||
|
||||
def getPort(self):
|
||||
return self.serviceProxySettings["port"]
|
||||
return self.serviceNetworkSettings["port"]
|
||||
|
||||
def getType(self):
|
||||
return self.serviceProxySettings["type"]
|
||||
return self.serviceNetworkSettings["type"]
|
||||
|
||||
def getAccess(self):
|
||||
return self.serviceNetworkSettings["access"]
|
||||
|
||||
def setMode(self, mode):
|
||||
self.serviceProxySettings["mode"] = mode
|
||||
self.serviceNetworkSettings["mode"] = mode
|
||||
|
||||
def setAddress(self, addr):
|
||||
self.serviceProxySettings["address"] = addr
|
||||
self.serviceNetworkSettings["address"] = addr
|
||||
|
||||
def setPort(self, port):
|
||||
self.serviceProxySettings["port"] = port
|
||||
self.serviceNetworkSettings["port"] = port
|
||||
|
||||
def setType(self, type):
|
||||
self.serviceProxySettings["type"] = type
|
||||
self.serviceNetworkSettings["type"] = type
|
||||
|
||||
def setAccess(self, access):
|
||||
self.serviceNetworkSettings["access"] = access
|
||||
|
||||
def autodetect(self):
|
||||
|
||||
@@ -233,10 +251,10 @@ class UpdateSettings():
|
||||
|
||||
def __init__(self):
|
||||
# Settings
|
||||
# all - If True, suppress all update notifications
|
||||
# prerelease - If True, suppress only prerelease notifications
|
||||
# Updates are completely suppressed via network settings
|
||||
# prerelease - If True, suppress prerelease notifications
|
||||
# version - Set to release tag that user does not want notifications for
|
||||
serviceUpdateDefaultSettings = { "all": False, "prerelease": True, 'version': None }
|
||||
serviceUpdateDefaultSettings = {"prerelease": True, 'version': None }
|
||||
self.serviceUpdateSettings = SettingsProvider.getInstance().getSettings("pyfaServiceUpdateSettings", serviceUpdateDefaultSettings)
|
||||
|
||||
def get(self, type):
|
||||
@@ -245,5 +263,4 @@ class UpdateSettings():
|
||||
def set(self, type, value):
|
||||
self.serviceUpdateSettings[type] = value
|
||||
|
||||
# @todo: "reopen fits" setting class
|
||||
# @todo: migrate fit settings (from fit service) here?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
# Copyright (C) 2014 Ryan Holmes
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
@@ -29,16 +29,14 @@ class CheckUpdateThread(threading.Thread):
|
||||
threading.Thread.__init__(self)
|
||||
self.callback = callback
|
||||
self.settings = service.settings.UpdateSettings.getInstance()
|
||||
self.network = service.Network.getInstance()
|
||||
|
||||
def run(self):
|
||||
# Suppress all
|
||||
if (self.settings.get('all')):
|
||||
return
|
||||
network = service.Network.getInstance()
|
||||
|
||||
try:
|
||||
# @todo: use proxy settings?
|
||||
response = urllib2.urlopen('https://api.github.com/repos/DarkFenX/Pyfa/releases')
|
||||
jsonResponse = json.loads(response.read());
|
||||
response = network.request('https://api.github.com/repos/DarkFenX/Pyfa/releases', network.UPDATE)
|
||||
jsonResponse = json.loads(response.read())
|
||||
|
||||
for release in jsonResponse:
|
||||
# Suppress pre releases
|
||||
@@ -69,8 +67,8 @@ class CheckUpdateThread(threading.Thread):
|
||||
else:
|
||||
if release['prerelease'] and rVersion > config.expansionVersion:
|
||||
wx.CallAfter(self.callback, release) # Singularity -> Singularity
|
||||
break;
|
||||
except: # for when there is no internet connection
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
def versiontuple(self, v):
|
||||
|
||||
Reference in New Issue
Block a user