add a 'clear cache' button in the settings
[mevemon] / package / src / mevemon.py
1 #!/usr/bin/env python
2 #
3 # mEveMon - A character monitor for EVE Online
4 # Copyright (c) 2010  Ryan and Danny Campbell, and the mEveMon Team
5 #
6 # mEveMon is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # mEveMon is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import os
21 import time
22 import sys
23 import logging
24 import logging.handlers
25 import shutil
26
27 import hildon
28 import gtk
29 #conic is used for connection handling
30 import conic
31
32 from eveapi import eveapi
33 import fetchimg
34 import apicache
35 import file_settings as settings
36 from constants import LOGPATH, MAXBYTES, LOGCOUNT, CONFIG_DIR, IMG_CACHE_PATH
37 from constants import APICACHE_PATH
38
39 #ugly hack to check maemo version. any better way?
40 if hasattr(hildon, "StackableWindow"):
41     from ui.fremantle import gui
42 else:
43     from ui.diablo import gui
44
45 class mEveMon:
46     """ The controller class for mEvemon. The intent is to help
47         abstract the EVE API and settings code from the UI code.
48     """
49     def __init__(self):
50         self.program = hildon.Program()
51         self.connect_to_network()
52         self.settings = settings.Settings()
53         self.cached_api = eveapi.EVEAPIConnection( cacheHandler = \
54                 apicache.cache_handler(debug=False))
55         self.gui = gui.mEveMonUI(self)
56         self.gui.run()
57
58     def run(self):
59         gtk.main()
60     
61     def quit(self, *args):
62         gtk.main_quit()
63
64     def get_auth(self, uid):
65         """ Returns an authentication object to be used for eveapi calls
66             that require authentication.
67         """
68         api_key = self.settings.get_api_key(uid)
69
70         try:
71             auth = self.cached_api.auth(userID=uid, apiKey=api_key)
72         except Exception, e:
73             self.gui.report_error(str(e))
74             logging.getLogger('mevemon').exception("Failed to get character name")
75             return None
76
77         return auth
78
79     def get_char_sheet(self, uid, char_id):
80         """ Returns an object containing information about the character specified
81             by the provided character ID.
82         """
83         try:
84             sheet = self.get_auth(uid).character(char_id).CharacterSheet()
85         except Exception, e:
86             self.gui.report_error(str(e))
87             logging.getLogger('mevemon').exception("Failed to get character name")
88             return None
89
90         return sheet
91
92     def charid2uid(self, char_id):
93         """ Takes a character ID and returns the user ID of the account containing
94             the character.
95
96             Returns None if the character isn't found in any of the registered accounts.
97
98         """
99         acct_dict = self.settings.get_accounts()
100         
101         for uid, api_key in acct_dict.items():
102             auth = self.cached_api.auth(userID=uid, apiKey=api_key)
103             try:
104                 api_char_list = auth.account.Characters()
105                 characters = api_char_list.characters
106             except:
107                 characters = []
108
109             for character in characters:
110                 if character.characterID == char_id:
111                     return uid
112
113     
114     def char_id2name(self, char_id):
115         """ Takes a character ID and returns the character name associated with
116             that ID.
117             The EVE API accepts a comma-separated list of IDs, but for now we
118             will just handle a single ID.
119         """
120         try:
121             chars = self.cached_api.eve.CharacterName(ids=char_id).characters
122             name = chars[0].characterName
123         except Exception, e:
124             self.gui.report_error(str(e))
125             logging.getLogger('mevemon').exception("Failed to get character name")
126             return None
127
128         return name
129
130     def char_name2id(self, name):
131         """ Takes the name of an EVE character and returns the characterID.
132         
133             The EVE api accepts a comma separated list of names, but for now
134             we will just handle single names/
135         """
136         try:
137             chars = self.cached_api.eve.CharacterID(names=name).characters
138             char_id = chars[0].characterID
139             char_name = chars[0].name
140         except Exception, e:
141             self.gui.report_error(str(e))
142             logging.getLogger('mevemon').exception("Failed to get ID")
143             return None
144
145         return char_id
146
147     def get_chars_from_acct(self, uid):
148         """ Returns a list of characters associated with the provided user ID.
149         """
150         auth = self.get_auth(uid)
151         if not auth:
152             return None
153         else:
154             try:
155                 api_char_list = auth.account.Characters()
156                 char_list = [char.name for char in api_char_list.characters]
157             except Exception, e:
158                 self.gui.report_error(str(e))
159                 logging.getLogger('mevemon').exception("Failed to get character list")
160                 return None
161
162         return char_list
163
164     def get_characters(self):
165         """ Returns a list of (character_name, image_path, uid) tuples from all the
166             accounts that are registered to mEveMon.
167         
168             If there is an authentication issue, then instead of adding a valid
169             pair to the list, it appends an 'error message' 
170         """
171
172         ui_char_list = []
173         err_img = "/usr/share/mevemon/imgs/error.jpg"
174         err_txt = "Problem fetching info for account (or no accounts added)"
175
176         placeholder_chars = (err_txt, err_img, None)
177         
178         acct_dict = self.settings.get_accounts()
179         if not acct_dict:
180             return [placeholder_chars]
181
182         for uid in acct_dict.keys():
183             char_names = self.get_chars_from_acct(uid)
184             
185             if not char_names:
186                 ui_char_list.append((err_txt + "\t(UID: %s)" % uid, err_img, None))
187             else:
188                 # append each char we get to the list we'll return to the
189                 # UI --danny
190                 for char_name in char_names:
191                     ui_char_list.append((char_name, self.get_portrait(char_name, 64) , uid) )
192         
193         return ui_char_list
194
195     def get_portrait(self, char_name, size):
196         """ Returns the file path of the retrieved portrait
197         """
198         char_id = self.char_name2id(char_name)
199         
200         return fetchimg.portrait_filename(char_id, size)
201
202     def get_skill_tree(self):
203         """ Returns an object from eveapi containing skill tree info
204         """
205         try:
206             tree = self.cached_api.eve.SkillTree()
207         except Exception, e:
208             self.gui.report_error(str(e))
209             logging.getLogger('mevemon').exception("Failed to get skill-in-training:")
210             return None
211         
212         return tree
213
214     def get_skill_in_training(self, uid, char_id):
215         """ Returns an object from eveapi containing information about the
216             current skill in training
217         """
218         try:
219             skill = self.get_auth(uid).character(char_id).SkillInTraining()
220         except Exception, e:
221             self.gui.report_error(str(e))
222             logging.getLogger('mevemon').exception("Failed to get skill-in-training:")
223             return None
224
225         return skill
226
227     def connection_cb(self, connection, event, mgc):
228         """ I'm not sure why we need this, but connection.connect() won't work
229             without it, even empty.
230         """
231         pass    
232
233
234     def connect_to_network(self):
235         """ This will connect to the default network if avaliable, or pop up the
236             connection dialog to select a connection.
237             Running this when we start the program ensures we are connected to a
238             network.
239         """
240         connection = conic.Connection()
241         #why 0xAA55?
242         connection.connect("connection-event", self.connection_cb, 0xAA55)
243         assert(connection.request_connection(conic.CONNECT_FLAG_NONE))
244
245
246     def get_sp(self, uid, char_id):
247         """ Adds up the SP for all known skills, then calculates the SP gained
248             from an in-training skill.
249         """
250         actual_sp = 0
251         
252         sheet = self.get_char_sheet(uid, char_id)
253         for skill in sheet.skills:
254             actual_sp += skill.skillpoints
255
256         live_sp = actual_sp + self.get_training_sp(uid, char_id)
257
258         return live_sp
259
260     def get_spps(self, uid, char_id):
261         """ Calculate and returns the skill points per hour for the given character.
262         """
263         skill = self.get_skill_in_training(uid, char_id)
264         
265         if not skill.skillInTraining:
266             return (0, 0)
267
268         total_sp = skill.trainingDestinationSP - skill.trainingStartSP
269         total_time = skill.trainingEndTime - skill.trainingStartTime
270         
271         spps = float(total_sp) / total_time
272     
273         return (spps, skill.trainingStartTime)
274
275     def get_training_sp(self, uid, char_id):
276         """ returns the additional SP that the in-training skill has acquired
277         """
278         spps_tuple = self.get_spps(uid, char_id)
279         
280         if not spps_tuple:
281             return 0
282         spps, start_time = spps_tuple
283         eve_time = time.time() #evetime is utc, right?
284         time_diff =  eve_time - start_time
285
286         return (spps * time_diff) 
287
288     def clear_cache(self):
289         """ Clears all cached data (images and eveapi cache) """
290         try:
291             shutil.rmtree(IMG_CACHE_PATH)
292             shutil.rmtree(APICACHE_PATH)
293         except OSError, e:
294             logging.getLogger('mevemon').exception("Failed to clear cache")
295
296 def excepthook(ex_type, value, tb):
297     """ a replacement for the default exception handler that logs errors"""
298     logging.getLogger("mevemon").error('Uncaught exception:', 
299                       exc_info=(ex_type, value, tb))
300
301 def setupLogger():
302     """ sets up the logging """
303     if not os.path.exists(CONFIG_DIR):
304         os.makedirs(CONFIG_DIR)
305
306     logger = logging.getLogger("mevemon")
307     logger.setLevel(logging.DEBUG)
308     
309     fileHandler = logging.handlers.RotatingFileHandler(LOGPATH,
310                                                     maxBytes=MAXBYTES,
311                                                     backupCount=LOGCOUNT)
312     file_fmt = logging.Formatter('%(asctime)s %(name)-10s %(levelname)-5s %(message)s')
313     console_fmt = logging.Formatter('%(name)-10s %(levelname)-5s %(message)s')
314     fileHandler.setFormatter(file_fmt)
315     logger.addHandler(fileHandler)
316
317     #create console handler
318     console = logging.StreamHandler()
319     console.setLevel(logging.DEBUG)
320     console.setFormatter(console_fmt)
321     logger.addHandler(console)
322     logger.debug("Logging successfully set-up.")
323
324
325 if __name__ == "__main__":
326     setupLogger()
327     sys.excepthook = excepthook
328     app = mEveMon()
329     try:
330         app.run()
331     except KeyboardInterrupt:
332         sys.exit(0)