home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / bin / gnome-btdownload < prev    next >
Encoding:
Text File  |  2006-07-09  |  40.5 KB  |  1,312 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.24'
  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.         mo = re.search(rfc_2396_uri_regexp, path)
  57.         
  58.         if mo and (mo.group(2) == 'file' or mo.group(2) == None):
  59.             path = os.path.abspath(mo.group(5))
  60.     except:
  61.         pass
  62.     try:
  63.         uri = gnomevfs.URI(path)
  64.         
  65.         return str(uri)
  66.     except:
  67.         return None
  68.  
  69. # Checks if a path exists. This is still around from when this was a 'virtual'
  70. # function.
  71. def path_exists(path):
  72.     return gnomevfs.exists(path)
  73.  
  74. # GNOME wrapper
  75. def can_show_path(path):
  76.     return True
  77. def show_path(path):
  78.     gnome.url_show(fix_up_uri(path))
  79.  
  80. # GNOME wrapper
  81. def get_home_dir():
  82.     return os.path.expanduser('~')
  83.  
  84. # Wrapper
  85. def get_config_dir():
  86.     home_dir = get_home_dir()
  87.     
  88.     if path_exists(os.path.join(home_dir, '.gnome2')):
  89.         return os.path.join(home_dir, '.gnome2', app_name)
  90.     else:
  91.         return os.path.join(home_dir, '.'+app_name)
  92. def make_config_dir():
  93.     home_dir   = get_home_dir()
  94.     config_dir = None
  95.     
  96.     if path_exists(os.path.join(home_dir, '.gnome2')):
  97.         config_dir = os.path.join(home_dir, '.gnome2', app_name)
  98.     else:
  99.         config_dir = os.path.join(home_dir, '.'+app_name)
  100.     
  101.     if not path_exists(config_dir):
  102.         gnomevfs.make_directory(config_dir, 00750)
  103.     
  104.     cache_dir = os.path.join(config_dir, 'cache')
  105.     
  106.     if not path_exists(cache_dir):
  107.         gnomevfs.make_directory(cache_dir, 00777)
  108.  
  109. # Disabled until gnome_program.locate_file is exported by gnome-python...
  110. #def locate_file(type filename, sub):
  111. #    FIXME gnome_program.locate_file(gnome.FILE_DOMAIN_APP_DATADIR, filename, True)
  112.  
  113. # Load at most read_bytes from a URI and return the result.
  114. def get_url(uri, read_bytes):
  115.     content = ''
  116.     total_size = 0
  117.     
  118.     handle = gnomevfs.open(uri, gnomevfs.OPEN_READ)
  119.     
  120.     try:
  121.         tmp = handle.read(read_bytes)
  122.         tmp_size = len(tmp)
  123.         
  124.         while(tmp_size < read_bytes - total_size):
  125.             content += tmp
  126.             total_size += tmp_size
  127.             
  128.             tmp = handle.read(read_bytes - total_size)
  129.             tmp_size = len(tmp)
  130.     except gnomevfs.EOFError:
  131.         pass
  132.     
  133.     return content
  134.  
  135. # Fallback wrapper
  136. if not locate_file:
  137.     def fallback_locate_attempt_prefixes(path):
  138.         prefixes = ['', 'usr/', 'usr/local/']
  139.  
  140.         # Try them locally
  141.         for prefix in prefixes:
  142.             if os.path.exists(prefix + path):
  143.                 return prefix + path;
  144.  
  145.         # Try them from root
  146.         for prefix in prefixes:
  147.             if os.path.exists('/' + prefix + path):
  148.                 return '/' + prefix + path;
  149.  
  150.         return None
  151.         
  152.     def fallback_locate_attempt(prefix, path, sub, filename):
  153.         if sub:
  154.             prefix_path_sub_file = fallback_locate_attempt_prefixes(prefix + '/' + path + '/' + sub + '/' + filename)
  155.             if prefix_path_sub_file:
  156.                 return prefix_path_sub_file
  157.         
  158.         prefix_path_file = fallback_locate_attempt_prefixes(prefix + '/' + path + '/' + filename)
  159.         if prefix_path_file:
  160.             return prefix_path_file
  161.         
  162.         return None
  163.  
  164.     def fallback_locate_file(type, filename, sub=None):
  165.         if type == 'data':
  166.             # Sources:   Common   Common         FreeBSD        FreeBSD
  167.             prefixes = ['share', 'local/share', 'share/gnome', 'X11R6/share/gnome']
  168.  
  169.             for prefix in prefixes:
  170.                 result = fallback_locate_attempt(prefix, app_name, sub, filename)
  171.                 if result:
  172.                     return result
  173.         
  174.         print >> sys.stderr, _("Couldn't locate file, will probably explode...")
  175.         return None
  176.     
  177.     if not locate_file:
  178.         locate_file = fallback_locate_file
  179.  
  180. # Converts a number of seconds into a short displayable string.
  181. def fmt_time_short(all):
  182.     minutes = int(all / 60)
  183.     seconds = all - (minutes * 60)
  184.     
  185.     return '%0.2u.%0.2u' % (minutes, seconds)
  186.  
  187. # Converts a number of seconds into a precise, but verbose displayable string.
  188. def fmt_time_long_precise_verbose(seconds):
  189.     seconds = int(seconds)
  190.     
  191.     days     = seconds / (60 * 60 * 24)
  192.     seconds -= days * (60 * 60 * 24)
  193.     
  194.     hours    = seconds / (60 * 60)
  195.     seconds -= hours * (60 * 60)
  196.     
  197.     minutes  = seconds / 60
  198.     seconds -= minutes * 60
  199.     
  200.     # FIXME Kind-of convoluted, not really locale friendly...
  201.     def create_listing(items):
  202.         def append_listing(listing, singular, plural, count, index, length):
  203.             initial = not listing
  204.             
  205.             if listing or count != 0 or index+1 == length:
  206.                 listing += str(count) + ' '
  207.                 
  208.                 if count == 1:
  209.                     listing += singular
  210.                 else:
  211.                     listing += plural
  212.                 
  213.                 
  214.                 if index+1 < length:
  215.                     if index+2 == length:
  216.                         if initial:
  217.                             listing += ' and '
  218.                         else:
  219.                             listing += ', and '
  220.                     else:
  221.                         listing += ', '
  222.             
  223.             return listing
  224.         
  225.         listing = ''
  226.         
  227.         for index, item in zip(range(0,len(items)), items):
  228.             listing = append_listing(listing, item[0][0], item[0][1], item[1], index, len(items))
  229.         
  230.         return listing
  231.     
  232.     return create_listing((
  233.         (('day', 'days'), days),
  234.         (('hour', 'hours'), hours),
  235.         (('minute', 'minutes'), minutes),
  236.         (('second', 'seconds'), seconds)
  237.     ))
  238.  
  239. # Converts a number of seconds into a rough estimate
  240. def fmt_time_long_estimate(seconds):
  241.     seconds = int(seconds)
  242.     
  243.     days     = seconds / (60 * 60 * 24)
  244.     seconds -= days * (60 * 60 * 24)
  245.     
  246.     hours    = seconds / (60 * 60)
  247.     seconds -= hours * (60 * 60)
  248.     
  249.     minutes  = seconds / 60
  250.     seconds -= minutes * 60
  251.     
  252.     if days > 1:
  253.         return _('About %i days') % days
  254.     elif days == 1:
  255.         return _('About %i day') % days
  256.     elif hours > 1:
  257.         return _('About %i hours') % hours
  258.     elif hours == 1:
  259.         return _('About %i hour') % hours
  260.     elif minutes > 1:
  261.         return _('About %i minutes') % minutes
  262.     elif minutes == 1:
  263.         return _('About %i minute') % minutes
  264.     elif seconds > 1:
  265.         return _('About %i seconds') % seconds
  266.     elif seconds == 1:
  267.         return _('About %i second') % seconds
  268.     elif seconds <= 0:
  269.         return _('About %i seconds') % seconds
  270.  
  271. fmt_time_long = fmt_time_long_estimate
  272.  
  273. # A GNOME HIG compliant error dialog wrapper.
  274. class GtkHigErrorDialog:
  275.     glade_xml = None
  276.     dialog    = None
  277.     
  278.     def run(self):
  279.         self.dialog.run()
  280.         self.dialog.destroy()
  281.     
  282.     def __init__(self, text, subtext='', modal=False):
  283.         self.glade_xml = gtk.glade.XML(locate_file('data', 'errdiag.glade', 'glade'))
  284.         
  285.         self.dialog = self.glade_xml.get_widget('dialog_hig_error')
  286.         
  287.         self.glade_xml.get_widget('label_text').set_markup(text)
  288.         self.glade_xml.get_widget('label_subtext').set_markup(subtext)
  289.         
  290.         self.dialog.set_modal(modal)
  291.  
  292. # A GNOME HIG complient "continue session?" dialog wrapper.
  293. class GtkHigContinueSessionDialog:
  294.     glade_xml = None
  295.     dialog    = None
  296.     
  297.     def run(self):
  298.         ret = self.dialog.run()
  299.         
  300.         self.dialog.destroy()
  301.         
  302.         if ret == gtk.RESPONSE_ACCEPT:
  303.             return True
  304.         elif ret == gtk.RESPONSE_CANCEL:
  305.             return False
  306.         else:
  307.             return None
  308.     
  309.     def __init__(self, previous, modal=False):
  310.         self.glade_xml = gtk.glade.XML(locate_file('data', 'contdiag.glade', 'glade'))
  311.         
  312.         self.dialog = self.glade_xml.get_widget('dialog_hig_continue')
  313.         
  314.         self.glade_xml.get_widget('label_previous').set_markup(previous)
  315.         
  316.         self.dialog.set_modal(modal)
  317.  
  318. # A base wrapper for open and save dialogs.
  319. class GtkFileActionDialog:
  320.     dialog = None
  321.     
  322.     def run(self):
  323.         ret = None
  324.         
  325.         if self.dialog.run() == gtk.RESPONSE_ACCEPT:
  326.             ret = self.dialog.get_filename()
  327.         
  328.         self.dialog.destroy()
  329.         
  330.         return ret
  331.     
  332.     def __init__(self, title, action, buttons, filters=None, default_name=None, default_path=None, modal=False, multiple=False, localonly=False):
  333.         self.dialog = gtk.FileChooserDialog(title, None, action, buttons)
  334.         
  335.         self.dialog.set_local_only(localonly)
  336.         self.dialog.set_select_multiple(multiple)
  337.         
  338.         if default_path:
  339.             self.dialog.set_current_folder(default_path)
  340.         
  341.         if default_name and (action == gtk.FILE_CHOOSER_ACTION_SAVE or action == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER):
  342.             self.dialog.set_current_name(default_name)
  343.         
  344.         if filters:
  345.             default_filter = None
  346.             
  347.             for default, name, type, etc in filters:
  348.                 filter = gtk.FileFilter()
  349.                 
  350.                 filter.set_name(name)
  351.                 
  352.                 if type == 'mime':
  353.                     filter.add_mime_type(etc)
  354.                 elif type == 'pattern':
  355.                     filter.add_pattern(etc)
  356.                 elif type == 'custom':
  357.                     filter.add_custom(etc)
  358.                 
  359.                 self.dialog.add_filter(filter)
  360.                 
  361.                 if default:
  362.                     default_filter = filter
  363.             
  364.             all_filter = gtk.FileFilter()
  365.             all_filter.set_name(_('All files'))
  366.             all_filter.add_pattern('*')
  367.             
  368.             if not default_filter:
  369.                 default_filter = all_filter
  370.             
  371.             self.dialog.add_filter(all_filter)
  372.             self.dialog.set_filter(default_filter)
  373.         
  374.         self.dialog.set_modal(modal)
  375.         self.dialog.show()
  376.  
  377. # A wrapper for the open file dialog.
  378. class GtkFileOpenDialog(GtkFileActionDialog):
  379.     def __init__(self, title, folder=False, filters=None, default=None, modal=False, multiple=False, localonly=False):
  380.         action = gtk.FILE_CHOOSER_ACTION_OPEN
  381.         
  382.         if folder:
  383.             action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
  384.         
  385.         GtkFileActionDialog.__init__(self, title, action, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT), filters, default, None, modal, multiple, localonly)
  386.  
  387. # A wrapper for the save file dialog.
  388. class GtkFileSaveDialog(GtkFileActionDialog):
  389.     def __init__(self, title, folder=False, filters=None, default_name=None, default_path=None, modal=False, multiple=False, localonly=False):
  390.         action = gtk.FILE_CHOOSER_ACTION_SAVE
  391.         
  392.         if folder:
  393.             action = gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER
  394.         
  395.         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)
  396.  
  397. # Manages a BitTorrent session's state into something consistent.
  398. class BtState:
  399.     # Handles the arguments passed to a BtState at initialization based
  400.     # upon command line arguments.
  401.     class Args:
  402.         def __init__(self, args, bt_swa=(
  403.             '-i',
  404.             '--ip',
  405.             '--bind',
  406.             '--keepalive_interval',
  407.             '--download_slice_size',
  408.             '--request_backlog',
  409.             '--max_message_length',
  410.             '--timeout',
  411.             '--timeout_check_interval',
  412.             '--max_slice_length',
  413.             '--max_rate_recalculate_interval',
  414.             '--max_rate_period',
  415.             '--upload_rate_fudge',
  416.             '--display_interval',
  417.             '--rerequest_interval',
  418.             '--http_timeout',
  419.             '--snub_time',
  420.             '--spew',
  421.             '--check_hashes',
  422.             '--report_hash_failures',
  423.             '--rarest_first_priority_cutoff'
  424.         )):
  425.             self.swa_args        = []
  426.             self.path_origin     = None
  427.             self.path_output     = None
  428.             self.min_port        = None
  429.             self.max_port        = None
  430.             self.max_uploads     = None
  431.             self.max_upload_rate = None
  432.             self.torrent_file    = None
  433.             self.torrent_info    = None
  434.             
  435.             # The number of arguments following the current one to ignore.
  436.             ignore = 0
  437.             
  438.             for i in range(0,len(args)):
  439.                 # If we're ignoring this argument, skip it.
  440.                 if ignore > 0:
  441.                     ignore -= 1
  442.                     continue
  443.                 
  444.                 if args[i] == '--saveas':
  445.                     # Use the value to know where to save the session.
  446.                     
  447.                     # Ignore the next argument, since we're
  448.                     # going to use it now.
  449.                     ignore = 1
  450.                     
  451.                     if i+1 < len(args):
  452.                         self.set_path_output(args[i+1])
  453.                 elif args[i] == '--responsefile' or args[i] == '--url':
  454.                     # Use the value to know where the meta
  455.                     # file is located.
  456.                     
  457.                     # Ignore the next argument, since we're
  458.                     # going to use it now.
  459.                     ignore = 1
  460.                     
  461.                     # Convert "--responsefile [path]" into
  462.                     # "--url [uri]" and get a suggested
  463.                     # path_output if possible or needed.
  464.                     if i+1 < len(args):
  465.                         self.set_path_origin(args[i+1])
  466.                 elif args[i] == '--max_uploads':
  467.                     # Use the value for the maximum number
  468.                     # of peers to upload to.
  469.                     
  470.                     # Ignore the next argument, since we're
  471.                     # going to use it now.
  472.                     ignore = 1
  473.                     
  474.                     if i+1 < len(args):
  475.                         self.max_uploads = int(args[i+1])
  476.                 elif args[i] == '--max_upload_rate':
  477.                     # Use the value for the maximum rate in
  478.                     # kbps to upload to peers
  479.                     
  480.                     # Ignore the next argument, since we're
  481.                     # going to use it now.
  482.                     ignore = 1
  483.                     
  484.                     if i+1 < len(args):
  485.                         self.max_upload_rate = float(args[i+1])
  486.                 elif args[i] == '--minport':
  487.                     # Use the value for the minimum port in
  488.                     # the port range to use.
  489.                     
  490.                     # Ignore the next argument, since we're
  491.                     # going to use it now.
  492.                     ignore = 1
  493.                     
  494.                     if i+1 < len(args):
  495.                         self.min_port = int(args[i+1])
  496.                 elif args[i] == '--maxport':
  497.                     # Use the value for the maximum port in
  498.                     # the port range to use.
  499.                     
  500.                     # Ignore the next argument, since we're
  501.                     # going to use it now.
  502.                     ignore = 1
  503.                     
  504.                     if i+1 < len(args):
  505.                         self.max_port = int(args[i+1])
  506.                 elif args[i] in bt_swa:
  507.                     # This is some BitTorrent command line
  508.                     # switch, pass it on.
  509.                     
  510.                     # Ignore the next argument, since we're
  511.                     # going to use it now.
  512.                     ignore = 1
  513.                     
  514.                     if i+1 < len(args):
  515.                         self.swa_args.append(args[i])
  516.                         self.swa_args.append(args[i+1])
  517.                 elif re.match(r"(--)(.+)", args[i]):
  518.                     # This is some random command line
  519.                     # switch, ignore it.
  520.                     
  521.                     # Ignore the next argument, as well.
  522.                     ignore = 1
  523.                 else:
  524.                     # Assume any stray argument is the
  525.                     # path_origin as if passed to
  526.                     # --reponsefile or --url if it's a
  527.                     # valid URI.
  528.                     
  529.                     if not self.path_origin and i < len(args):
  530.                         fixed = fix_up_uri(args[i])
  531.                         
  532.                         if fixed:
  533.                             self.set_path_origin(fixed)
  534.         
  535.         # Returns the meta file's contents
  536.         def get_torrent_file(self):
  537.             if self.path_origin:
  538.                 if not self.torrent_file:
  539.                     try:
  540.                         self.torrent_file = get_url(self.path_origin, max_torrent_size)
  541.                     except:
  542.                         pass
  543.             
  544.             return self.torrent_file
  545.         
  546.         # Returns the meta file's information.
  547.         def get_torrent_info(self):
  548.             torrent_file = self.get_torrent_file()
  549.             
  550.             if torrent_file:
  551.                 if not self.torrent_info:
  552.                     try:
  553.                         self.torrent_info = BitTorrent.bencode.bdecode(torrent_file)
  554.                     except:
  555.                         pass
  556.             
  557.             return self.torrent_info
  558.         
  559.         # Suggest an output path from the input path and/or the meta
  560.         # file.
  561.         def find_suggested_path_output(self):
  562.             suggested_path_output = None
  563.             torrent_info          = self.get_torrent_info()
  564.             
  565.             if torrent_info:
  566.                 suggested_path_output = torrent_info['info']['name']
  567.             elif self.path_origin:
  568.                 mo = re.search(rfc_2396_uri_regexp, self.path_origin)
  569.                 
  570.                 suggested_path_output = os.path.basename(mo.group(5))
  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 URI formatted string.
  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(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.         digest     = sha.new(bt_state_args.get_torrent_file()).hexdigest()
  804.         digest_url = os.path.join(cache_dir, digest)
  805.         
  806.         if path_exists(digest_url):
  807.             previous_save_location = get_url(digest_url, max_torrent_size)
  808.             
  809.             if path_exists(previous_save_location):
  810.                 return previous_save_location
  811.     else:
  812.         make_config_dir()
  813.  
  814. def cache_save_location(bt_state_args):
  815.     config_dir = get_config_dir()
  816.     cache_dir = os.path.join(config_dir, 'cache')
  817.     
  818.     if not path_exists(cache_dir):
  819.         make_config_dir()
  820.     
  821.     # Just to make sure
  822.     config_dir = get_config_dir()
  823.     cache_dir = os.path.join(config_dir, 'cache')
  824.     
  825.     digest     = sha.new(bt_state_args.get_torrent_file()).hexdigest()
  826.     digest_url = os.path.join(cache_dir, digest)
  827.     
  828.     digest_file = file(digest_url, 'w')
  829.     digest_file.write(bt_state_args.path_output)
  830.     digest_file.close()
  831.  
  832. class GtkClient:
  833.     def __init__(self, args):
  834.         # Miscellaneous events that have happened in this process's
  835.         # BitTorrent sessions.
  836.         self.bt_events = []
  837.         
  838.         # Localization Setup
  839.         gtk.glade.bindtextdomain(app_name, '/usr/share/locale')
  840.         gtk.glade.textdomain(app_name)
  841.         gettext.bindtextdomain(app_name, '/usr/share/locale')
  842.         gettext.textdomain(app_name)
  843.         
  844.         # Gtk+ Setup
  845.         gtk.threads_init()
  846.         
  847.         # GConf Setup
  848.         self.gconf_client = gconf.client_get_default()
  849.         
  850.         self.gconf_client.add_dir('/apps/'+app_name, gconf.CLIENT_PRELOAD_RECURSIVE)
  851.         #self.gconf_client.notify_add('/apps/'+app_name+'/settings', self.on_gconf_settings_notify)
  852.         
  853.         # Bt Setup
  854.         bt_state_args = BtState.Args(args[1:])
  855.         
  856.         if not bt_state_args.path_origin:
  857.             filters = ((True, _('BitTorrent meta files'), 'mime', 'application/x-bittorrent'), )
  858.             result = GtkFileOpenDialog(_('Open location for BitTorrent meta file'), filters=filters, modal=True).run()
  859.             
  860.             if result:
  861.                 bt_state_args.set_path_origin(result)
  862.             else:
  863.                 # They hit Cancel
  864.                 sys.exit(1)
  865.         
  866.         if not bt_state_args.path_output:
  867.             previous_save_location = check_for_previous_save_location(bt_state_args)
  868.             
  869.             if previous_save_location:
  870.                 ret = GtkHigContinueSessionDialog(previous_save_location, modal=True).run()
  871.                 
  872.                 if ret == None:
  873.                     # They closed the window without an answer
  874.                     sys.exit(2)
  875.                 elif ret:
  876.                     bt_state_args.set_path_output(previous_save_location)
  877.         
  878.         if not bt_state_args.path_output:
  879.             previous_path = None
  880.             default = None
  881.             
  882.             # Look up the previous save path.
  883.             try:
  884.                 previous_path = self.gconf_client.get_string('/apps/'+app_name+'/previous_path')
  885.                 
  886.                 if not os.path.isdir(previous_path):
  887.                     previous_path = None
  888.             except:
  889.                 pass
  890.             
  891.             # Run Gtk+ file selector; localonly=True due to
  892.             # BitTorrent.
  893.             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()
  894.             
  895.             if result:
  896.                 bt_state_args.set_path_output(result)
  897.                 
  898.                 # Save the new path
  899.                 try:
  900.                     self.gconf_client.set_string('/apps/'+app_name+'/previous_path', os.path.dirname(result))
  901.                 except:
  902.                     pass
  903.                 
  904.                 cache_save_location(bt_state_args)
  905.             else:
  906.                 # They hit Cancel
  907.                 sys.exit(3)
  908.         
  909.         if not bt_state_args.min_port or not bt_state_args.max_port:
  910.             min_port = self.gconf_client.get_int('/apps/'+app_name+'/settings/min_port')
  911.             max_port = self.gconf_client.get_int('/apps/'+app_name+'/settings/max_port')
  912.             
  913.             if min_port and max_port:
  914.                 bt_state_args.set_ports(min_port, max_port)
  915.         
  916.         self.bt_state = BtState(bt_state_args)
  917.         
  918.         # Run Gtk+ main window
  919.         self.glade_xml = gtk.glade.XML(locate_file('data', 'dlsession.glade', 'glade'))
  920.     
  921.         self.setup_treeview_events()
  922.         
  923.         self.glade_xml.signal_autoconnect({
  924.             'on_window_main_destroy':
  925.                 self.on_window_main_destroy,
  926.             'on_button_open_clicked':
  927.                 self.on_button_open_clicked,
  928.             'on_button_resume_clicked':
  929.                 self.on_button_resume_clicked,
  930.             'on_button_stop_clicked':
  931.                 self.on_button_stop_clicked,
  932.             'on_button_close_clicked':
  933.                 self.on_button_close_clicked,
  934.             'on_checkbutton_cap_uploads_toggled':
  935.                 self.on_checkbutton_cap_uploads_toggled,
  936.             'on_spinbutton_cap_uploads_value_changed':
  937.                 self.on_spinbutton_cap_uploads_value_changed,
  938.             'on_checkbutton_cap_upload_rate_toggled':
  939.                 self.on_checkbutton_cap_upload_rate_toggled,
  940.             'on_spinbutton_cap_upload_rate_value_changed':
  941.                 self.on_spinbutton_cap_upload_rate_value_changed,
  942.             'on_button_events_clear_clicked':
  943.                 self.on_button_events_clear_clicked,
  944.             'on_checkbutton_events_display_error_dialogs_toggled':
  945.                 self.on_checkbutton_events_display_error_dialogs_toggled
  946.         })
  947.         
  948.         self.glade_xml.get_widget('label_download_address_output').set_text(self.bt_state.path_origin)
  949.         self.glade_xml.get_widget('window_main').set_title(os.path.basename(self.bt_state.path_output))
  950.         
  951.         # Set GUI preferences
  952.         display_error_dialogs = self.gconf_client.get_bool('/apps/'+app_name+'/settings/display_error_dialogs')
  953.         if display_error_dialogs != None:
  954.             self.glade_xml.get_widget('checkbutton_events_display_error_dialogs').set_active(display_error_dialogs)
  955.         
  956.         if bt_state_args.max_uploads:
  957.             self.glade_xml.get_widget('spinbutton_cap_uploads').set_value(bt_state_args.max_uploads)
  958.             self.glade_xml.get_widget('checkbutton_cap_uploads').set_active(True)
  959.         else:
  960.             cap_upload_peers = self.gconf_client.get_int('/apps/'+app_name+'/settings/cap_upload_peers')
  961.             if cap_upload_peers != None:
  962.                 self.glade_xml.get_widget('spinbutton_cap_uploads').set_value(cap_upload_peers)
  963.             
  964.             cap_upload = self.gconf_client.get_bool('/apps/'+app_name+'/settings/cap_upload')
  965.             if cap_upload != None:
  966.                 self.glade_xml.get_widget('checkbutton_cap_uploads').set_active(cap_upload)
  967.         
  968.         cap_upload_rate_kbps = self.gconf_client.get_float('/apps/'+app_name+'/settings/cap_upload_rate_kbps')
  969.         if cap_upload_rate_kbps != None:
  970.             self.glade_xml.get_widget('spinbutton_cap_upload_rate').set_value(cap_upload_rate_kbps)
  971.         
  972.         if bt_state_args.max_upload_rate != None:
  973.             if bt_state_args.max_upload_rate > 0:
  974.                 self.glade_xml.get_widget('spinbutton_cap_upload_rate').set_value(bt_state_args.max_upload_rate)
  975.                 self.glade_xml.get_widget('checkbutton_cap_upload_rate').set_active(True)
  976.             else:
  977.                 self.glade_xml.get_widget('checkbutton_cap_upload_rate').set_active(False)
  978.         else:
  979.             cap_upload_rate = self.gconf_client.get_bool('/apps/'+app_name+'/settings/cap_upload_rate')
  980.             if cap_upload_rate != None:
  981.                 self.glade_xml.get_widget('checkbutton_cap_upload_rate').set_active(cap_upload_rate)
  982.         
  983.         # Save arguments for session recovery
  984.         self.session_recovery_cwd  = os.getcwd()
  985.         self.session_recovery_args = args[:1] + bt_state_args.get_args()
  986.         
  987.         # Setup session
  988.         master_client = gnome.ui.master_client()
  989.         
  990.         master_client.connect('save-yourself', self.on_gc_save_yourself, None)
  991.         master_client.connect('die', self.on_gc_die, None)
  992.         
  993.         master_client.set_restart_style(gnome.ui.RESTART_IF_RUNNING)
  994.         
  995.         # Run Bt
  996.         self.run_bt()
  997.         
  998.         # Run Gtk+
  999.         gtk.main()
  1000.     
  1001.     # Appends an event to the log.
  1002.     def log_event(self, type, text):
  1003.         t = fmt_time_short(time.time() - self.bt_state.time_begin)
  1004.         
  1005.         if type == 'Error' and self.glade_xml.get_widget('checkbutton_events_display_error_dialogs').get_active():
  1006.             # Try to specially adapt the error message.
  1007.             try:
  1008.                 mo = re.search(r"([A-Za-z \'\,]+) - [\<]?([^\>]+)[\>]?", str(text))
  1009.                 
  1010.                 GtkHigErrorDialog('<b>' + mo.group(1) + '</b>', mo.group(2)).run()
  1011.             except:
  1012.                 GtkHigErrorDialog(str(text)).run()
  1013.         
  1014.         if self.bt_events:
  1015.             self.bt_events.append((t, type, text))
  1016.         else:
  1017.             print >> sys.stderr, i18n('%s, %s: %s' % (t, type, text))
  1018.     
  1019.     # BitTorrent callbacks
  1020.     def on_bt_file(self, default, size, saveas, dir):
  1021.         path = self.bt_state.file(default, size, saveas, dir)
  1022.         
  1023.         gtk.threads_enter()
  1024.         
  1025.         label_download_file_output = self.glade_xml.get_widget('label_download_file_output')
  1026.         label_download_file_output.set_text(path)
  1027.         
  1028.         gtk.threads_leave()
  1029.         
  1030.         return path
  1031.     
  1032.     def on_bt_status(self, dict = {}, fractionDone = None, timeEst = None, downRate = None, upRate = None, activity = None):
  1033.         # To support BitTorrent 3.2, pack anything supplied seperately
  1034.         # from dict into dict.
  1035.         if fractionDone:
  1036.             dict['fractionDone'] = fractionDone
  1037.         if timeEst:
  1038.             dict['timeEst'] = timeEst
  1039.         if downRate:
  1040.             dict['downRate'] = downRate
  1041.         if upRate:
  1042.             dict['upRate'] = upRate
  1043.         if activity:
  1044.             dict['activity'] = activity
  1045.         
  1046.         self.bt_state.status(dict)
  1047.         
  1048.         gtk.threads_enter()
  1049.         
  1050.         label_download_elapsed_output = self.glade_xml.get_widget('label_download_time_elapsed_output')
  1051.         label_download_elapsed_output.set_text(fmt_time_long_precise_verbose(time.time() - self.bt_state.time_begin))
  1052.         
  1053.         if dict.has_key('fractionDone'):
  1054.             progressbar_download_status = self.glade_xml.get_widget('progressbar_download_status')
  1055.             window_main = self.glade_xml.get_widget('window_main')
  1056.             
  1057.             perc_string = str(int(dict['fractionDone'] * 100)) + '%'
  1058.             
  1059.             progressbar_download_status.set_fraction(dict['fractionDone'])
  1060.             progressbar_download_status.set_text(perc_string)
  1061.             window_main.set_title(perc_string + ' of ' + os.path.basename(self.bt_state.path_output))
  1062.         
  1063.         if dict.has_key('downTotal') or dict.has_key('fractionDone'):
  1064.             label_download_status_output = self.glade_xml.get_widget('label_download_status_output')
  1065.             
  1066.             label_download_status_output.set_text('%.1f of %.1f MB at %.2f KB/s' %
  1067.                 (float(self.bt_state.get_dl_amount()) / (1 << 20),
  1068.                  float(self.bt_state.size_total)      / (1 << 20),
  1069.                  float(self.bt_state.dl_rate)         / (1 << 10)))
  1070.             
  1071.         if dict.has_key('timeEst'):
  1072.             label_download_time_remaining_output = self.glade_xml.get_widget('label_download_time_remaining_output')
  1073.             
  1074.             label_download_time_remaining_output.set_text(fmt_time_long(dict['timeEst']))
  1075.         
  1076.         if dict.has_key('upRate') or dict.has_key('upTotal'):
  1077.             label_upload_status_output = self.glade_xml.get_widget('label_upload_status_output')
  1078.             
  1079.             if self.bt_state.get_ul_amount():
  1080.                 label_upload_status_output.set_text(_('%.1f MB at %.2f KB/s') %
  1081.                     (float(self.bt_state.get_ul_amount()) / (1 << 20),
  1082.                      float(self.bt_state.ul_rate)         / (1 << 10)))
  1083.             else:
  1084.                 label_upload_status_output.set_text('%.2f KB/s' %
  1085.                     (float(self.bt_state.ul_rate) / (1 << 10)))
  1086.         
  1087.         if dict.has_key('activity'):
  1088.             self.log_event('Activity', dict['activity'])
  1089.         
  1090.         gtk.threads_leave()
  1091.     
  1092.     def on_bt_finished(self):
  1093.         self.bt_state.finished()
  1094.         self.on_bt_status({'fractionDone': float(1.0), 'timeEst': 0, 'activity': 'finished'})
  1095.         
  1096.         gtk.threads_enter()
  1097.         
  1098.         progressbar_download_status          = self.glade_xml.get_widget('progressbar_download_status')
  1099.         label_download_time_remaining_output = self.glade_xml.get_widget('label_download_time_remaining_output')
  1100.         button_open                          = self.glade_xml.get_widget('button_open')
  1101.         
  1102.         progressbar_download_status.set_fraction(1.0)
  1103.         progressbar_download_status.set_text('100%')
  1104.         label_download_time_remaining_output.set_text(_('None'))
  1105.         
  1106.         # Check if the completed session can be 'shown'
  1107.         if can_show_path and show_path and can_show_path(self.bt_state.path_output):
  1108.             button_open.set_sensitive(True)
  1109.         
  1110.         gtk.threads_leave()
  1111.     
  1112.     def on_bt_error(self, msg):
  1113.         gtk.threads_enter()
  1114.         
  1115.         self.log_event('Error', msg)
  1116.         
  1117.         gtk.threads_leave()
  1118.     
  1119.     def on_bt_path(self, path):
  1120.         self.bt_state.path(path)
  1121.         
  1122.         gtk.threads_enter()
  1123.         
  1124.         label_download_file_output = self.glade_xml.get_widget('label_download_file_output')
  1125.         label_download_file_output.set_text(self.bt_state.path_output)
  1126.         
  1127.         gtk.threads_leave()
  1128.     
  1129.     def on_bt_param(self, params):
  1130.         self.bt_state.param(params)
  1131.         
  1132.         gtk.threads_enter()
  1133.         
  1134.         checkbutton_cap_uploads = self.glade_xml.get_widget('checkbutton_cap_uploads')
  1135.         spinbutton_cap_uploads  = self.glade_xml.get_widget('spinbutton_cap_uploads')
  1136.         checkbutton_cap_upload_rate = self.glade_xml.get_widget('checkbutton_cap_upload_rate')
  1137.         spinbutton_cap_upload_rate  = self.glade_xml.get_widget('spinbutton_cap_upload_rate')
  1138.         
  1139.         if params.has_key('max_uploads'):
  1140.             checkbutton_cap_uploads.set_sensitive(True)
  1141.             spinbutton_cap_uploads.set_sensitive(True)
  1142.         else:
  1143.             checkbutton_cap_uploads.set_sensitive(False)
  1144.             spinbutton_cap_uploads.set_sensitive(False)
  1145.         
  1146.         if params.has_key('max_upload_rate'):
  1147.             checkbutton_cap_upload_rate.set_sensitive(True)
  1148.             spinbutton_cap_upload_rate.set_sensitive(True)
  1149.         else:
  1150.             checkbutton_cap_upload_rate.set_sensitive(False)
  1151.             spinbutton_cap_upload_rate.set_sensitive(False)
  1152.         
  1153.         gtk.threads_leave()
  1154.     
  1155.     # GnomeClient callbacks
  1156.     def on_gc_save_yourself(self, client, phase, save_style, is_shutdown, interact_style, is_fast, data=None):
  1157.         master_client = gnome.ui.master_client()
  1158.         
  1159.         args = self.session_recovery_args[:]
  1160.         
  1161.         checkbutton_cap_uploads = self.glade_xml.get_widget('checkbutton_cap_uploads')
  1162.         spinbutton_cap_uploads  = self.glade_xml.get_widget('spinbutton_cap_uploads')
  1163.         checkbutton_cap_upload_rate = self.glade_xml.get_widget('checkbutton_cap_upload_rate')
  1164.         spinbutton_cap_upload_rate  = self.glade_xml.get_widget('spinbutton_cap_upload_rate')
  1165.         
  1166.         if checkbutton_cap_uploads.get_active():
  1167.             args.append('--max_uploads')
  1168.             args.append(str(spinbutton_cap_uploads.get_value()))
  1169.         
  1170.         if checkbutton_cap_upload_rate.get_active():
  1171.             args.append('--max_upload_rate')
  1172.             args.append(str(spinbutton_cap_upload_rate.get_value()))
  1173.         
  1174.         master_client.set_current_directory(self.session_recovery_cwd)
  1175.         master_client.set_clone_command(len(args), args)
  1176.         master_client.set_restart_command(len(args), args)
  1177.         
  1178.         return True
  1179.     
  1180.     def on_gc_die(self, client, data=None):
  1181.         self.join()
  1182.         gtk.main_quit()
  1183.     
  1184.     # GTK+ callbacks
  1185.     def on_window_main_destroy(self, widget, data=None):
  1186.         self.join()
  1187.         gtk.main_quit()
  1188.     
  1189.     def on_button_open_clicked(self, widget, data=None):
  1190.         if show_path:
  1191.             show_path(self.bt_state.path_output)
  1192.     
  1193.     def on_button_resume_clicked(self, widget, data=None):
  1194.         button_resume = self.glade_xml.get_widget('button_resume')
  1195.         button_stop   = self.glade_xml.get_widget('button_stop')
  1196.         button_close  = self.glade_xml.get_widget('button_close')
  1197.         
  1198.         button_resume.set_sensitive(False)
  1199.         button_stop.show()
  1200.         button_close.hide()
  1201.         
  1202.         self.run_bt(resuming=True)
  1203.     
  1204.     def on_button_stop_clicked(self, widget, data=None):
  1205.         self.join()
  1206.         
  1207.         button_resume = self.glade_xml.get_widget('button_resume')
  1208.         button_stop   = self.glade_xml.get_widget('button_stop')
  1209.         button_close  = self.glade_xml.get_widget('button_close')
  1210.         
  1211.         button_resume.set_sensitive(True)
  1212.         button_stop.hide()
  1213.         button_close.show()
  1214.     
  1215.     def on_button_close_clicked(self, widget, data=None):
  1216.         window_main = self.glade_xml.get_widget('window_main')
  1217.         window_main.destroy()
  1218.     
  1219.     def on_checkbutton_cap_uploads_toggled(self, widget, data=None):
  1220.         spinbutton_cap_uploads = self.glade_xml.get_widget('spinbutton_cap_uploads')
  1221.         
  1222.         try:
  1223.             if widget.get_active():
  1224.                 self.bt_state.cap_uploads(int(spinbutton_cap_uploads.get_value()))
  1225.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload', True)
  1226.             else:
  1227.                 self.bt_state.cap_uploads(0)
  1228.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload', False)
  1229.         except:
  1230.             pass
  1231.     
  1232.     def on_spinbutton_cap_uploads_value_changed(self, widget, data=None):
  1233.         checkbutton_cap_uploads = self.glade_xml.get_widget('checkbutton_cap_uploads')
  1234.         
  1235.         try:
  1236.             self.gconf_client.set_int('/apps/'+app_name+'/settings/cap_upload_peers', int(widget.get_value()))
  1237.         except:
  1238.             pass
  1239.         
  1240.         if checkbutton_cap_uploads.get_active():
  1241.             self.bt_state.cap_uploads(int(widget.get_value()))
  1242.     
  1243.     def on_checkbutton_cap_upload_rate_toggled(self, widget, data=None):
  1244.         spinbutton_cap_upload_rate = self.glade_xml.get_widget('spinbutton_cap_upload_rate')
  1245.         
  1246.         try:
  1247.             if widget.get_active():
  1248.                 self.bt_state.cap_upload_rate(spinbutton_cap_upload_rate.get_value())
  1249.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload_rate', True)
  1250.             else:
  1251.                 self.bt_state.cap_upload_rate(0)
  1252.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/cap_upload_rate', False)
  1253.         except:
  1254.             pass
  1255.     
  1256.     def on_spinbutton_cap_upload_rate_value_changed(self, widget, data=None):
  1257.         checkbutton_cap_upload_rate = self.glade_xml.get_widget('checkbutton_cap_upload_rate')
  1258.         
  1259.         try:
  1260.             self.gconf_client.set_float('/apps/'+app_name+'/settings/cap_upload_rate_kbps', float(widget.get_value()))
  1261.         except:
  1262.             pass
  1263.         
  1264.         if checkbutton_cap_upload_rate.get_active():
  1265.             self.bt_state.cap_upload_rate(widget.get_value())
  1266.     
  1267.     def on_button_events_clear_clicked(self, widget, data=None):
  1268.         if self.bt_events:
  1269.             self.bt_events.clear()
  1270.     
  1271.     def on_checkbutton_events_display_error_dialogs_toggled(self, widget, data=None):
  1272.         try:
  1273.             if widget.get_active():
  1274.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/display_error_dialogs', True)
  1275.             else:
  1276.                 self.gconf_client.set_bool('/apps/'+app_name+'/settings/display_error_dialogs', False)
  1277.         except:
  1278.             pass
  1279.     
  1280.     # GTK+ setup stuff to supliment Glade.
  1281.     def setup_treeview_events(self):
  1282.         treeview_events = self.glade_xml.get_widget('treeview_events')
  1283.         
  1284.         list_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
  1285.         
  1286.         treeview_events.set_model(list_store)
  1287.         
  1288.         treeview_events.append_column(gtk.TreeViewColumn('Time', gtk.CellRendererText(), text=0))
  1289.         treeview_events.append_column(gtk.TreeViewColumn('Type', gtk.CellRendererText(), text=1))
  1290.         treeview_events.append_column(gtk.TreeViewColumn('Text', gtk.CellRendererText(), text=2))
  1291.         
  1292.         self.bt_events = list_store
  1293.     
  1294.     # Helpful wrapper to start BitTorrent session.
  1295.     def run_bt(self, resuming=False):
  1296.         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)
  1297.     
  1298.     # Helpful wrapper to end BitTorrent session.
  1299.     def join(self):
  1300.         if self.bt_state:
  1301.             self.bt_state.join()
  1302.  
  1303. # Start the client
  1304. def run(args):
  1305.     client = GtkClient(args)
  1306.  
  1307. # Automatically start the client as long as this isn't being used as a module
  1308. # for some reason.
  1309. if __name__ == '__main__':
  1310.     gtk.window_set_default_icon_from_file('/usr/share/gnome-btdownload/pixmaps/download.png')
  1311.     run(sys.argv)
  1312.