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