instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Player / BaseApp.py
1 # Written by Arno Bakker, Choopan RATTANAPOKA, Jie Yang
2 # see LICENSE.txt for license information
3 """ Base class for Player and Plugin Background process. See swarmplayer.py """
4
5 #
6 # TODO: set 'download_slice_size' to 32K, such that pieces are no longer
7 # downloaded in 2 chunks. This particularly avoids a bad case where you
8 # kick the source: you download chunk 1 of piece X
9 # from lagging peer and download chunk 2 of piece X from source. With the piece
10 # now complete you check the sig. As the first part of the piece is old, this
11 # fails and we kick the peer that gave us the completing chunk, which is the 
12 # source.
13 #
14 # Note that the BT spec says: 
15 # "All current implementations use 2 15 , and close connections which request 
16 # an amount greater than 2 17." http://www.bittorrent.org/beps/bep_0003.html
17 #
18 # So it should be 32KB already. However, the BitTorrent (3.4.1, 5.0.9), 
19 # BitTornado and Azureus all use 2 ** 14 = 16KB chunks.
20
21 import os
22 import sys
23 import time
24 import shutil
25 from sets import Set
26
27 from base64 import encodestring
28 from threading import enumerate,currentThread,RLock
29 from traceback import print_exc
30 # Ric: added svc ext  
31 from BaseLib.Video.utils import svcextdefaults
32
33 if sys.platform == "darwin":
34     # on Mac, we can only load VLC/OpenSSL libraries
35     # relative to the location of tribler.py
36     os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
37 try:
38     import wxversion
39     wxversion.select('2.8')
40 except:
41     pass
42 import wx
43
44 from BaseLib.__init__ import LIBRARYNAME
45 from BaseLib.Core.API import *
46 from BaseLib.Policies.RateManager import UserDefinedMaxAlwaysOtherwiseEquallyDividedRateManager
47 from BaseLib.Utilities.Instance2Instance import *
48
49 from BaseLib.Player.systray import *
50 # from BaseLib.Player.Reporter import Reporter
51 from BaseLib.Player.UtilityStub import UtilityStub
52 from BaseLib.Core.Statistics.Status.Status import get_status_holder
53
54 DEBUG = False
55 RATELIMITADSL = False
56 DOWNLOADSPEED = 300
57 DISKSPACE_LIMIT = 5L * 1024L * 1024L * 1024L  # 5 GB
58 DEFAULT_MAX_UPLOAD_SEED_WHEN_SEEDING = 75 # KB/s
59
60 class BaseApp(wx.App,InstanceConnectionHandler):
61     def __init__(self, redirectstderrout, appname, appversion, params, single_instance_checker, installdir, i2iport, sport):
62         self.appname = appname
63         self.appversion = appversion
64         self.params = params
65         self.single_instance_checker = single_instance_checker
66         self.installdir = installdir
67         self.i2iport = i2iport
68         self.sport = sport
69         self.error = None
70         self.s = None
71         self.tbicon = None
72         
73         self.downloads_in_vodmode = Set() # Set of playing Downloads, one for SP, many for Plugin
74         self.ratelimiter = None
75         self.ratelimit_update_count = 0
76         self.playermode = DLSTATUS_DOWNLOADING
77         self.getpeerlistcount = 2 # for research Reporter
78         self.shuttingdown = False
79         
80         InstanceConnectionHandler.__init__(self,self.i2ithread_readlinecallback)
81         wx.App.__init__(self, redirectstderrout)
82
83         
84     def OnInitBase(self):
85         """ To be wrapped in a OnInit() method that returns True/False """
86         
87         # Normal startup
88         # Read config
89         state_dir = Session.get_default_state_dir('.'+self.appname)
90         
91         self.utility = UtilityStub(self.installdir,state_dir)
92         self.utility.app = self
93         print >>sys.stderr,self.utility.lang.get('build')
94         self.iconpath = os.path.join(self.installdir,LIBRARYNAME,'Images',self.appname+'Icon.ico')
95         self.logopath = os.path.join(self.installdir,LIBRARYNAME,'Images',self.appname+'Logo.png')
96
97         
98         # Start server for instance2instance communication
99         self.i2is = Instance2InstanceServer(self.i2iport,self,timeout=(24.0*3600.0)) 
100
101
102         # The playerconfig contains all config parameters that are not
103         # saved by checkpointing the Session or its Downloads.
104         self.load_playerconfig(state_dir)
105
106         # Install systray icon
107         # Note: setting this makes the program not exit when the videoFrame
108         # is being closed.
109         self.tbicon = PlayerTaskBarIcon(self,self.iconpath)
110         
111         # Start Tribler Session
112         cfgfilename = Session.get_default_config_filename(state_dir)
113         
114         if DEBUG:
115             print >>sys.stderr,"main: Session config",cfgfilename
116         try:
117             self.sconfig = SessionStartupConfig.load(cfgfilename)
118             
119             print >>sys.stderr,"main: Session saved port",self.sconfig.get_listen_port(),cfgfilename
120         except:
121             print_exc()
122             self.sconfig = SessionStartupConfig()
123             self.sconfig.set_install_dir(self.installdir)
124             self.sconfig.set_state_dir(state_dir)
125             self.sconfig.set_listen_port(self.sport)
126             self.configure_session()    
127
128         self.s = Session(self.sconfig)
129         self.s.set_download_states_callback(self.sesscb_states_callback)
130
131         # self.reporter = Reporter( self.sconfig )
132
133         if RATELIMITADSL:
134             self.ratelimiter = UserDefinedMaxAlwaysOtherwiseEquallyDividedRateManager()
135             self.ratelimiter.set_global_max_speed(DOWNLOAD,DOWNLOADSPEED)
136             self.ratelimiter.set_global_max_speed(UPLOAD,90)
137
138
139         # Arno: For extra robustness, ignore any errors related to restarting
140         try:
141             # Load all other downloads in cache, but in STOPPED state
142             self.s.load_checkpoint(initialdlstatus=DLSTATUS_STOPPED)
143         except:
144             print_exc()
145
146         # Start remote control
147         self.i2is.start()
148
149         # report client version
150         # from BaseLib.Core.Statistics.StatusReporter import get_reporter_instance
151         reporter = get_status_holder("LivingLab")
152         reporter.create_and_add_event("client-startup-version", [self.utility.lang.get("version")])
153         reporter.create_and_add_event("client-startup-build", [self.utility.lang.get("build")])
154         reporter.create_and_add_event("client-startup-build-date", [self.utility.lang.get("build_date")])
155
156     def configure_session(self):
157         # No overlay
158         self.sconfig.set_overlay(False)
159         self.sconfig.set_megacache(False)
160
161
162     def _get_poa(self, tdef):
163         """Try to load a POA - possibly trigger a GUI-thing or should the plugin handle getting it if none is already available?"""
164         
165         from BaseLib.Core.ClosedSwarm import ClosedSwarm,PaymentIntegration
166         print >>sys.stderr, "Swarm_id:",encodestring(tdef.infohash).replace("\n","")
167         try:
168             poa = ClosedSwarm.trivial_get_poa(self.s.get_state_dir(),
169                                               self.s.get_permid(),
170                                               tdef.infohash)
171             
172             poa.verify()
173             if not poa.torrent_id == tdef.infohash:
174                 raise Exception("Bad POA - wrong infohash")
175             print >> sys.stderr,"Loaded poa from ",self.s.get_state_dir()
176         except:
177             # Try to get it or just let the plugin handle it?
178             swarm_id = encodestring(tdef.infohash).replace("\n","")
179             my_id = encodestring(self.s.get_permid()).replace("\n", "")
180             try:
181                 # TODO: Support URLs from torrents?
182                 poa = PaymentIntegration.wx_get_poa(None,
183                                                     swarm_id,
184                                                     my_id,
185                                                     swarm_title=tdef.get_name())
186             except Exception,e:
187                 print >> sys.stderr, "Failed to get POA:",e
188                 poa = None
189
190         try:
191             ClosedSwarm.trivial_save_poa(self.s.get_state_dir(),
192                                          self.s.get_permid(),
193                                          tdef.infohash,
194                                          poa)
195         except Exception,e:
196             print >> sys.stderr,"Failed to save POA",e
197             
198         if poa:
199             if not poa.torrent_id == tdef.infohash:
200                 raise Exception("Bad POA - wrong infohash")
201
202         return poa
203
204
205     def start_download(self,tdef,dlfile,poa=None,supportedvodevents=None):
206         """ Start download of torrent tdef and play video file dlfile from it """
207         if poa:
208             from BaseLib.Core.ClosedSwarm import ClosedSwarm
209             if not poa.__class__ == ClosedSwarm.POA:
210                 raise InvalidPOAException("Not a POA")
211             
212         # Free diskspace, if needed
213         destdir = self.get_default_destdir()
214         if not os.access(destdir,os.F_OK):
215             os.mkdir(destdir)
216
217         # Arno: For extra robustness, ignore any errors related to restarting
218         # TODO: Extend code such that we can also delete files from the 
219         # disk cache, not just Downloads. This would allow us to keep the
220         # parts of a Download that we already have, but that is being aborted
221         # by the user by closing the video window. See remove_playing_*
222         try:
223             if not self.free_up_diskspace_by_downloads(tdef.get_infohash(),tdef.get_length([dlfile])):
224                 print >>sys.stderr,"main: Not enough free diskspace, ignoring"
225         except:
226             print_exc()
227         
228         # Setup how to download
229         dcfg = DownloadStartupConfig()
230
231         # CLOSED SWARMS
232         if poa:
233             dcfg.set_poa(poa)
234             print >> sys.stderr,"POA:",dcfg.get_poa()
235         else:
236             dcfg.set_poa(None)
237             
238         # Delegate processing to VideoPlayer
239         if supportedvodevents is None:
240             supportedvodevents = self.get_supported_vod_events()
241             
242         print >>sys.stderr,"bg: VOD EVENTS",supportedvodevents
243         dcfg.set_video_events(supportedvodevents)
244         
245         # Ric: added svc
246         if tdef.is_multifile_torrent():
247             svcdlfiles = self.is_svc(dlfile, tdef)
248
249             if svcdlfiles is not None:
250                 dcfg.set_video_event_callback(self.sesscb_vod_event_callback, dlmode=DLMODE_SVC)
251                 # Ric: svcdlfiles is an ordered list of svc layers
252                 dcfg.set_selected_files(svcdlfiles)
253             else:
254                 # Normal multi-file torrent
255                 dcfg.set_video_event_callback(self.sesscb_vod_event_callback)
256                 dcfg.set_selected_files([dlfile])
257         else:
258             dcfg.set_video_event_callback(self.sesscb_vod_event_callback)
259             # Do not set selected file
260                     
261
262         dcfg.set_dest_dir(destdir)
263         
264         # Arno: 2008-7-15: commented out, just stick with old ABC-tuned 
265         # settings for now
266         #dcfg.set_max_conns_to_initiate(300)
267         #dcfg.set_max_conns(300)
268         
269         # Cap at 1 MB/s
270         print >>sys.stderr,"bg: Capping Download speed to 1 MByte/s"
271         dcfg.set_max_speed(DOWNLOAD,1024)
272         
273         
274         # Stop all non-playing, see if we're restarting one
275         infohash = tdef.get_infohash()
276         newd = None
277         for d in self.s.get_downloads():
278             if d.get_def().get_infohash() == infohash:
279                 # Download already exists.
280                 # One safe option is to remove it (but not its downloaded content)
281                 # so we can start with a fresh DownloadStartupConfig. However,
282                 # this gives funky concurrency errors and could prevent a
283                 # Download from starting without hashchecking (as its checkpoint
284                 # was removed) 
285                 # Alternative is to set VOD callback, etc. at Runtime:
286                 print >>sys.stderr,"main: Reusing old duplicate Download",`infohash`
287                 newd = d
288                                     
289                 # If we have a POA, we add it to the existing download
290                 if poa:
291                     d.set_poa(poa)
292
293             if d not in self.downloads_in_vodmode:
294                 d.stop()
295
296         self.s.lm.h4xor_reset_init_conn_counter()
297
298         # ARNOTODO: does this work with Plugin's duplicate download facility?
299
300         self.playermode = DLSTATUS_DOWNLOADING
301         if newd is None:
302             print >>sys.stderr,"main: Starting new Download",`infohash`
303             newd = self.s.start_download(tdef,dcfg)
304         # Ric: added restart of an svc download
305         else:
306             newd.set_video_events(self.get_supported_vod_events())
307
308             svcdlfiles = self.is_svc(dlfile, tdef)
309             if svcdlfiles is not None:
310                 newd.set_video_event_callback(self.sesscb_vod_event_callback, dlmode = DLMODE_SVC)
311                 # Ric: svcdlfiles is an ordered list of svc layers
312                 newd.set_selected_files(svcdlfiles)
313             else:
314                 newd.set_video_event_callback(self.sesscb_vod_event_callback)
315                 if tdef.is_multifile_torrent():
316                     newd.set_selected_files([dlfile])
317
318             print >>sys.stderr,"main: Restarting existing Download",`infohash`
319             newd.restart()
320
321         self.downloads_in_vodmode.add(newd)
322
323         print >>sys.stderr,"main: Saving content to",newd.get_dest_files()
324         return newd
325
326
327     def sesscb_vod_event_callback(self,d,event,params):
328         pass
329         
330     def get_supported_vod_events(self):
331         pass
332
333
334     #
335     # DownloadCache
336     #
337     def free_up_diskspace_by_downloads(self,infohash,needed):
338         
339         if DEBUG:
340             print >> sys.stderr,"main: free_up: needed",needed,DISKSPACE_LIMIT
341         if needed > DISKSPACE_LIMIT:
342             # Not cleaning out whole cache for bigguns
343             if DEBUG:
344                 print >> sys.stderr,"main: free_up: No cleanup for bigguns"
345             return True 
346         
347         inuse = 0L
348         timelist = []
349         dlist = self.s.get_downloads()
350         for d in dlist:
351             hisinfohash = d.get_def().get_infohash()
352             if infohash == hisinfohash:
353                 # Don't delete the torrent we want to play
354                 continue
355             destfiles = d.get_dest_files()
356             if DEBUG:
357                 print >> sys.stderr,"main: free_up: Downloaded content",`destfiles`
358             
359             dinuse = 0L
360             for (filename,savepath) in destfiles:
361                 stat = os.stat(savepath)
362                 dinuse += stat.st_size
363             inuse += dinuse
364             timerec = (stat.st_ctime,dinuse,d)
365             timelist.append(timerec)
366             
367         if inuse+needed < DISKSPACE_LIMIT:
368             # Enough available, done.
369             if DEBUG:
370                 print >> sys.stderr,"main: free_up: Enough avail",inuse
371             return True
372         
373         # Policy: remove oldest till sufficient
374         timelist.sort()
375         if DEBUG:
376             print >> sys.stderr,"main: free_up: Found",timelist,"in dest dir"
377         
378         got = 0L
379         for ctime,dinuse,d in timelist:
380             print >> sys.stderr,"main: free_up: Removing",`d.get_def().get_name_as_unicode()`,"to free up diskspace, t",ctime
381             self.s.remove_download(d,removecontent=True)
382             got += dinuse
383             if got > needed:
384                 return True
385         # Deleted all, still no space:
386         return False
387         
388         
389     #
390     # Process periodically reported DownloadStates
391     #
392     def sesscb_states_callback(self,dslist):
393         """ Called by Session thread """
394
395         #print >>sys.stderr,"bg: sesscb_states_callback",currentThread().getName()
396
397         # Display some stats
398         if (int(time.time()) % 5) == 0:
399             for ds in dslist:
400                 d = ds.get_download()
401                 print >>sys.stderr, '%s %s %5.2f%% %s up %8.2fKB/s down %8.2fKB/s' % \
402                     (d.get_def().get_name(), \
403                      dlstatus_strings[ds.get_status()], \
404                      ds.get_progress() * 100, \
405                      ds.get_error(), \
406                      ds.get_current_speed(UPLOAD), \
407                      ds.get_current_speed(DOWNLOAD))
408         
409         # Arno: we want the prebuf stats every second, and we want the
410         # detailed peerlist, needed for research stats. Getting them every
411         # second may be too expensive, so get them every 10.
412         #
413         self.getpeerlistcount += 1
414         getpeerlist = (self.getpeerlistcount % 10) == 0
415         haspeerlist =  (self.getpeerlistcount % 10) == 1
416
417         # Arno: delegate to GUI thread. This makes some things (especially
418         #access control to self.videoFrame easier
419         #self.gui_states_callback(dslist)
420         #print >>sys.stderr,"bg: sesscb_states_callback: calling GUI",currentThread().getName()
421         wx.CallAfter(self.gui_states_callback_wrapper,dslist,haspeerlist)
422         
423         #print >>sys.stderr,"main: SessStats:",self.getpeerlistcount,getpeerlist,haspeerlist
424         return (1.0,getpeerlist) 
425
426
427     def gui_states_callback_wrapper(self,dslist,haspeerlist):
428         try:
429             self.gui_states_callback(dslist,haspeerlist)
430         except:
431             print_exc()
432
433
434     def gui_states_callback(self,dslist,haspeerlist):
435         """ Called by *GUI* thread.
436         CAUTION: As this method is called by the GUI thread don't to any 
437         time-consuming stuff here! """
438         
439         #print >>sys.stderr,"main: Stats:"
440         if self.shuttingdown:
441             return ([],0,0)
442         
443         # See which Download is currently playing
444         playermode = self.playermode
445
446         totalspeed = {}
447         totalspeed[UPLOAD] = 0.0
448         totalspeed[DOWNLOAD] = 0.0
449         totalhelping = 0
450
451         # When not playing, display stats for all Downloads and apply rate control.
452         if playermode == DLSTATUS_SEEDING:
453             if DEBUG:
454                 for ds in dslist:
455                     print >>sys.stderr,"main: Stats: Seeding: %s %.1f%% %s" % (dlstatus_strings[ds.get_status()],100.0*ds.get_progress(),ds.get_error())
456             self.ratelimit_callback(dslist)
457             
458         # Calc total dl/ul speed and find DownloadStates for playing Downloads
459         playing_dslist = []
460         for ds in dslist:
461             if ds.get_download() in self.downloads_in_vodmode:
462                 playing_dslist.append(ds)
463             elif DEBUG and playermode == DLSTATUS_DOWNLOADING:
464                 print >>sys.stderr,"main: Stats: Waiting: %s %.1f%% %s" % (dlstatus_strings[ds.get_status()],100.0*ds.get_progress(),ds.get_error())
465             
466             for dir in [UPLOAD,DOWNLOAD]:
467                 totalspeed[dir] += ds.get_current_speed(dir)
468             totalhelping += ds.get_num_peers()
469
470         # Report statistics on all downloads to research server, every 10 secs
471         # if haspeerlist:
472         #     try:
473         #         for ds in dslist:
474         #             self.reporter.report_stat(ds)
475         #     except:
476         #         print_exc()
477
478         # Set systray icon tooltip. This has limited size on Win32!
479         txt = self.appname+' '+self.appversion+'\n\n'
480         txt += 'DL: %.1f\n' % (totalspeed[DOWNLOAD])
481         txt += 'UL:   %.1f\n' % (totalspeed[UPLOAD])
482         txt += 'Helping: %d\n' % (totalhelping) 
483         #print >>sys.stderr,"main: ToolTip summary",txt
484         self.OnSetSysTrayTooltip(txt)
485
486         # No playing Downloads        
487         if len(playing_dslist) == 0:
488             return ([],0,0)
489         elif DEBUG and playermode == DLSTATUS_DOWNLOADING:
490             for ds in playing_dslist:
491                 print >>sys.stderr,"main: Stats: DL: %s %.1f%% %s dl %.1f ul %.1f n %d" % (dlstatus_strings[ds.get_status()],100.0*ds.get_progress(),ds.get_error(),ds.get_current_speed(DOWNLOAD),ds.get_current_speed(UPLOAD),ds.get_num_peers())
492
493         # If we're done playing we can now restart any previous downloads to 
494         # seed them.
495         if playermode != DLSTATUS_SEEDING:
496             playing_seeding_count = 0
497             for ds in playing_dslist:
498                  if ds.get_status() == DLSTATUS_SEEDING:
499                     playing_seeding_count += 1
500             if len(playing_dslist) == playing_seeding_count: 
501                     self.restart_other_downloads()
502
503         # cf. 25 Mbps cap to reduce CPU usage and improve playback on slow machines
504         # Arno: on some torrents this causes VLC to fail to tune into the video
505         # although it plays audio???
506         #ds.get_download().set_max_speed(DOWNLOAD,1500)
507     
508         
509         return (playing_dslist,totalhelping,totalspeed) 
510
511
512     def OnSetSysTrayTooltip(self,txt):         
513         if self.tbicon is not None:
514             self.tbicon.set_icon_tooltip(txt)
515
516     #
517     # Download Management
518     #
519     def restart_other_downloads(self):
520         """ Called by GUI thread """
521         if self.shuttingdown:
522             return
523         print >>sys.stderr,"main: Restarting other downloads"
524         self.playermode = DLSTATUS_SEEDING
525         self.ratelimiter = UserDefinedMaxAlwaysOtherwiseEquallyDividedRateManager()
526         self.set_ratelimits()
527
528         dlist = self.s.get_downloads()
529         for d in dlist:
530             if d not in self.downloads_in_vodmode:
531                 d.set_mode(DLMODE_NORMAL) # checkpointed torrents always restarted in DLMODE_NORMAL, just make extra sure
532                 d.restart() 
533
534
535     def remove_downloads_in_vodmode_if_not_complete(self):
536         print >>sys.stderr,"main: Removing playing download if not complete"
537         for d in self.downloads_in_vodmode:
538             d.set_state_callback(self.sesscb_remove_playing_callback)
539         
540     def sesscb_remove_playing_callback(self,ds):
541         """ Called by SessionThread """
542         
543         print >>sys.stderr,"main: sesscb_remove_playing_callback: status is",dlstatus_strings[ds.get_status()],"progress",ds.get_progress()
544         
545         d = ds.get_download()
546         name = d.get_def().get_name()
547         if (ds.get_status() == DLSTATUS_DOWNLOADING and ds.get_progress() >= 0.9) or ds.get_status() == DLSTATUS_SEEDING:
548             pass
549             print >>sys.stderr,"main: sesscb_remove_playing_callback: voting for KEEPING",`name`            
550         else:
551             print >>sys.stderr,"main: sesscb_remove_playing_callback: voting for REMOVING",`name`
552             if self.shuttingdown:
553                 # Arno, 2010-04-23: Do it now ourselves, wx won't do it anymore. Saves
554                 # hashchecking on sparse file on Linux.
555                 self.remove_playing_download(d)
556                 
557             wx.CallAfter(self.remove_playing_download,d)
558         
559         return (-1.0,False)
560         
561
562     def remove_playing_download(self,d):
563         """ Called by MainThread """
564         if self.s is not None:
565             print >>sys.stderr,"main: Removing incomplete download",`d.get_def().get_name_as_unicode()`
566             try:
567                 self.s.remove_download(d,removecontent=True)
568                 self.downloads_in_vodmode.remove(d)
569             except:
570                 print_exc()
571
572     def stop_playing_download(self,d):
573         """ Called by MainThread """
574         print >>sys.stderr,"main: Stopping download",`d.get_def().get_name_as_unicode()`
575         try:
576             d.stop()
577             self.downloads_in_vodmode.remove(d)
578         except:
579             print_exc()
580
581
582     #
583     # Rate limiter
584     #
585     def set_ratelimits(self):
586         uploadrate = float(self.playerconfig['total_max_upload_rate'])
587         print >>sys.stderr,"main: set_ratelimits: Setting max upload rate to",uploadrate
588         if self.ratelimiter is not None:
589             self.ratelimiter.set_global_max_speed(UPLOAD,uploadrate)
590             self.ratelimiter.set_global_max_seedupload_speed(uploadrate)
591
592     def ratelimit_callback(self,dslist):
593         """ When the player is in seeding mode, limit the used upload to
594         the limit set by the user via the options menu. 
595         Called by *GUI* thread """
596         if self.ratelimiter is None:
597             return
598
599         # Adjust speeds once every 4 seconds
600         adjustspeeds = False
601         if self.ratelimit_update_count % 4 == 0:
602             adjustspeeds = True
603         self.ratelimit_update_count += 1
604         
605         if adjustspeeds:
606             self.ratelimiter.add_downloadstatelist(dslist)
607             self.ratelimiter.adjust_speeds()
608
609
610     #
611     # Player config file
612     # 
613     def load_playerconfig(self,state_dir):
614         self.playercfgfilename = os.path.join(state_dir,'playerconf.pickle')
615         self.playerconfig = None
616         try:
617             f = open(self.playercfgfilename,"rb")
618             self.playerconfig = pickle.load(f)
619             f.close()
620         except:
621             print_exc()
622             self.playerconfig = {}
623             self.playerconfig['total_max_upload_rate'] = DEFAULT_MAX_UPLOAD_SEED_WHEN_SEEDING # KB/s
624
625     def save_playerconfig(self):
626         try:
627             f = open(self.playercfgfilename,"wb")
628             pickle.dump(self.playerconfig,f)
629             f.close()
630         except:
631             print_exc()
632             
633     def set_playerconfig(self,key,value):
634         self.playerconfig[key] = value
635         
636         if key == 'total_max_upload_rate':
637             try:
638                 self.set_ratelimits()
639             except:
640                 print_exc()
641     
642     def get_playerconfig(self,key):
643         return self.playerconfig[key]
644
645
646     #
647     # Shutdown
648     #
649     def OnExit(self):
650         print >>sys.stderr,"main: ONEXIT",currentThread().getName()
651         self.shuttingdown = True
652         self.remove_downloads_in_vodmode_if_not_complete()
653
654         # To let Threads in Session finish their business before we shut it down.
655         time.sleep(2) 
656         
657         if self.s is not None:
658             self.s.shutdown(hacksessconfcheckpoint=False)
659         
660         if self.tbicon is not None:
661             self.tbicon.RemoveIcon()
662             self.tbicon.Destroy()
663
664         ts = enumerate()
665         for t in ts:
666             print >>sys.stderr,"main: ONEXIT: Thread still running",t.getName(),"daemon",t.isDaemon()
667         
668         self.ExitMainLoop()
669
670     
671     def clear_session_state(self):
672         """ Try to fix apps by doing hard reset. Called from systray menu """
673         try:
674             if self.s is not None:
675                 dlist = self.s.get_downloads()
676                 for d in dlist:
677                     self.s.remove_download(d,removecontent=True)
678         except:
679             print_exc()
680         time.sleep(1) # give network thread time to do stuff
681         try:
682                 dldestdir = self.get_default_destdir()
683                 shutil.rmtree(dldestdir,True) # ignore errors
684         except:
685             print_exc()
686         try:
687                 dlcheckpointsdir = os.path.join(self.s.get_state_dir(),STATEDIR_DLPSTATE_DIR)
688                 shutil.rmtree(dlcheckpointsdir,True) # ignore errors
689         except:
690             print_exc()
691         try:
692                 cfgfilename = os.path.join(self.s.get_state_dir(),STATEDIR_SESSCONFIG)
693                 os.remove(cfgfilename)
694         except:
695             print_exc()
696
697         self.s = None # HARD EXIT
698         #self.OnExit()
699         sys.exit(0) # DIE HARD 4.0
700
701
702     def show_error(self,msg):
703         dlg = wx.MessageDialog(None, msg, self.appname+" Error", wx.OK|wx.ICON_ERROR)
704         result = dlg.ShowModal()
705         dlg.Destroy()
706         
707     
708     def get_default_destdir(self):
709         return os.path.join(self.s.get_state_dir(),'downloads')
710
711     
712     def is_svc(self, dlfile, tdef):
713         """ Ric: check if it as an SVC download. If it is add the enhancement 
714         layers to the dlfiles
715         """
716         svcfiles = None
717         
718         if tdef.is_multifile_torrent():
719             enhancement = tdef.get_files(exts=svcextdefaults)
720             # Ric: order the enhancement layer in the svcfiles list
721             # if the list of enhancements is not empty
722             if enhancement:
723                 enhancement.sort()
724                 if tdef.get_length(enhancement[0]) == tdef.get_length(dlfile):
725                     svcfiles = [dlfile]
726                     svcfiles.extend(enhancement)
727                 
728         return svcfiles
729
730     #
731     # InstanceConnectionHandler
732     #
733     def i2ithread_readlinecallback(self,ic,cmd):    
734         pass
735