# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+import os
+import time
+import sys
+import logging
+import logging.handlers
+import util
import hildon
import gtk
+#conic is used for connection handling
+import conic
+
from eveapi import eveapi
import fetchimg
import apicache
-
-# we will store our preferences in gconf
-import gnome.gconf
+import file_settings as settings
+from constants import LOGPATH, MAXBYTES, LOGCOUNT, CONFIG_DIR, IMG_CACHE_PATH
+from constants import APICACHE_PATH
+from constants import REQUIRED_ACCESS_MASK
#ugly hack to check maemo version. any better way?
if hasattr(hildon, "StackableWindow"):
- from ui.fremantle import ui
- is_fremantle = True
+ from ui.fremantle import gui
else:
- from ui.diablo import ui
- is_fremantle = False
+ from ui.diablo import gui
-class mEveMon():
+class mEveMon:
+ """ The controller class for mEvemon. The intent is to help
+ abstract the EVE API and settings code from the UI code.
+ """
def __init__(self):
self.program = hildon.Program()
- self.program.__init__()
- self.gconf = gnome.gconf.client_get_default()
- self.set_auth()
- self.ui = ui.mEveMonUI(self)
+ self.connect_to_network()
+ self.settings = settings.Settings()
+ self.cached_api = eveapi.EVEAPIConnection( cacheHandler = \
+ apicache.cache_handler(debug=False))
+ self.gui = gui.mEveMonUI(self)
+ self.gui.run()
def run(self):
gtk.main()
def quit(self, *args):
gtk.main_quit()
- def get_api_key(self):
- return self.gconf.get_string("/apps/maemo/mevemon/eve_api_key") or ''
-
- def get_uid(self):
- return self.gconf.get_string("/apps/maemo/mevemon/eve_uid") or ''
-
- def set_api_key(self, key):
- self.gconf.set_string("/apps/maemo/mevemon/eve_api_key", key)
+ def get_auth(self, key_id):
+ """ Returns an authentication object to be used for eveapi calls
+ that require authentication.
+ """
+ ver_code = self.settings.get_ver_code(key_id)
- def set_uid(self, uid):
- self.gconf.set_string("/apps/maemo/mevemon/eve_uid", uid)
+ try:
+ auth = self.cached_api.auth(keyID=key_id, vCode=ver_code)
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get character name")
+ return None
+ return auth
- def set_auth(self):
+ def get_char_sheet(self, key_id, char_id):
+ """ Returns an object containing information about the character specified
+ by the provided character ID.
"""
- set self.auth to None if there was a problem. somehow later on we'll
- have to pass the error to the UI, but for now I just want the program
- to not be broken. --danny
- """
- uid = self.get_uid()
- api_key = self.get_api_key()
- self.cached_api = eveapi.EVEAPIConnection( cacheHandler = \
- apicache.cache_handler( debug = False ) )
try:
- self.auth = self.cached_api.auth( userID = uid, apiKey = api_key )
- except eveapi.Error, e:
- # we need to deal with this, so raise --danny
- raise
- except ValueError, e:
- self.auth = None
- #raise
-
- def get_auth(self):
- return self.auth
-
- def get_char_sheet(self, charID):
- if not self.auth: return None
- try:
- sheet = self.auth.character(charID).CharacterSheet()
- except eveapi.Error, e:
- # we should really have a logger that logs this error somewhere
+ sheet = self.get_auth(key_id).character(char_id).CharacterSheet()
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get character name")
return None
return sheet
- def char_id2name(self, charID):
- # the api can take a comma-seperated list of ids, but we'll just take
- # a single id for now
+ def charid2key_id(self, char_id):
+ """ Takes a character ID and returns the user ID of the account containing
+ the character.
+
+ Returns None if the character isn't found in any of the registered accounts.
+
+ """
+ acct_dict = self.settings.get_accounts()
+
+ for key_id, ver_code in acct_dict.items():
+ auth = self.cached_api.auth(keyID=key_id, vCode=ver_code)
+ try:
+ api_char_list = auth.account.Characters()
+ characters = api_char_list.characters
+ except:
+ characters = []
+
+ for character in characters:
+ if character.characterID == char_id:
+ return key_id
+
+
+ def char_id2name(self, char_id):
+ """ Takes a character ID and returns the character name associated with
+ that ID.
+ The EVE API accepts a comma-separated list of IDs, but for now we
+ will just handle a single ID.
+ """
try:
- chars = self.cached_api.eve.CharacterName(ids=charID).characters
+ chars = self.cached_api.eve.CharacterName(ids=char_id).characters
name = chars[0].characterName
- except eveapi.Error, e:
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get character name")
return None
return name
def char_name2id(self, name):
- # the api can take a comma-seperated list of names, but we'll just take
- # a single name for now
+ """ Takes the name of an EVE character and returns the characterID.
+
+ The EVE api accepts a comma separated list of names, but for now
+ we will just handle single names/
+ """
try:
chars = self.cached_api.eve.CharacterID(names=name).characters
char_id = chars[0].characterID
- except eveapi.Error, e:
+ char_name = chars[0].name
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get ID")
return None
return char_id
-
- def get_characters( self ):
+ def get_chars_from_acct(self, key_id):
+ """ Returns a list of characters associated with the provided user ID.
"""
- returns a list containing a single character with an error message for a
- name, if there's a problem. FIXME --danny
+ auth = self.get_auth(key_id)
+ if not auth:
+ return None
+ else:
+ try:
+ api_char_list = auth.account.Characters()
+ char_name_list = [char.name for char in api_char_list.characters]
+ char_id_list = [char.characterID for char in api_char_list.characters]
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get character list")
+ return None
+
+ return char_name_list, char_id_list
+
+ def get_characters(self):
+ """ Returns a list of (character_name, image_path, key_id) tuples from all the
+ accounts that are registered to mEveMon.
+
+ If there is an authentication issue, then instead of adding a valid
+ pair to the list, it appends an 'error message'
"""
+
ui_char_list = []
- if is_fremantle:
- err_img = "/opt/mevemon/imgs/error.jpg"
- else:
- err_img = "/usr/share/mevemon/imgs/error.jpg"
+ err_img = "/usr/share/mevemon/imgs/error.jpg"
+ err_txt = "Problem fetching info for account (or no accounts added)"
+ bad_key = "Incorrect key access. Your access mask should be %s." % REQUIRED_ACCESS_MASK
- placeholder_chars = [("Please check your API settings.", err_img)]
- if not self.auth: return placeholder_chars
- try:
- api_char_list = self.auth.account.Characters()
- # append each char we get to the list we'll return to the
- # UI --danny
- for character in api_char_list.characters:
- ui_char_list.append( ( character.name, fetchimg.portrait_filename( character.characterID, 64 ) ) )
- except eveapi.Error, e:
- # again, we need to handle this... --danny
- raise
+ placeholder_chars = (err_txt, err_img, None)
+
+ acct_dict = self.settings.get_accounts()
+ if not acct_dict:
+ return [placeholder_chars]
+
+ for key_id, ver_code in acct_dict.items():
+
+ char_names, char_ids = self.get_chars_from_acct(key_id)
+
+ if not char_names:
+ ui_char_list.append((err_txt + "\t(KEY_ID: %s)" % key_id, err_img, None))
+ else:
+
+ # since there are char names, let's check the key
+ # access and if it's bad we'll generate a key URL for
+ # each character
+ for char_name, char_id in zip(char_names, char_ids):
+ if self.get_access_mask(key_id, ver_code) != REQUIRED_ACCESS_MASK:
+ key_url = self.generate_access_mask_url(char_id)
+ ui_char_list.append((bad_key, err_img, None))
+ else:
+ # append each char we get to the list we'll
+ # return to the UI --danny
+ ui_char_list.append((char_name, self.get_portrait(char_name, 64), key_id))
+
return ui_char_list
def get_portrait(self, char_name, size):
+ """ Returns the file path of the retrieved portrait
"""
- returns the relative path of the retrieved portrait
- """
- charID = self.char_name2id(char_name)
- return fetchimg.portrait_filename(charID, size)
+ char_id = self.char_name2id(char_name)
+
+ return fetchimg.portrait_filename(char_id, size)
def get_skill_tree(self):
+ """ Returns an object from eveapi containing skill tree info
+ """
try:
tree = self.cached_api.eve.SkillTree()
- except eveapi.Error, e:
- print e
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get skill-in-training:")
return None
return tree
- def get_skill_in_training(self, charID):
+ def get_skill_in_training(self, key_id, char_id):
+ """ Returns an object from eveapi containing information about the
+ current skill in training
+ """
try:
- skill = self.auth.character(charID).SkillInTraining()
- except:
- print e
+ # should this be accessing the cached object? (confused) --danny
+ skill = self.get_auth(key_id).character(char_id).SkillInTraining()
+ except Exception, e:
+ self.gui.report_error(str(e))
+ logging.getLogger('mevemon').exception("Failed to get skill-in-training:")
return None
return skill
+ def connection_cb(self, connection, event, mgc):
+ """ I'm not sure why we need this, but connection.connect() won't work
+ without it, even empty.
+ """
+ pass
+
+
+ def connect_to_network(self):
+ """ This will connect to the default network if avaliable, or pop up the
+ connection dialog to select a connection.
+ Running this when we start the program ensures we are connected to a
+ network.
+ """
+ connection = conic.Connection()
+ #why 0xAA55?
+ connection.connect("connection-event", self.connection_cb, 0xAA55)
+ assert(connection.request_connection(conic.CONNECT_FLAG_NONE))
+
+
+ def get_sp(self, key_id, char_id):
+ """ Adds up the SP for all known skills, then calculates the SP gained
+ from an in-training skill.
+ """
+ actual_sp = 0
+
+ sheet = self.get_char_sheet(key_id, char_id)
+ for skill in sheet.skills:
+ actual_sp += skill.skillpoints
+
+ live_sp = actual_sp + self.get_training_sp(key_id, char_id)
+
+ return live_sp
+
+ def get_spps(self, key_id, char_id):
+ """ Calculate and returns the skill points per hour for the given character.
+ """
+ skill = self.get_skill_in_training(key_id, char_id)
+
+ if not skill.skillInTraining:
+ return (0, 0)
+
+ total_sp = skill.trainingDestinationSP - skill.trainingStartSP
+ total_time = skill.trainingEndTime - skill.trainingStartTime
+
+ spps = float(total_sp) / total_time
+
+ return (spps, skill.trainingStartTime)
+
+ def get_training_sp(self, key_id, char_id):
+ """ returns the additional SP that the in-training skill has acquired
+ """
+ spps_tuple = self.get_spps(key_id, char_id)
+
+ if not spps_tuple:
+ return 0
+ spps, start_time = spps_tuple
+ eve_time = time.time() #evetime is utc, right?
+ time_diff = eve_time - start_time
+
+ return (spps * time_diff)
+
+ def clear_cache(self):
+ """ Clears all cached data (images and eveapi cache) """
+ try:
+ util.clean_dir(IMG_CACHE_PATH)
+ util.clean_dir(APICACHE_PATH)
+ except OSError, e:
+ logging.getLogger('mevemon').exception("Failed to clear cache")
+
+ def get_access_mask(self, key_id, ver_code):
+
+ """
+ Returns the access mask that determines what data we have
+ access to on the account.
+
+ """
+
+ return self.cached_api.account.APIKeyInfo(keyID=key_id, vCode=ver_code).key.accessMask
+
+ def generate_access_mask_url(self, char_id):
+
+ """
+ Generates a URL to send the user to the page that will help
+ them create an access key with exactly the access mevemon
+ needs.
+ """
+
+ return "https://supporttest.eveonline.com/api/Key/CreatePredefined/%s/%s/false" % (REQUIRED_ACCESS_MASK, char_id)
+
+def excepthook(ex_type, value, tb):
+ """ a replacement for the default exception handler that logs errors"""
+ logging.getLogger("mevemon").error('Uncaught exception:',
+ exc_info=(ex_type, value, tb))
+
+def setupLogger():
+ """ sets up the logging """
+ if not os.path.exists(CONFIG_DIR):
+ os.makedirs(CONFIG_DIR)
+
+ logger = logging.getLogger("mevemon")
+ logger.setLevel(logging.DEBUG)
+
+ fileHandler = logging.handlers.RotatingFileHandler(LOGPATH,
+ maxBytes=MAXBYTES,
+ backupCount=LOGCOUNT)
+ file_fmt = logging.Formatter('%(asctime)s %(name)-10s %(levelname)-5s %(message)s')
+ console_fmt = logging.Formatter('%(name)-10s %(levelname)-5s %(message)s')
+ fileHandler.setFormatter(file_fmt)
+ logger.addHandler(fileHandler)
+
+ #create console handler
+ console = logging.StreamHandler()
+ console.setLevel(logging.DEBUG)
+ console.setFormatter(console_fmt)
+ logger.addHandler(console)
+ logger.debug("Logging successfully set-up.")
+
if __name__ == "__main__":
+ setupLogger()
+ sys.excepthook = excepthook
app = mEveMon()
- app.run()
+ try:
+ app.run()
+ except KeyboardInterrupt:
+ sys.exit(0)