home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / bin / gnome-btdownload < prev    next >
Encoding:
Text File  |  2007-03-29  |  42.0 KB  |  1,344 lines

  1. #!/usr/bin/python
  2.  
  3. # BitTorrent related modules.
  4. import BitTorrent.download, BitTorrent.bencode
  5.  
  6. # Various system and utility modules.
  7. import os, os.path, threading, sha, sys, time, re
  8.  
  9. # GTK+ and GNOME related modules.
  10. import gobject, gtk, gtk.glade, gnome, gnome.ui, gconf
  11. try:
  12.     # The new module name
  13.     import gnomevfs
  14. except:
  15.     import gnome.vfs
  16.     gnomevfs = gnome.vfs
  17.  
  18. # Gettext
  19. import gettext
  20. _ = gettext.gettext
  21.  
  22. # The name of this program.
  23. app_name         = 'gnome-btdownload'
  24.  
  25. # The version of this program.
  26. app_version      = '0.0.25'
  27.  
  28. # A hack that is set to a value that is the largest possible BitTorrent meta
  29. # file. This is passed to get_url to pull the entire (hopefully) meta file into
  30. # memory. I do this to prevent huge files from being loaded into memory.
  31. max_torrent_size = 0x400000 # 4 MB
  32.  
  33. # From RFC 2396, the regular expression for well-formed URIs
  34. rfc_2396_uri_regexp = r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?"
  35.  
  36. # If assigned, called with:
  37. #    type: string
  38. #        Describes the classification of the file to infer where it's
  39. #        stored.
  40. #        
  41. #        Currently valid options:
  42. #            * 'data'
  43. #    filename: string
  44. #        The name of the file for which to look.
  45. #    sub: string or None
  46. #        A potential sub-directory to check (and prefer) for the file.
  47. locate_file = None
  48.  
  49. # The 'GnomeProgram' for this process.
  50. gnome_program = gnome.program_init(app_name, app_version)
  51.  
  52. # Makes sure a URI is fully qualified and return the result. Return None if it
  53. # isn't a URI.
  54. def fix_up_uri(path):
  55.     try:
  56.         # 2 == GNOME_VFS_MAKE_URI_DIR_CURRENT, but isn't exported to python.
  57.         # This does not appear to support relative URIs, but does
  58.         # support relative paths, at least.
  59.         return gnomevfs.URI(gnomevfs.make_uri_from_input_with_dirs(path, 2))
  60.     except:
  61.         return None
  62.  
  63. # Checks if a path exists. This is still around from when this was a 'virtual'
  64. # function.
  65. def path_exists(path):
  66.     return gnomevfs.exists(path)
  67.  
  68. # GNOME wrapper
  69. def can_show_path(path):
  70.     return True
  71. def show_path(path):
  72.     gnome.url_show(str(fix_up_uri(path)))
  73.  
  74. # GNOME wrapper
  75. def get_home_dir():
  76.     return os.path.expanduser('~')
  77.  
  78. # Wrapper
  79. def get_config_dir():
  80.     home_dir = get_home_dir()
  81.     
  82.     if path_exists(os.path.join(home_dir, '.gnome2')):
  83.         return os.path.join(home_dir, '.gnome2', app_name)
  84.     else:
  85.         return os.path.join(home_dir, '.'+app_name)
  86. def make_config_dir():
  87.     home_dir   = get_home_dir()
  88.     config_dir = None
  89.     
  90.     if path_exists(os.path.join(home_dir, '.gnome2')):
  91.         config_dir = os.path.join(home_dir, '.gnome2', app_name)
  92.     else:
  93.         config_dir = os.path.join(home_dir, '.'+app_name)
  94.     
  95.     if not path_exists(config_dir):
  96.         gnomevfs.make_directory(config_dir, 00750)
  97.     
  98.     cache_dir = os.path.join(config_dir, 'cache')
  99.     
  100.     if not path_exists(cache_dir):
  101.         gnomevfs.make_directory(cache_dir, 00777)
  102.  
  103. # Disabled until gnome_program.locate_file is exported by gnome-python...
  104. #def locate_file(type filename, sub):
  105. #    FIXME gnome_program.locate_file(gnome.FILE_DOMAIN_APP_DATADIR, filename, True)
  106.  
  107. # Load at most read_bytes from a URI and return the result.
  108. def get_url(uri, read_bytes):
  109.     content = ''
  110.     total_size = 0
  111.     
  112.     try:
  113.         handle = gnomevfs.open(uri, gnomevfs.OPEN_READ)
  114.     except Exception, e:
  115.         print >> sys.stderr, 'gnomevfs.open failed with: url('+str(uri)+') e: '+str(e)
  116.         return None
  117.     try:
  118.         tmp = handle.read(read_bytes)
  119.         tmp_size = len(tmp)
  120.         
  121.         while(tmp_size < read_bytes - total_size):
  122.             content += tmp
  123.             total_size += tmp_size
  124.             
  125.             tmp = handle.read(read_bytes - total_size)
  126.             tmp_size = len(tmp)
  127.     except gnomevfs.EOFError:
  128.         pass
  129.     
  130.     return content
  131.  
  132. # Fallback wrapper
  133. if not locate_file:
  134.     def fallback_locate_attempt_prefixes(path):
  135.         prefixes = ['', 'usr/', 'usr/local/']
  136.  
  137.         # Try them locally
  138.         for prefix in prefixes:
  139.             if os.path.exists(prefix + path):
  140.                 return prefix + path;
  141.  
  142.         # Try them from root
  143.         for prefix in prefixes:
  144.             if os.path.exists('/' + prefix + path):
  145.                 return '/' + prefix + path;
  146.  
  147.         return None
  148.         
  149.     def fallback_locate_attempt(prefix, path, sub, filename):
  150.         if sub:
  151.             prefix_path_sub_file = fallback_locate_attempt_prefixes(prefix + '/' + path + '/' + sub + '/' + filename)
  152.             if prefix_path_sub_file:
  153.                 return prefix_path_sub_file
  154.         
  155.         prefix_path_file = fallback_locate_attempt_prefixes(prefix + '/' + path + '/' + filename)
  156.         if prefix_path_file:
  157.             return prefix_path_file
  158.         
  159.         return None
  160.  
  161.     def fallback_locate_file(type, filename, sub=None):
  162.         if type == 'data':
  163.             # Sources:   Common   Common         FreeBSD        FreeBSD
  164.             prefixes = ['share', 'local/share', 'share/gnome', 'X11R6/share/gnome']
  165.  
  166.             for prefix in prefixes:
  167.                 result = fallback_locate_attempt(prefix, app_name, sub, filename)
  168.                 if result:
  169.                     return result
  170.         
  171.         print >> sys.stderr, _("Couldn't locate file, will probably explode...")
  172.         return None
  173.     
  174.     if not locate_file:
  175.         locate_file = fallback_locate_file
  176.  
  177. # Converts a number of seconds into a short displayable string.
  178. def fmt_time_short(all):
  179.     minutes = int(all / 60)
  180.     seconds = all - (minutes * 60)
  181.     
  182.     return '%0.2u.%0.2u' % (minutes, seconds)
  183.  
  184. # Converts a number of seconds into a precise, but verbose displayable string.
  185. def fmt_time_long_precise_verbose(seconds):
  186.     seconds = int(seconds)
  187.     
  188.     days     = seconds / (60 * 60 * 24)
  189.     seconds -= days * (60 * 60 * 24)
  190.     
  191.     hours    = seconds / (60 * 60)
  192.     seconds -= hours * (60 * 60)
  193.     
  194.     minutes  = seconds / 60
  195.     seconds -= minutes * 60
  196.     
  197.     # FIXME Kind-of convoluted, not really locale friendly...
  198.     def create_listing(items):
  199.         def append_listing(listing, singular, plural, count, index, length):
  200.             initial = not listing
  201.             
  202.             if listing or count != 0 or index+1 == length:
  203.                 listing += str(count) + ' '
  204.                 
  205.                 if count == 1:
  206.                     listing += singular
  207.                 else:
  208.                     listing += plural
  209.                 
  210.                 
  211.                 if index+1 < length:
  212.                     if index+2 == length:
  213.                         if initial:
  214.                             listing += ' and '
  215.                         else:
  216.                             listing += ', and '
  217.                     else:
  218.                         listing += ', '
  219.             
  220.             return listing
  221.         
  222.         listing = ''
  223.         
  224.         for index, item in zip(range(0,len(items)), items):
  225.             listing = append_listing(listing, item[0][0], item[0][1], item[1], index, len(items))
  226.         
  227.         return listing
  228.     
  229.     return create_listing((
  230.         (('day', 'days'), days),
  231.         (('hour', 'hours'), hours),
  232.         (('minute', 'minutes'), minutes),
  233.         (('second', 'seconds'), seconds)
  234.     ))
  235.  
  236. # Converts a number of seconds into a rough estimate
  237. def fmt_time_long_estimate(seconds):
  238.     seconds = int(seconds)
  239.     
  240.     days     = seconds / (60 * 60 * 24)
  241.     seconds -= days * (60 * 60 * 24)
  242.     
  243.     hours    = seconds / (60 * 60)
  244.     seconds -= hours * (60 * 60)
  245.     
  246.     minutes  = seconds / 60
  247.     seconds -= minutes * 60
  248.     
  249.     if days > 1:
  250.         return _('About %i days') % days
  251.     elif days == 1:
  252.         return _('About %i day') % days
  253.     elif hours > 1:
  254.         return _('About %i hours') % hours
  255.     elif hours == 1:
  256.         return _('About %i hour') % hours
  257.     elif minutes > 1:
  258.         return _('About %i minutes') % minutes
  259.     elif minutes == 1:
  260.         return _('About %i minute') % minutes
  261.     elif seconds > 1:
  262.         return _('About %i seconds') % seconds
  263.     elif seconds == 1:
  264.         return _('About %i second') % seconds
  265.     elif seconds <= 0:
  266.         return _('About %i seconds') % seconds
  267.  
  268. fmt_time_long = fmt_time_long_estimate
  269.  
  270. # A GNOME HIG compliant error dialog wrapper.
  271. class GtkHigErrorDialog:
  272.     glade_xml = None
  273.     dialog    = None
  274.     
  275.     def run(self):
  276.         self.dialog.run()
  277.         self.dialog.destroy()
  278.     
  279.     def __init__(self, text, subtext='', modal=False):
  280.         self.glade_xml = gtk.glade.XML(locate_file('data', 'errdiag.glade', 'glade'))
  281.         
  282.         self.dialog = self.glade_xml.get_widget('dialog_hig_error')
  283.         
  284.         # It's no longer HIG-y to leave dialogs with empty titles. Oh,
  285.         # and no window managers play along either. So, we'll set the
  286.         # title to the error message for now.
  287.         self.dialog.set_title(text)
  288.         
  289.         self.glade_xml.get_widget('label_text').set_markup('<b>' + str(text) + '</b>')
  290.         self.glade_xml.get_widget('label_subtext').set_markup(subtext)
  291.         
  292.         self.dialog.set_modal(modal)
  293.  
  294. # A GNOME HIG complient "continue session?" dialog wrapper.
  295. class GtkHigContinueSessionDialog:
  296.     glade_xml = None
  297.     dialog    = None
  298.     
  299.     def run(self):
  300.         ret = self.dialog.run()
  301.         
  302.         self.dialog.destroy()
  303.         
  304.         if ret == gtk.RESPONSE_ACCEPT:
  305.             return True
  306.         elif ret == gtk.RESPONSE_CANCEL:
  307.             return False
  308.         else:
  309.             return None
  310.     
  311.     def __init__(self, previous, modal=False):
  312.         self.glade_xml = gtk.glade.XML(locate_file('data', 'contdiag.glade', 'glade'))
  313.         
  314.         self.dialog = self.glade_xml.get_widget('dialog_hig_continue')
  315.         
  316.         self.glade_xml.get_widget('label_previous').set_markup(previous)
  317.         
  318.         self.dialog.set_modal(modal)
  319.  
  320. # A base wrapper for open and save dialogs.
  321. class GtkFileActionDialog:
  322.     dialog = None
  323.     
  324.     def run(self):
  325.         ret = None
  326.         
  327.         if self.dialog.run() == gtk.RESPONSE_ACCEPT:
  328.             ret = self.dialog.get_filename()
  329.         
  330.         self.dialog.destroy()
  331.         
  332.         return ret
  333.     
  334.     def __init__(self, title, action, buttons, filters=None, default_name=None, default_path=None, modal=False, multiple=False, localonly=False):
  335.         self.dialog = gtk.FileChooserDialog(title, None, action, buttons)
  336.         
  337.         self.dialog.set_local_only(localonly)
  338.         self.dialog.set_select_multiple(multiple)
  339.         
  340.         if default_path:
  341.             self.dialog.set_current_folder(default_path)
  342.         
  343.         if default_name and (action == gtk.FILE_CHOOSER_ACTION_SAVE or action == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER):
  344.             self.dialog.set_current_name(default_name)
  345.         
  346.         if filters:
  347.             default_filter = None
  348.             
  349.             for default, name, type, etc in filters:
  350.                 filter = gtk.FileFilter()
  351.                 
  352.                 filter.set_name(name)
  353.                 
  354.                 if type == 'mime':
  355.                     filter.add_mime_type(etc)
  356.                 elif type == 'pattern':
  357.                     filter.add_pattern(etc)
  358.                 elif type == 'custom':
  359.                     filter.add_custom(etc)
  360.                 
  361.                 self.dialog.add_filter(filter)
  362.                 
  363.                 if default:
  364.                     default_filter = filter
  365.             
  366.             all_filter = gtk.FileFilter()
  367.             all_filter.set_name(_('All files'))
  368.             all_filter.add_pattern('*')
  369.             
  370.             if not default_filter:
  371.                 default_filter = all_filter
  372.             
  373.             self.dialog.add_filter(all_filter)
  374.             self.dialog.set_filter(default_filter)
  375.         
  376.         self.dialog.set_modal(modal)
  377.         self.dialog.show()
  378.  
  379. # A wrapper for the open file dialog.
  380. class GtkFileOpenDialog(GtkFileActionDialog):
  381.     def __init__(self, title, folder=False, filters=None, default=None, modal=False, multiple=False, localonly=False):
  382.         action = gtk.FILE_CHOOSER_ACTION_OPEN
  383.         
  384.         if folder:
  385.             action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
  386.         
  387.         GtkFileActionDialog.__init__(self, title, action, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT), filters, default, None, modal, multiple, localonly)
  388.  
  389. # A wrapper for the save file dialog.
  390. class GtkFileSaveDialog(GtkFileActionDialog):
  391.     def __init__(self, title, folder=False, filters=None, default_name=None, default_path=None, modal=False, multiple=False, localonly=False):
  392.         action = gtk.FILE_CHOOSER_ACTION_SAVE
  393.         
  394.         if folder:
  395.             action = gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER
  396.         
  397.         GtkFileActionDialog.__init__(self, title, action, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT), filters, default_name, default_path, modal, multiple, localonly)
  398.  
  399. # Manages a BitTorrent session's state into something consistent.
  400. class BtState:
  401.     # Handles the arguments passed to a BtState at initialization based
  402.     # upon command line arguments.
  403.     class Args:
  404.         def __init__(self, args, bt_swa=(
  405.             '-i',
  406.             '--ip',
  407.             '--bind',
  408.             '--keepalive_interval',
  409.             '--download_slice_size',
  410.             '--request_backlog',
  411.             '--max_message_length',
  412.             '--timeout',
  413.             '--timeout_check_interval',
  414.             '--max_slice_length',
  415.             '--max_rate_recalculate_interval',
  416.             '--max_rate_period',
  417.             '--upload_rate_fudge',
  418.             '--display_interval',
  419.             '--rerequest_interval',
  420.             '--http_timeout',
  421.             '--snub_time',
  422.             '--spew',
  423.             '--check_hashes',
  424.             '--report_hash_failures',
  425.             '--rarest_first_priority_cutoff'
  426.         )):
  427.             self.swa_args        = []
  428.             self.path_origin     = None
  429.             self.path_output     = None
  430.             self.min_port        = None
  431.             self.max_port        = None
  432.             self.max_uploads     = None
  433.             self.max_upload_rate = None
  434.             self.torrent_file    = None
  435.             self.torrent_info    = None
  436.             
  437.             # The number of arguments following the current one to ignore.
  438.             ignore = 0
  439.             
  440.             for i in range(0,len(args)):
  441.                 # If we're ignoring this argument, skip it.
  442.                 if ignore > 0:
  443.                     ignore -= 1
  444.                     continue
  445.                 
  446.                 if args[i] == '--saveas':
  447.                     # Use the value to know where to save the session.
  448.                     
  449.                     # Ignore the next argument, since we're
  450.                     # going to use it now.
  451.                     ignore = 1
  452.                     
  453.                     if i+1 < len(args):
  454.                         self.set_path_output(args[i+1])
  455.                 elif args[i] == '--responsefile' or args[i] == '--url':
  456.                     # Use the value to know where the meta
  457.                     # file is located.
  458.                     
  459.                     # Ignore the next argument, since we're
  460.                     # going to use it now.
  461.                     ignore = 1
  462.                     
  463.                     # Convert "--responsefile [path]" into
  464.                     # "--url [uri]" and get a suggested
  465.                     # path_output if possible or needed.
  466.                     if i+1 < len(args):
  467.                         self.set_path_origin(fix_up_uri(args[i+1]))
  468.                 elif args[i] == '--max_uploads':
  469.                     # Use the value for the maximum number
  470.                     # of peers to upload to.
  471.                     
  472.                     # Ignore the next argument, since we're
  473.                     # going to use it now.
  474.                     ignore = 1
  475.                     
  476.                     if i+1 < len(args):
  477.                         self.max_uploads = int(args[i+1])
  478.                 elif args[i] == '--max_upload_rate':
  479.                     # Use the value for the maximum rate in
  480.                     # kbps to upload to peers
  481.                     
  482.                     # Ignore the next argument, since we're
  483.                     # going to use it now.
  484.                     ignore = 1
  485.                     
  486.                     if i+1 < len(args):
  487.                         self.max_upload_rate = float(args[i+1])
  488.                 elif args[i] == '--minport':
  489.                     # Use the value for the minimum port in
  490.                     # the port range to use.
  491.                     
  492.                     # Ignore the next argument, since we're
  493.                     # going to use it now.
  494.                     ignore = 1
  495.                     
  496.                     if i+1 < len(args):
  497.                         self.min_port = int(args[i+1])
  498.                 elif args[i] == '--maxport':
  499.                     # Use the value for the maximum port in
  500.                     # the port range to use.
  501.                     
  502.                     # Ignore the next argument, since we're
  503.                     # going to use it now.
  504.                     ignore = 1
  505.                     
  506.                     if i+1 < len(args):
  507.                         self.max_port = int(args[i+1])
  508.                 elif args[i] in bt_swa:
  509.                     # This is some BitTorrent command line
  510.                     # switch, pass it on.
  511.                     
  512.                     # Ignore the next argument, since we're
  513.                     # going to use it now.
  514.                     ignore = 1
  515.                     
  516.                     if i+1 < len(args):
  517.                         self.swa_args.append(args[i])
  518.                         self.swa_args.append(args[i+1])
  519.                 elif re.match(r"(--)(.+)", args[i]):
  520.                     # This is some random command line
  521.                     # switch, ignore it.
  522.                     
  523.                     # Ignore the next argument, as well.
  524.                     ignore = 1
  525.                 else:
  526.                     # Assume any stray argument is the
  527.                     # path_origin as if passed to
  528.                     # --reponsefile or --url if it's a
  529.                     # valid URI.
  530.                     
  531.                     if not self.path_origin and i < len(args):
  532.                         fixed = fix_up_uri(args[i])
  533.                         
  534.                         if fixed:
  535.                             self.set_path_origin(fixed)
  536.         
  537.         # Returns the meta file's contents
  538.         def get_torrent_file(self):
  539.             if self.path_origin:
  540.                 if not self.torrent_file:
  541.                     try:
  542.                         self.torrent_file = get_url(self.path_origin, max_torrent_size)
  543.                     except:
  544.                         pass
  545.             
  546.             return self.torrent_file
  547.         
  548.         # Returns the meta file's information.
  549.         def get_torrent_info(self):
  550.             torrent_file = self.get_torrent_file()
  551.             
  552.             if torrent_file:
  553.                 if not self.torrent_info:
  554.                     try:
  555.                         self.torrent_info = BitTorrent.bencode.bdecode(torrent_file)
  556.                     except:
  557.                         pass
  558.             
  559.             return self.torrent_info
  560.         
  561.         # Suggest an output path from the input path and/or the meta
  562.         # file.
  563.         def find_suggested_path_output(self):
  564.             suggested_path_output = None
  565.             torrent_info          = self.get_torrent_info()
  566.             
  567.             if torrent_info:
  568.                 suggested_path_output = torrent_info['info']['name']
  569.             elif self.path_origin:
  570.                 suggested_path_output = self.path_origin.short_name
  571.                 
  572.                 if suggested_path_output[-len('.torrent'):] == '.torrent':
  573.                     suggested_path_output = suggested_path_output[:-len('.torrent')]
  574.             
  575.             return suggested_path_output
  576.         
  577.         # Tells if the torrent tracks multiple files by reading the
  578.         # meta file.
  579.         def test_has_multiple_files(self):
  580.             has_multiple_files = False
  581.             torrent_info       = self.get_torrent_info()
  582.             
  583.             if torrent_info:
  584.                 has_multiple_files = torrent_info['info'].has_key('files')
  585.             
  586.             return has_multiple_files
  587.         
  588.         # Set the path_origin and update anything that might depend
  589.         # upon it. Expects a GnomeVFSURI.
  590.         def set_path_origin(self, path_origin):
  591.             # Invalidate information retreived from any old
  592.             # path_origin, just in-case
  593.             self.torrent_file = None
  594.             self.torrent_info = None
  595.             
  596.             self.path_origin = path_origin
  597.         
  598.         # Set the path_output.
  599.         def set_path_output(self, path_output):
  600.             self.path_output = path_output
  601.         
  602.         # Set the min_port and max_port.
  603.         def set_ports(self, min_port, max_port):
  604.             self.min_port = min_port
  605.             self.max_port = max_port
  606.         
  607.         # Retreive the entire argument string
  608.         def get_args(self):
  609.             args = []
  610.             
  611.             if self.path_origin:
  612.                 args.append('--url')
  613.                 args.append(str(self.path_origin))
  614.             
  615.             if self.path_output:
  616.                 args.append('--saveas')
  617.                 args.append(self.path_output)
  618.             
  619.             if self.min_port and self.max_port:
  620.                 args.append('--minport')
  621.                 args.append(str(self.min_port))
  622.                 args.append('--maxport')
  623.                 args.append(str(self.max_port))
  624.             
  625.             return args + self.swa_args
  626.     
  627.     def __init__(self, args):
  628.         # BitTorrent module related information
  629.         self.path_origin     = args.path_origin # The URI of the meta file
  630.         self.size_total      = 0                # Total bytes of the download
  631.         self.args            = args.get_args()  # The command line arguments to pass to the BitTorrent module's download
  632.         # Local information
  633.         self.path_output     = ''               # The path to which the session is being downloaded.
  634.         # Transfer information
  635.         self.done            = False            # True if the download portion of the session is complete
  636.         self.event           = None             # Event used to flag the BitTorrent module to kill the session
  637.         self.thread          = None             # Thread running the BitTorrent module's download
  638.         self.activity        = None             # What the session is doing at the moment
  639.         self.time_begin      = 0.0              # When the current session began
  640.         self.time_remaining  = 0.0              # Estimated time remaining for the download to complete
  641.         self.dl_rate         = 0.0              # The current rate of download in bytes/sec
  642.         self.dl_amount       = 0                # Bytes downloaded from the current session
  643.         self.dl_pre_amount   = 0                # Bytes downloaded from previous sessions
  644.         self.ul_rate         = 0.0              # The current rate of upload in bytes/sec
  645.         self.ul_amount       = None             # Bytes uploaded from the current session (None for unknown)
  646.         self.ul_pre_amount   = 0                # Bytes uploaded from previous sessions
  647.         self.max_uploads     = 0                # Maximum number of peers to upload to
  648.         self.max_upload_rate = 0.0              # Maximum total bytes/sec to upload at once
  649.         # Implementation information
  650.         self.params          = {}               # Parameters passed from the BitTorrent module
  651.         self.params_pounce   = True             # If current settings still need to be reflected in the session
  652.     
  653.     # Return the corrected-for-multiple-sessions downloaded amount in bytes.
  654.     def get_dl_amount(self):
  655.         if self.activity == 'checking existing file':
  656.             return self.dl_amount
  657.         else:
  658.             return self.dl_amount + self.dl_pre_amount
  659.     
  660.     # Return the corrected-for-multiple-sessions uploaded amount in bytes.
  661.     def get_ul_amount(self):
  662.         if self.ul_amount:
  663.             return self.ul_amount + self.ul_pre_amount
  664.         elif self.ul_pre_amount > 0:
  665.             return self.ul_pre_amount
  666.         else:
  667.             return None
  668.     
  669.     # Pseudo-callback to update state when 'file' BitTorrent callback is called.
  670.     def file(self, default, size, saveas, dir):
  671.         self.done       = False
  672.         self.size_total = size
  673.         
  674.         if saveas:
  675.             self.path_output = os.path.abspath(saveas)
  676.         else:
  677.             self.path_output = os.path.abspath(default)
  678.         
  679.         return self.path_output
  680.     
  681.     # Pseudo-callback to update state when 'status' BitTorrent callback is called.
  682.     def status(self, dict):
  683.         if not self.done:
  684.             if dict.has_key('downRate'):
  685.                 self.dl_rate = float(dict['downRate'])
  686.             
  687.             dl_amount = None
  688.             if dict.has_key('downTotal'):
  689.                 dl_amount = long(dict['downTotal'] * (1 << 20))
  690.             elif dict.has_key('fractionDone'):
  691.                 dl_amount = long(float(dict['fractionDone']) * self.size_total)
  692.             if dl_amount:
  693.                 if dl_amount == 0:
  694.                     self.dl_pre_amount = self.dl_amount
  695.                 self.dl_amount = dl_amount
  696.             
  697.             if dict.has_key('timeEst'):
  698.                 self.time_remaining = float(dict['timeEst'])
  699.         
  700.         if dict.has_key('upRate'):
  701.             self.ul_rate = float(dict['upRate'])
  702.         
  703.         if dict.has_key('upTotal'):
  704.             self.ul_amount = long(dict['upTotal'] * (1 << 20))
  705.  
  706.         if dict.has_key('activity'):        
  707.             self.activity = dict['activity']
  708.  
  709.             # Incorporate the previous phase(s) in our download amount.
  710.             self.dl_pre_amount += self.dl_amount
  711.             self.dl_amount = 0
  712.     
  713.     # Pseudo-callback to update state when 'finished' BitTorrent callback is called.
  714.     def finished(self):
  715.         self.done = True
  716.         self.dl_amount = self.size_total - self.dl_pre_amount
  717.     
  718.     # Pseudo-callback to update state when 'path' BitTorrent callback is called.
  719.     def path(self, path):
  720.         self.path_output = path
  721.     
  722.     # Pseudo-callback to update state when 'param' BitTorrent callback is called.
  723.     def param(self, params):
  724.         if params:
  725.             self.params = params
  726.             
  727.             if self.params_pounce:
  728.                 self.cap_uploads(self.max_uploads)
  729.                 self.cap_upload_rate(self.max_upload_rate)
  730.                 
  731.                 self.params_pounce = False
  732.         else:
  733.             self.params = {}
  734.     
  735.     # Function to run in another thread.
  736.     def download_thread(self, file, status, finished, error, cols, path, param):
  737.         try:
  738.             # BitTorrent 3.3-style
  739.             BitTorrent.download.download(self.args, file, status, finished, error, self.event, cols, path, param)
  740.         except:
  741.             # BitTorrent 3.2-style
  742.             BitTorrent.download.download(self.args, file, status, finished, error, self.event, cols, path)
  743.     
  744.     # Start a session with the specified callbacks (which should each call
  745.     # BtState updaters).
  746.     def download(self, file, status, finished, error, cols, path, param, resuming=False):
  747.         self.done          = False
  748.         self.time_begin    = time.time()
  749.         self.event         = threading.Event()
  750.         self.thread        = threading.Thread(None, self.download_thread, 'bt_dl_thread', (file, status, finished, error, cols, path, param))
  751.         self.dl_rate       = 0.0
  752.         self.dl_amount     = 0
  753.         self.dl_pre_amount = 0
  754.         self.ul_rate       = 0.0
  755.         self.params        = None
  756.         self.params_pounce = True
  757.  
  758.         if resuming:
  759.             if self.ul_amount:
  760.                 self.ul_pre_amount += self.ul_amount
  761.         else:
  762.             self.ul_pre_amount = 0
  763.         self.ul_amount = None
  764.         
  765.         self.thread.start()
  766.     
  767.     # Try to end the BitTorrent session and wait for it to die before
  768.     # returning.
  769.     def join(self):
  770.         if self.event:
  771.             self.event.set()
  772.             self.event = None
  773.         if self.thread:
  774.             self.thread.join()
  775.             self.thread = None
  776.     
  777.     # Cap the number of peers to which you will upload.
  778.     def cap_uploads(self, uploads):
  779.         self.max_uploads = int(uploads)
  780.         
  781.         if self.params and self.params.has_key('max_uploads'):
  782.             self.params['max_uploads'](self.max_uploads)
  783.             return self.max_uploads
  784.         else:
  785.             return None
  786.     
  787.     # Cap the total bytes/sec of which you will upload.
  788.     def cap_upload_rate(self, upload_rate):
  789.         self.max_upload_rate = upload_rate
  790.         
  791.         if self.params and self.params.has_key('max_upload_rate'):
  792.             self.params['max_upload_rate'](int(self.max_upload_rate * (1 << 10)))
  793.             return int(self.max_upload_rate * (1 << 10))
  794.         else:
  795.             return None
  796.  
  797. # Persistance functions to resume a previously downloaded torrent
  798. def check_for_previous_save_location(bt_state_args):
  799.     config_dir = get_config_dir()
  800.     cache_dir = os.path.join(config_dir, 'cache')
  801.     
  802.     if path_exists(cache_dir):
  803.         torrent_file = bt_state_args.get_torrent_file()
  804.         
  805.         if torrent_file:
  806.             digest     = sha.new(torrent_file).hexdigest()
  807.             digest_url = os.path.join(cache_dir, digest)
  808.             
  809.             if path_exists(digest_url):
  810.                 previous_save_location = get_url(digest_url, max_torrent_size)
  811.                 
  812.                 if path_exists(fix_up_uri(previous_save_location)):
  813.                     return previous_save_location
  814.         else:
  815.             print >> sys.stderr, 'check_for_previous_save_location failed to get torrent_file'
  816.     else:
  817.         make_config_dir()
  818.  
  819. def cache_save_location(bt_state_args):
  820.     config_dir = get_config_dir()
  821.     cache_dir = os.path.join(config_dir, 'cache')
  822.     
  823.     if not path_exists(cache_dir):
  824.         make_config_dir()
  825.     
  826.     # Just to make sure
  827.     config_dir = get_config_dir()
  828.     cache_dir = os.path.join(config_dir, 'cache')
  829.     
  830.     torrent_file = bt_state_args.get_torrent_file()
  831.     
  832.     if torrent_file:
  833.         digest     = sha.new(torrent_file).hexdigest()
  834.         digest_url = os.path.join(cache_dir, digest)
  835.         
  836.         digest_file = file(digest_url, 'w')
  837.         digest_file.write(bt_state_args.path_output)
  838.         digest_file.close()
  839.     else:
  840.         print >> sys.stderr, 'cache_save_location failed to get torrent_file'
  841.  
  842. class GtkClient:
  843.     def __init__(self, args):
  844.         # Miscellaneous events that have happened in this process's
  845.         # BitTorrent sessions.
  846.         self.bt_events = []
  847.         
  848.         # Time that the last error dialog was displayed
  849.         self.last_error = 0
  850.         
  851.         # Localization Setup
  852.         gtk.glade.bindtextdomain(app_name, '/usr/share/locale')
  853.         gtk.glade.textdomain(app_name)
  854.         gettext.bindtextdomain(app_name, '/usr/share/locale')
  855.         gettext.textdomain(app_name)
  856.         
  857.         # Gtk+ Setup
  858.         gtk.threads_init()
  859.         
  860.         # GConf Setup
  861.         self.gconf_client = gconf.client_get_default()
  862.         
  863.         self.gconf_client.add_dir('/apps/'+app_name, gconf.CLIENT_PRELOAD_RECURSIVE)
  864.         #self.gconf_client.notify_add('/apps/'+app_name+'/settings', self.on_gconf_settings_notify)
  865.         
  866.         # Bt Setup
  867.         bt_state_args = BtState.Args(args[1:])
  868.         
  869.         if not bt_state_args.path_origin or not path_exists(bt_state_args.path_origin):
  870.             filters = ((True, _('BitTorrent meta files'), 'mime', 'application/x-bittorrent'), )
  871.             result = GtkFileOpenDialog(_('Open location for BitTorrent meta file'), filters=filters, modal=True).run()
  872.             
  873.             if result:
  874.                 bt_state_args.set_path_origin(fix_up_uri(result))
  875.             else:
  876.                 # They hit Cancel
  877.                 sys.exit(1)
  878.         
  879.         if not bt_state_args.path_output:
  880.             previous_save_location = check_for_previous_save_location(bt_state_args)
  881.             
  882.             if previous_save_location:
  883.                 ret = GtkHigContinueSessionDialog(previous_save_location, modal=True).run()
  884.                 
  885.                 if ret == None:
  886.                     # They closed the window without an answer
  887.                     sys.exit(2)
  888.                 elif ret:
  889.                     bt_state_args.set_path_output(previous_save_location)
  890.         
  891.         if not bt_state_args.path_output:
  892.             previous_path = None
  893.             default = None
  894.             
  895.             # Look up the previous save path.
  896.             try:
  897.                 previous_path = self.gconf_client.get_string('/apps/'+app_name+'/previous_path')
  898.                 
  899.                 if not os.path.isdir(previous_path):
  900.                     previous_path = None
  901.             except:
  902.                 pass
  903.             
  904.             # Run Gtk+ file selector; localonly=True due to
  905.             # BitTorrent.
  906.             result = GtkFileSaveDialog(_('Save location for BitTorrent session'), default_name=bt_state_args.find_suggested_path_output(), default_path=previous_path, modal=True, localonly=True, folder=bt_state_args.test_has_multiple_files()).run()
  907.             
  908.             if result:
  909.                 bt_state_args.set_path_output(result)
  910.                 
  911.                 # Save the new path
  912.                 try:
  913.                     self.gconf_client.set_string('/apps/'+app_name+'/previous_path', os.path.dirname(result))
  914.                 except:
  915.                     pass
  916.                 
  917.                 cache_save_location(bt_state_args)
  918.             else:
  919.                 # They hit Cancel
  920.                 sys.exit(3)
  921.         
  922.         if not bt_state_args.min_port or not bt_state_args.max_port:
  923.             min_port = self.gconf_client.get_int('/apps/'+app_name+'/settings/min_port')
  924.             max_port = self.gconf_client.get_int('/apps/'+app_name+'/settings/max_port')
  925.             
  926.             if min_port and max_port:
  927.                 bt_state_args.set_ports(min_port, max_port)
  928.         
  929.         self.bt_state = BtState(bt_state_args)
  930.         
  931.         # Run Gtk+ main window
  932.         self.glade_xml = gtk.glade.XML(locate_file('data', 'dlsession.glade', 'glade'))
  933.     
  934.         self.setup_treeview_events()
  935.         
  936.         self.glade_xml.signal_autoconnect({
  937.             'on_window_main_destroy':
  938.                 self.on_window_main_destroy,
  939.             'on_button_open_clicked':
  940.                 self.on_button_open_clicked,
  941.             'on_button_resume_clicked':
  942.                 self.on_button_resume_clicked,
  943.             'on_button_stop_clicked':
  944.                 self.on_button_stop_clicked,
  945.             'on_button_close_clicked':
  946.                 self.on_button_close_clicked,
  947.             'on_checkbutton_cap_uploads_toggled':
  948.                 self.on_checkbutton_cap_uploads_toggled,
  949.             'on_spinbutton_cap_uploads_value_changed':
  950.                 self.on_spinbutton_cap_uploads_value_changed,
  951.             'on_checkbutton_cap_upload_rate_toggled':
  952.                 self.on_checkbutton_cap_upload_rate_toggled,
  953.             'on_spinbutton_cap_upload_rate_value_changed':
  954.                 self.on_spinbutton_cap_upload_rate_value_changed,
  955.             'on_button_events_clear_clicked':
  956.                 self.on_button_events_clear_clicked,
  957.             'on_checkbutton_events_display_error_dialogs_toggled':
  958.                 self.on_checkbutton_events_display_error_dialogs_toggled
  959.         })
  960.         
  961.         self.glade_xml.get_widget('label_download_address_output').set_text(gnomevfs.format_uri_for_display(str(self.bt_state.path_origin)))
  962.         self.glade_xml.get_widget('window_main').set_title(os.path.basename(self.bt_state.path_output))
  963.         
  964.         # Set GUI preferences
  965.         display_error_dialogs = self.gconf_client.get_bool('/apps/'+app_name+'/settings/display_error_dialogs')
  966.         if display_error_dialogs != None:
  967.             self.glade_xml.get_widget('checkbutton_events_display_error_dialogs').set_active(display_error_dialogs)
  968.         
  969.         if bt_state_args.max_uploads:
  970.             self.glade_xml.get_widget('spinbutton_cap_uploads').set_value(bt_state_args.max_uploads)
  971.             self.glade_xml.get_widget('checkbutton_cap_uploads').set_active(True)
  972.         else:
  973.             cap_upload_peers = self.gconf_client.get_int('/apps/'+app_name+'/settings/cap_upload_peers')
  974.             if cap_upload_peers != None:
  975.                 self.glade_xml.get_widget('spinbutton_cap_uploads').set_value(cap_upload_peers)
  976.             
  977.             cap_upload = self.gconf_client.get_bool('/apps/'+app_name+'/settings/cap_upload')
  978.             if cap_upload != None:
  979.                 self.glade_xml.get_widget('checkbutton_cap_uploads').set_active(cap_upload)
  980.         
  981.         cap_upload_rate_kbps = self.gconf_client.get_float('/apps/'+app_name+'/settings/cap_upload_rate_kbps')
  982.         if cap_upload_rate_kbps != None:
  983.             self.glade_xml.get_widget('spinbutton_cap_upload_rate').set_value(cap_upload_rate_kbps)
  984.         
  985.         if bt_state_args.max_upload_rate != None:
  986.             if bt_state_args.max_upload_rate > 0:
  987.                 self.glade_xml.get_widget('spinbutton_cap_upload_rate').set_value(bt_state_args.max_upload_rate)
  988.                 self.glade_xml.get_widget('checkbutton_cap_upload_rate').set_active(True)
  989.             else:
  990.                 self.glade_xml.get_widget('checkbutton_cap_upload_rate').set_active(False)
  991.         else:
  992.             cap_upload_rate = self.gconf_client.get_bool('/apps/'+app_name+'/settings/cap_upload_rate')
  993.             if cap_upload_rate != None:
  994.                 self.glade_xml.get_widget('checkbutton_cap_upload_rate').set_active(cap_upload_rate)
  995.         
  996.         # Save arguments for session recovery
  997.         self.session_recovery_cwd  = os.getcwd()
  998.         self.session_recovery_args = args[:1] + bt_state_args.get_args()
  999.         
  1000.         # Setup session
  1001.         master_client = gnome.ui.master_client()
  1002.         
  1003.         master_client.connect('save-yourself', self.on_gc_save_yourself, None)
  1004.         master_client.connect('die', self.on_gc_die, None)
  1005.         
  1006.         master_client.set_restart_style(gnome.ui.RESTART_IF_RUNNING)
  1007.         
  1008.         # Run Bt
  1009.         self.run_bt()
  1010.         
  1011.         # Run Gtk+
  1012.         gtk.main()
  1013.     
  1014.     # Appends an event to the log.
  1015.     def log_event(self, type, text):
  1016.         t = fmt_time_short(time.time() - self.bt_state.time_begin)
  1017.         
  1018.         notebook_main = self.glade_xml.get_widget('notebook_main')
  1019.         vbox_events   = self.glade_xml.get_widget('vbox_events')
  1020.         events_tab    = notebook_main.page_num(vbox_events)
  1021.         
  1022.         # Check if the user is looking at the events tab. If so, do not
  1023.         # pop up an error dialog.
  1024.         if events_tab != None and notebook_main.get_current_page() != events_tab:
  1025.             if type == 'Error':
  1026.                 # Select the events tab now (this will also prevent
  1027.                 # more error dialogs from popping up).
  1028.                 #
  1029.                 # Say it with me: "worst solution EVER". :P
  1030.                 notebook_main.set_current_page(events_tab)
  1031.                 
  1032.                 if self.glade_xml.get_widget('checkbutton_events_display_error_dialogs').get_active():
  1033.                     # Try to specially adapt the error message.
  1034.                     try:
  1035.                         mo = re.search(r"([A-Za-z ',]+) - [<]?([^>]+)[>]?", str(text))
  1036.                         
  1037.                         GtkHigErrorDialog(mo.group(1), mo.group(2)).run()
  1038.                     except:
  1039.                         GtkHigErrorDialog(str(text)).run()
  1040.         
  1041.         if self.bt_events:
  1042.             treeview_events = self.glade_xml.get_widget('treeview_events')
  1043.             
  1044.             # Scroll to the newly added event
  1045.             iter = self.bt_events.append((t, type, text))
  1046.             path = treeview_events.get_model().get_path(iter)
  1047.             treeview_events.scroll_to_cell(path)
  1048.         else:
  1049.             print >> sys.stderr, i18n('%s, %s: %s' % (t, type, text))
  1050.     
  1051.     # BitTorrent callbacks
  1052.     def on_bt_file(self, default, size, saveas, dir):
  1053.         path = self.bt_state.file(default, size, saveas, dir)
  1054.         
  1055.         gtk.threads_enter()
  1056.         
  1057.         label_download_file_output = self.glade_xml.get_widget('label_download_file_output')
  1058.         label_download_file_output.set_text(path)
  1059.         
  1060.         gtk.threads_leave()
  1061.         
  1062.         return path
  1063.     
  1064.     def on_bt_status(self, dict = {}, fractionDone = None, timeEst = None, downRate = None, upRate = None, activity = None):
  1065.         # To support BitTorrent 3.2, pack anything supplied seperately
  1066.         # from dict into dict.
  1067.         if fractionDone:
  1068.             dict['fractionDone'] = fractionDone
  1069.         if timeEst:
  1070.             dict['timeEst'] = timeEst
  1071.         if downRate:
  1072.             dict['downRate'] = downRate
  1073.         if upRate:
  1074.             dict['upRate'] = upRate
  1075.         if activity:
  1076.             dict['activity'] = activity
  1077.         
  1078.         self.bt_state.status(dict)
  1079.         
  1080.         gtk.threads_enter()
  1081.         
  1082.         label_download_elapsed_output = self.glade_xml.get_widget('label_download_time_elapsed_output')
  1083.         label_download_elapsed_output.set_text(fmt_time_long_precise_verbose(time.time() - self.bt_state.time_begin))
  1084.         
  1085.         if dict.has_key('fractionDone'):
  1086.             progressbar_download_status = self.glade_xml.get_widget('progressbar_download_status')
  1087.             window_main = self.glade_xml.get_widget('window_main')
  1088.             
  1089.             perc_string = str(int(dict['fractionDone'] * 100)) + '%'
  1090.             
  1091.             progressbar_download_status.set_fraction(dict['fractionDone'])
  1092.             progressbar_download_status.set_text(perc_string)
  1093.             window_main.set_title(perc_string + ' of ' + os.path.basename(self.bt_state.path_output))
  1094.         
  1095.         if dict.has_key('downTotal') or dict.has_key('fractionDone'):
  1096.             label_download_status_output = self.glade_xml.get_widget('label_download_status_output')
  1097.             
  1098.             label_download_status_output.set_text('%.1f of %.1f MB at %.2f KB/s' %
  1099.                 (float(self.bt_state.get_dl_amount()) / (1 << 20),
  1100.                  float(self.bt_state.size_total)      / (1 << 20),
  1101.                  float(self.bt_state.dl_rate)         / (1 << 10)))
  1102.             
  1103.         if dict.has_key('timeEst'):
  1104.             label_download_time_remaining_output = self.glade_xml.get_widget('label_download_time_remaining_output')
  1105.             
  1106.             label_download_time_remaining_output.set_text(fmt_time_long(dict['timeEst']))
  1107.         
  1108.         if dict.has_key('upRate') or dict.has_key('upTotal'):
  1109.             label_upload_status_output = self.glade_xml.get_widget('label_upload_status_output')
  1110.             
  1111.             if self.bt_state.get_ul_amount():
  1112.                 label_upload_status_output.set_text(_('%.1f MB at %.2f KB/s') %
  1113.                     (float(self.bt_state.get_ul_amount()) / (1 << 20),
  1114.                      float(self.bt_state.ul_rate)         / (1 << 10)))
  1115.             else:
  1116.                 label_upload_status_output.set_text('%.2f KB/s' %
  1117.                     (float(self.bt_state.ul_rate) / (1 << 10)))
  1118.         
  1119.         if dict.has_key('activity'):
  1120.             self.log_event('Activity', dict['activity'])
  1121.         
  1122.         gtk.threads_leave()
  1123.     
  1124.     def on_bt_finished(self):
  1125.         self.bt_state.finished()
  1126.         self.on_bt_status({'fractionDone': float(1.0), 'timeEst': 0, 'activity': 'finished'})
  1127.         
  1128.         gtk.threads_enter()
  1129.         
  1130.         progressbar_download_status          = self.glade_xml.get_widget('progressbar_download_status')
  1131.         label_download_time_remaining_output = self.glade_xml.get_widget('label_download_time_remaining_output')
  1132.         button_open                          = self.glade_xml.get_widget('button_open')
  1133.         
  1134.         progressbar_download_status.set_fraction(1.0)
  1135.         progressbar_download_status.set_text('100%')
  1136.         label_download_time_remaining_output.set_text(_('None'))
  1137.         
  1138.         # Check if the completed session can be 'shown'
  1139.         if can_show_path and show_path and can_show_path(self.bt_state.path_output):
  1140.             button_open.set_sensitive(True)
  1141.         
  1142.         gtk.threads_leave()
  1143.     
  1144.     def on_bt_error(self, msg):
  1145.         gtk.threads_enter()
  1146.         
  1147.         self.log_event('Error', msg)
  1148.         
  1149.         gtk.threads_leave()
  1150.     
  1151.     def on_bt_path(self, path):
  1152.         self.bt_state.path(path)
  1153.         
  1154.         gtk.threads_enter()
  1155.         
  1156.         label_download_file_output = self.glade_xml.get_widget('label_download_file_output')
  1157.         label_download_file_output.set_text(self.bt_state.path_output)
  1158.         
  1159.         gtk.threads_leave()
  1160.     
  1161.     def on_bt_param(self, params):
  1162.         self.bt_state.param(params)
  1163.         
  1164.         gtk.threads_enter()
  1165.         
  1166.         checkbutton_cap_uploads = self.glade_xml.get_widget('checkbutton_cap_uploads')
  1167.         spinbutton_cap_uploads  = self.glade_xml.get_widget('spinbutton_cap_uploads')
  1168.         checkbutton_cap_upload_rate = self.glade_xml.get_widget('checkbutton_cap_upload_rate')
  1169.         spinbutton_cap_upload_rate  = self.glade_xml.get_widget('spinbutton_cap_upload_rate')
  1170.         
  1171.         if params.has_key('max_uploads'):
  1172.             checkbutton_cap_uploads.set_sensitive(True)
  1173.             spinbutton_cap_uploads.set_sensitive(True)
  1174.         else:
  1175.             checkbutton_cap_uploads.set_sensitive(False)
  1176.             spinbutton_cap_uploads.set_sensitive(False)
  1177.         
  1178.         if params.has_key('max_upload_rate'):
  1179.             checkbutton_cap_upload_rate.set_sensitive(True)
  1180.             spinbutton_cap_upload_rate.set_sensitive(True)
  1181.         else:
  1182.             checkbutton_cap_upload_rate.set_sensitive(False)
  1183.             spinbutton_cap_upload_rate.set_sensitive(False)
  1184.         
  1185.         gtk.threads_leave()
  1186.     
  1187.     # GnomeClient callbacks
  1188.     def on_gc_save_yourself(self, client, phase, save_style, is_shutdown, interact_style, is_fast, data=None):
  1189.         master_client = gnome.ui.master_client()
  1190.         
  1191.         args = self.session_recovery_args[:]
  1192.         
  1193.         checkbutton_cap_uploads = self.glade_xml.get_widget('checkbutton_cap_uploads')
  1194.         spinbutton_cap_uploads  = self.glade_xml.get_widget('spinbutton_cap_uploads')
  1195.         checkbutton_cap_upload_rate = self.glade_xml.get_widget('checkbutton_cap_upload_rate')
  1196.         spinbutton_cap_upload_rate  = self.glade_xml.get_widget('spinbutton_cap_upload_rate')
  1197.         
  1198.         if checkbutton_cap_uploads.get_active():
  1199.             args.append('--max_uploads')
  1200.             args.append(str(spinbutton_cap_uploads.get_value()))
  1201.         
  1202.         if checkbutton_cap_upload_rate.get_active():
  1203.             args.append('--max_upload_rate')
  1204.             args.append(str(spinbutton_cap_upload_rate.get_value()))
  1205.         
  1206.         master_client.set_current_directory(self.session_recovery_cwd)
  1207.         master_client.set_clone_command(len(args), args)
  1208.         master_client.set_restart_command(len(args), args)
  1209.         
  1210.         return True
  1211.     
  1212.     def on_gc_die(self, client, data=None):
  1213.         self.join()
  1214.         gtk.main_quit()
  1215.     
  1216.     # GTK+ callbacks
  1217.     def on_window_main_destroy(self, widget, data=None):
  1218.         self.join()
  1219.         gtk.main_quit()
  1220.     
  1221.     def on_button_open_clicked(self, widget, data=None):
  1222.         if show_path:
  1223.             show_path(self.bt_state.path_output)
  1224.     
  1225.     def on_button_resume_clicked(self, widget, data=None):
  1226.         button_resume = self.glade_xml.get_widget('button_resume')
  1227.         button_stop   = self.glade_xml.get_widget('button_stop')
  1228.         button_close  = self.glade_xml.get_widget('button_close')
  1229.         
  1230.         button_resume.set_sensitive(False)
  1231.         button_stop.show()
  1232.         button_close.hide()
  1233.         
  1234.         self.run_bt(resuming=True)
  1235.     
  1236.     def on_button_stop_clicked(self, widget, data=None):
  1237.         self.join()
  1238.         
  1239.         button_resume = self.glade_xml.get_widget('button_resume')
  1240.         button_stop   = self.glade_xml.get_widget('button_stop')
  1241.         button_close  = self.glade_xml.get_widget('button_close')
  1242.         
  1243.         button_resume.set_sensitive(True)
  1244.         button_stop.hide()
  1245.         button_close.show()
  1246.     
  1247.     def on_button_close_clicked(self, widget, data=None):
  1248.         window_main = self.glade_xml.get_widget('window_main')
  1249.         window_main.destroy()
  1250.     
  1251.     def on_checkbutton_cap_uploads_toggled(self, widget, data=None):
  1252.         spinbutton_cap_uploads = self.glade_xml.get_widget('spinbutton_cap_uploads')
  1253.         
  1254.         try:
  1255.             if widget.get_active():
  1256.                 self.bt_state.cap_uploads(int(spinbutton_cap_uploads.get_value()))
  1257.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload', True)
  1258.             else:
  1259.                 self.bt_state.cap_uploads(0)
  1260.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload', False)
  1261.         except:
  1262.             pass
  1263.     
  1264.     def on_spinbutton_cap_uploads_value_changed(self, widget, data=None):
  1265.         checkbutton_cap_uploads = self.glade_xml.get_widget('checkbutton_cap_uploads')
  1266.         
  1267.         try:
  1268.             self.gconf_client.set_int('/apps/'+app_name+'/settings/cap_upload_peers', int(widget.get_value()))
  1269.         except:
  1270.             pass
  1271.         
  1272.         if checkbutton_cap_uploads.get_active():
  1273.             self.bt_state.cap_uploads(int(widget.get_value()))
  1274.     
  1275.     def on_checkbutton_cap_upload_rate_toggled(self, widget, data=None):
  1276.         spinbutton_cap_upload_rate = self.glade_xml.get_widget('spinbutton_cap_upload_rate')
  1277.         
  1278.         try:
  1279.             if widget.get_active():
  1280.                 self.bt_state.cap_upload_rate(spinbutton_cap_upload_rate.get_value())
  1281.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload_rate', True)
  1282.             else:
  1283.                 self.bt_state.cap_upload_rate(0)
  1284.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload_rate', False)
  1285.         except:
  1286.             pass
  1287.     
  1288.     def on_spinbutton_cap_upload_rate_value_changed(self, widget, data=None):
  1289.         checkbutton_cap_upload_rate = self.glade_xml.get_widget('checkbutton_cap_upload_rate')
  1290.         
  1291.         try:
  1292.             self.gconf_client.set_float('/apps/'+app_name+'/settings/cap_upload_rate_kbps', float(widget.get_value()))
  1293.         except:
  1294.             pass
  1295.         
  1296.         if checkbutton_cap_upload_rate.get_active():
  1297.             self.bt_state.cap_upload_rate(widget.get_value())
  1298.     
  1299.     def on_button_events_clear_clicked(self, widget, data=None):
  1300.         if self.bt_events:
  1301.             self.bt_events.clear()
  1302.     
  1303.     def on_checkbutton_events_display_error_dialogs_toggled(self, widget, data=None):
  1304.         try:
  1305.             if widget.get_active():
  1306.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/display_error_dialogs', True)
  1307.             else:
  1308.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/display_error_dialogs', False)
  1309.         except:
  1310.             pass
  1311.     
  1312.     # GTK+ setup stuff to supliment Glade.
  1313.     def setup_treeview_events(self):
  1314.         treeview_events = self.glade_xml.get_widget('treeview_events')
  1315.         
  1316.         list_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
  1317.         
  1318.         treeview_events.set_model(list_store)
  1319.         
  1320.         treeview_events.append_column(gtk.TreeViewColumn('Time', gtk.CellRendererText(), text=0))
  1321.         treeview_events.append_column(gtk.TreeViewColumn('Type', gtk.CellRendererText(), text=1))
  1322.         treeview_events.append_column(gtk.TreeViewColumn('Text', gtk.CellRendererText(), text=2))
  1323.         
  1324.         self.bt_events = list_store
  1325.     
  1326.     # Helpful wrapper to start BitTorrent session.
  1327.     def run_bt(self, resuming=False):
  1328.         self.bt_state.download(self.on_bt_file, self.on_bt_status, self.on_bt_finished, self.on_bt_error, 100, self.on_bt_path, self.on_bt_param, resuming=resuming)
  1329.     
  1330.     # Helpful wrapper to end BitTorrent session.
  1331.     def join(self):
  1332.         if self.bt_state:
  1333.             self.bt_state.join()
  1334.  
  1335. # Start the client
  1336. def run(args):
  1337.     client = GtkClient(args)
  1338.  
  1339. # Automatically start the client as long as this isn't being used as a module
  1340. # for some reason.
  1341. if __name__ == '__main__':
  1342.     gtk.window_set_default_icon_from_file('/usr/share/gnome-btdownload/pixmaps/download.png')
  1343.     run(sys.argv)
  1344.