53fce38c6e9a606275f60c19536310e09aff2933
[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
21 import hildon
22 import gtk
23 from eveapi import eveapi
24 import fetchimg
25 import apicache
26 import os.path
27 import traceback
28 import time
29
30 #conic is used for connection handling
31 import conic
32 #import socket for handling socket exceptions
33 import socket
34
35 # we will store our preferences in gconf
36 import gnome.gconf
37
38 #ugly hack to check maemo version. any better way?
39 if hasattr(hildon, "StackableWindow"):
40     from ui.fremantle import gui
41 else:
42     from ui.diablo import gui
43
44 class mEveMon():
45     """
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     """
50
51     about_name = 'mEveMon'
52     about_text = ('Mobile character monitor for EVE Online')
53     about_authors = ['Ryan Campbell <campbellr@gmail.com>',
54                      'Danny Campbell <danny.campbell@gmail.com>']
55
56     about_website = 'http://mevemon.garage.maemo.org'
57     app_version = '0.4'
58
59
60     GCONF_DIR = "/apps/maemo/mevemon"
61
62     def __init__(self):
63         self.program = hildon.Program()
64         self.program.__init__()
65         self.gconf = gnome.gconf.client_get_default()
66         #NOTE: remove this after a few releases
67         self.update_settings()
68         self.connect_to_network()
69         self.cached_api = eveapi.EVEAPIConnection( cacheHandler = \
70                 apicache.cache_handler(debug=False))
71         self.gui = gui.mEveMonUI(self)
72         self.gui.run()
73
74     def run(self):
75         gtk.main()
76     
77     def quit(self, *args):
78         gtk.main_quit()
79
80     def update_settings(self):
81         """
82         Update from the old pre 0.3 settings to the new settings layout.
83         We should remove this eventually, once no one is using pre-0.3 mEveMon
84         """
85         uid = self.gconf.get_string("%s/eve_uid" % self.GCONF_DIR)
86         
87         if uid:
88             key = self.gconf.get_string("%s/eve_api_key" % self.GCONF_DIR)
89             self.add_account(uid, key)
90             self.gconf.unset("%s/eve_uid" % self.GCONF_DIR)
91             self.gconf.unset("%s/eve_api_key" % self.GCONF_DIR)
92
93
94     def get_accounts(self):
95         """
96         Returns a dictionary containing uid:api_key pairs gathered from gconf
97         """
98         accounts = {}
99         entries = self.gconf.all_entries("%s/accounts" % self.GCONF_DIR)
100
101         for entry in entries:
102             key = os.path.basename(entry.get_key())
103             value = entry.get_value().to_string()
104             accounts[key] = value
105
106         return accounts
107         
108     def get_api_key(self, uid):
109         """
110         Returns the api key associated with the given uid.
111         """
112         return self.gconf.get_string("%s/accounts/%s" % (self.GCONF_DIR, uid)) or ''
113
114     def remove_account(self, uid):
115         """
116         Removes the provided uid key from gconf
117         """
118         self.gconf.unset("%s/accounts/%s" % (self.GCONF_DIR, uid))
119
120     def add_account(self, uid, api_key):
121         """
122         Adds the provided uid:api_key pair to gconf.
123         """
124         self.gconf.set_string("%s/accounts/%s" % (self.GCONF_DIR, uid), api_key)
125
126     def get_auth(self, uid):
127         """
128         Returns an authentication object to be used for eveapi calls
129         that require authentication.
130         """
131         api_key = self.get_api_key(uid)
132
133         try:
134             auth = self.cached_api.auth(userID=uid, apiKey=api_key)
135         except Exception, e:
136             self.gui.report_error(str(e))
137             traceback.print_exc()
138             return None
139
140         return auth
141
142     def get_char_sheet(self, uid, char_id):
143         """
144         Returns an object containing information about the character specified
145         by the provided character ID.
146         """
147         try:
148             sheet = self.get_auth(uid).character(char_id).CharacterSheet()
149         except Exception, e:
150             self.gui.report_error(str(e))
151             # TODO: we should really have a logger that logs this error somewhere
152             traceback.print_exc()
153             return None
154
155         return sheet
156
157     def charid2uid(self, char_id):
158         """
159         Takes a character ID and returns the user ID of the account containing
160         the character.
161
162         Returns None if the character isn't found in any of the registered accounts.
163
164         """
165         acct_dict = self.get_accounts()
166         
167         for uid, api_key in acct_dict.items():
168             auth = self.cached_api.auth(userID=uid, apiKey=api_key)
169             api_char_list = auth.account.Characters()
170             
171             for character in api_char_list.characters:
172                 if character.characterID == char_id:
173                     return uid
174
175         
176         return None
177     
178     def char_id2name(self, char_id):
179         """
180         Takes a character ID and returns the character name associated with
181         that ID.
182         The EVE API accepts a comma-separated list of IDs, but for now we
183         will just handle a single ID.
184         """
185         try:
186             chars = self.cached_api.eve.CharacterName(ids=char_id).characters
187             name = chars[0].characterName
188         except Exception, e:
189             self.gui.report_error(str(e))
190             traceback.print_exc()
191             return None
192
193         return name
194
195     def char_name2id(self, name):
196         """
197         Takes the name of an EVE character and returns the characterID.
198         
199         The EVE api accepts a comma separated list of names, but for now
200         we will just handle single names/
201         """
202         try:
203             chars = self.cached_api.eve.CharacterID(names=name).characters
204             char_id = chars[0].characterID
205             char_name = chars[0].name
206         except Exception, e:
207             self.gui.report_error(str(e))
208             traceback.print_exc()
209             return None
210
211         return char_id
212
213     def get_chars_from_acct(self, uid):
214         """
215         Returns a list of characters associated with the provided user ID.
216         """
217         auth = self.get_auth(uid)
218         if not auth:
219             return None
220         else:
221             try:
222                 api_char_list = auth.account.Characters()
223                 char_list = [char.name for char in api_char_list.characters]
224             except Exception, e:
225                 self.gui.report_error(str(e))
226                 traceback.print_exc()
227                 return None
228
229         return char_list
230
231     def get_characters(self):
232         """
233         Returns a list of (character_name, image_path, uid) tuples from all the
234         accounts that are registered to mEveMon.
235         
236         If there is an authentication issue, then instead of adding a valid
237         pair to the list, it appends an 'error message' 
238
239         """
240
241         ui_char_list = []
242         err_img = "/usr/share/mevemon/imgs/error.jpg"
243
244         placeholder_chars = ("Please check your API settings.", err_img, "0")
245         
246         acct_dict = self.get_accounts()
247         if not acct_dict:
248             return [placeholder_chars]
249
250         for uid in acct_dict.keys():
251             char_names = self.get_chars_from_acct(uid)
252             
253             if not char_names:
254                 ui_char_list.append(placeholder_chars)
255             else:
256                 # append each char we get to the list we'll return to the
257                 # UI --danny
258                 for char_name in char_names:
259                     ui_char_list.append((char_name, self.get_portrait(char_name, 64) , uid) )
260         
261         return ui_char_list
262
263     def get_portrait(self, char_name, size):
264         """
265         Returns the file path of the retrieved portrait
266         """
267         char_id = self.char_name2id(char_name)
268         
269         return fetchimg.portrait_filename(char_id, size)
270
271     def get_skill_tree(self):
272         """
273         Returns an object from eveapi containing skill tree info
274         """
275         try:
276             tree = self.cached_api.eve.SkillTree()
277         except Exception, e:
278             self.gui.report_error(str(e))
279             traceback.print_exc()
280             return None
281         
282         return tree
283
284     def get_skill_in_training(self, uid, char_id):
285         """
286         Returns an object from eveapi containing information about the
287         current skill in training
288         """
289         try:
290             skill = self.get_auth(uid).character(char_id).SkillInTraining()
291         except Exception, e:
292             self.gui.report_error(str(e))
293             traceback.print_exc()
294             return None
295
296         return skill
297
298     def connection_cb(self, connection, event, mgc):
299         """
300         I'm not sure why we need this, but connection.connect() won't work
301         without it, even empty.
302         """
303         pass    
304
305
306     def connect_to_network(self):
307         """
308         This will connect to the default network if avaliable, or pop up the
309         connection dialog to select a connection.
310         Running this when we start the program ensures we are connected to a
311         network.
312         """
313         connection = conic.Connection()
314         #why 0xAA55?
315         connection.connect("connection-event", self.connection_cb, 0xAA55)
316         assert(connection.request_connection(conic.CONNECT_FLAG_NONE))
317
318
319     def get_sp(self, uid, char_id):
320         """
321         Adds up the SP for all known skills, then calculates the SP gained
322         from an in-training skill.
323         """
324         actual_sp = 0
325         
326         sheet = self.get_char_sheet(uid, char_id)
327         for skill in sheet.skills:
328             actual_sp += skill.skillpoints
329
330         live_sp = actual_sp + self.get_training_sp(uid, char_id)
331
332         return live_sp
333
334     def get_spps(self, uid, char_id):
335         """
336         Calculate and returns the skill points per hour for the given character.
337         """
338         skill = self.get_skill_in_training(uid, char_id)
339         
340         if not skill.skillInTraining:
341             return (0, 0)
342
343         total_sp = skill.trainingDestinationSP - skill.trainingStartSP
344         total_time = skill.trainingEndTime - skill.trainingStartTime
345         
346         spps = float(total_sp) / total_time
347     
348         return (spps, skill.trainingStartTime)
349
350     def get_training_sp(self, uid, char_id):
351         """
352         returns the additional SP that the in-training skill has acquired
353         """
354         spps_tuple = self.get_spps(uid, char_id)
355         
356         if not spps_tuple:
357             return 0
358         spps, start_time = spps_tuple
359         eve_time = time.time() #evetime is utc, right?
360         time_diff =  eve_time - start_time
361
362         return (spps * time_diff) 
363
364
365 if __name__ == "__main__":
366     app = mEveMon()
367     app.run()