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