[Xfce4-commits] <eatmonkey:aria2-xml-rpc> First draft is finally in
Mike Massonnet
noreply at xfce.org
Sat Jan 23 12:10:04 CET 2010
Updating branch refs/heads/aria2-xml-rpc
to 78325d6ff76362fbf28790d7f5bdc1f56126577a (commit)
from b22028ada2c68e6b8595bb6affd5eb3e312a8d58 (commit)
commit 78325d6ff76362fbf28790d7f5bdc1f56126577a
Author: Mike Massonnet <mmassonnet at xfce.org>
Date: Sat Jan 23 12:06:46 2010 +0100
First draft is finally in
Written in Ruby, it depends on the ruby-gnome2 project for the GTK+
bindings.
po/eatmonkey.pot | 169 +++++++++++
src/eat.rb | 27 ++
src/eataria2.rb | 617 ++++++++++++++++++++++++++++++++++++++++
src/eatmanager.rb | 302 ++++++++++++++++++++
src/eatsettings.rb | 223 +++++++++++++++
src/manager.ui | 792 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/settings.ui | 770 ++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 2900 insertions(+), 0 deletions(-)
diff --git a/po/eatmonkey.pot b/po/eatmonkey.pot
new file mode 100644
index 0000000..457967a
--- /dev/null
+++ b/po/eatmonkey.pot
@@ -0,0 +1,169 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-23 11:01+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../eatmonkey.desktop.in.h:1
+msgid "Download Manager"
+msgstr ""
+
+#: ../eatmonkey.desktop.in.h:2 ../src/manager.ui.h:2
+msgid "Eatmonkey"
+msgstr ""
+
+#: ../eatmonkey.desktop.in.h:3
+msgid "Stupid download manager for monkeys and Capuchins!"
+msgstr ""
+
+#: ../src/manager.ui.h:1
+msgid "E_dit"
+msgstr ""
+
+#: ../src/manager.ui.h:3
+msgid "H_elp"
+msgstr ""
+
+#: ../src/manager.ui.h:4
+msgid "New download"
+msgstr ""
+
+#: ../src/manager.ui.h:5
+msgid "Open from URI/File"
+msgstr ""
+
+#: ../src/manager.ui.h:6
+msgid "Select a file - Torrents, Metalinks"
+msgstr ""
+
+#: ../src/manager.ui.h:7
+msgid "Select from clipboard"
+msgstr ""
+
+#: ../src/manager.ui.h:8
+msgid "Select from file"
+msgstr ""
+
+#: ../src/manager.ui.h:9
+msgid "_Compact View"
+msgstr ""
+
+#: ../src/manager.ui.h:10
+msgid "_Details View"
+msgstr ""
+
+#: ../src/manager.ui.h:11
+msgid "_File"
+msgstr ""
+
+#: ../src/manager.ui.h:12
+msgid "_History"
+msgstr ""
+
+#: ../src/manager.ui.h:13
+msgid "_Log"
+msgstr ""
+
+#: ../src/manager.ui.h:14
+msgid "_View"
+msgstr ""
+
+#: ../src/settings.ui.h:1
+msgid "(0=infinite)"
+msgstr ""
+
+#: ../src/settings.ui.h:2
+msgid "Always require encryption"
+msgstr ""
+
+#: ../src/settings.ui.h:3
+msgid "Authentication"
+msgstr ""
+
+#: ../src/settings.ui.h:4
+msgid "BitTorrent"
+msgstr ""
+
+#: ../src/settings.ui.h:5
+msgid "Distributed Hash Table allows to use torrents without a working tracker"
+msgstr ""
+
+#: ../src/settings.ui.h:6
+msgid "Download Options"
+msgstr ""
+
+#: ../src/settings.ui.h:7
+msgid "Download directory:"
+msgstr ""
+
+#: ../src/settings.ui.h:8
+msgid "Enable DHT (Distributed Hash Table)"
+msgstr ""
+
+#: ../src/settings.ui.h:9
+msgid "Enable PEX (Peer Exchange)"
+msgstr ""
+
+#: ../src/settings.ui.h:10
+msgid "General"
+msgstr ""
+
+#: ../src/settings.ui.h:11
+msgid "Host:"
+msgstr ""
+
+#: ../src/settings.ui.h:12
+msgid "KiB/s"
+msgstr ""
+
+#: ../src/settings.ui.h:13
+msgid "Maximum download speed:"
+msgstr ""
+
+#: ../src/settings.ui.h:14
+msgid "Maximum number of concurrent downloads:"
+msgstr ""
+
+#: ../src/settings.ui.h:15
+msgid "Maximum number of peers:"
+msgstr ""
+
+#: ../src/settings.ui.h:16
+msgid "Maximum upload speed:"
+msgstr ""
+
+#: ../src/settings.ui.h:17
+msgid "Password:"
+msgstr ""
+
+#: ../src/settings.ui.h:18
+msgid "Peer Exchange helps finding more peers through connected peers"
+msgstr ""
+
+#: ../src/settings.ui.h:19
+msgid "Seed ratio:"
+msgstr ""
+
+#: ../src/settings.ui.h:20
+msgid "Settings"
+msgstr ""
+
+#: ../src/settings.ui.h:21
+msgid "Username:"
+msgstr ""
+
+#: ../src/settings.ui.h:22
+msgid "aria2 XML-RPC Server"
+msgstr ""
diff --git a/src/eat.rb b/src/eat.rb
new file mode 100644
index 0000000..f8bfb7a
--- /dev/null
+++ b/src/eat.rb
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby -w
+
+$stderr.reopen('/dev/null', 'w')
+require "glib2"
+require "gtk2"
+$stderr=STDOUT
+
+require "eataria2"
+require "eatmanager"
+
+include Gtk
+
+if __FILE__ == $0
+ aria2 = Eat::Aria2Listener.instance
+ settings = Eat::Settings.instance
+ if settings["custom-server"]
+ aria2.use_custom_server(settings["xmlrpc-host"], settings["xmlrpc-port"], settings["xmlrpc-user"], settings["xmlrpc-passwd"])
+ end
+ aria2.connect
+ puts "Version: aria2 %s" % aria2.version
+
+ manager = Eat::Manager.new
+ manager.show
+
+ main
+end
+
diff --git a/src/eataria2.rb b/src/eataria2.rb
new file mode 100644
index 0000000..477d025
--- /dev/null
+++ b/src/eataria2.rb
@@ -0,0 +1,617 @@
+#!/usr/bin/ruby -w
+
+require "xmlrpc/client"
+require "fileutils"
+require "singleton"
+require "timeout"
+require "pp"
+
+require "glib2"
+
+def debug(*args)
+ return if false
+ pp args
+end
+
+
+
+module Eat
+end
+
+
+
+class Eat::Aria2 < GLib::Object
+
+ DEFAULT_PORT = 6801
+ DEFAULT_HOST = '127.0.0.1'
+
+ attr_accessor :user
+ attr_accessor :password
+ attr_accessor :hostname
+ attr_accessor :port
+
+ attr_reader :is_connected
+ attr_reader :version
+
+ private
+
+ type_register
+
+ signal_new("download_status", GLib::Signal::RUN_FIRST, nil,
+ nil, # Return type: void
+ String) # Parameters: gid
+ signal_new("download_completed", GLib::Signal::RUN_FIRST, nil,
+ nil, # Return type: void
+ String) # Parameters: gid
+ signal_new("download_removed", GLib::Signal::RUN_FIRST, nil,
+ nil, # Return type: void
+ String) # Parameters: gid
+ signal_new("download_stopped", GLib::Signal::RUN_FIRST, nil,
+ nil, # Return type: void
+ String) # Parameters: gid
+
+ def signal_do_download_status(gid)
+ end
+ def signal_do_download_completed(gid)
+ end
+ def signal_do_download_removed(gid)
+ end
+ def signal_do_download_stopped(gid)
+ end
+
+ def initialize(hostname = DEFAULT_HOST, port = DEFAULT_PORT, user = nil, password = nil)
+ super(nil)
+
+ @new_downloads = []
+
+ @confdir = ENV['XDG_CONFIG_HOME']
+ @confdir = ENV['HOME']+"/.config" if @confdir == nil
+ @confdir += "/eatmonkey"
+
+ @hostname, @port, @user, @password = hostname, port, user, password
+ @is_connected = false
+ @use_local_server = true
+ @version = "n/a"
+ @@client = nil if !defined? @@client
+
+ if !defined? @@pid
+ begin
+ @@pid = File.open(@confdir+"/aria2.pid").readline.strip.to_i
+ Process.kill("CONT", @@pid)
+ rescue
+ @@pid = 0
+ end
+ end
+ end
+
+=begin
+ start_server:
+ Returns negative values on error, -1 if a service is already listening
+ on the port and -2 if there was a problem during the creaton of the
+ server. It returns the pid of the server on success or 0 if no server
+ should be run (see use_local_server).
+=end
+ def start_server()
+ return 0 if @use_local_server == false
+ begin
+ TCPSocket.new(@hostname, @port).close
+ return -1
+ rescue
+ begin
+ # Create config file
+ FileUtils.mkdir_p(@confdir, :mode => 0700)
+ FileUtils.touch(@confdir+"/aria2.conf")
+ # Cleanup log file
+ FileUtils.rm(@confdir+"/aria2.log", :force => true)
+ # Launch aria2c process
+ command = "aria2c --disable-ipv6=true --enable-xml-rpc --xml-rpc-listen-port=#{@port} " \
+ "--conf-path=#{@confdir}/aria2.conf --log=#{@confdir}/aria2.log --log-level=notice " \
+ "--dht-file-path=#{@confdir}/dht.dat --lowest-speed-limit=1K"
+ debug("start server", command)
+ @@pid = Process.spawn(command, :pgroup=>true, :chdir=>ENV['HOME'],
+ STDOUT=>"/dev/null", STDIN=>"/dev/null")
+ Process.detach(@@pid)
+ # Wait for the server to respond properly to requests
+ begin
+ debug("test request")
+ begin
+ result = @@client.call("aria2.getVersion")
+ rescue
+ end
+ sleep 1 if result == nil
+ end while result == nil
+ # Store pid in a file
+ begin
+ File.open(@confdir+"/aria2.pid", "w").puts(@@pid)
+ rescue
+ p $!
+ end
+ puts "Started aria2 XML-RPC Server (pid #{@@pid.to_s})..."
+ return @@pid
+ rescue
+ return -2
+ end
+ end
+ end
+
+ def call(method, *args)
+ begin
+ result = @@client.call(method, *args)
+ rescue XMLRPC::FaultException => e
+ # Unsupported/Bad XMLRPC request
+ debug("XMLRPC server didn't support the request")
+ puts e.message
+ rescue Errno::EPIPE => e
+ # Connection interrupted/timed out/server shutdown
+ return nil if !@is_connected
+ connect(true)
+ result = call(method, args)
+ rescue Errno::ECONNREFUSED => e
+ # Connection refused
+ return nil if start_server < 0
+ result = call(method, args)
+ rescue Exception => e
+ # Unhandled exception
+ puts $!, e.message
+ end
+ result
+ end
+
+ public
+
+=begin
+ connect:
+ Creates an XMLRPC::Client instance and sends a first request that will
+ make sure we can talk to the server.
+=end
+ def connect(force=false)
+ return if @@client and force == false
+ @is_connected = false
+ begin
+ @@client = XMLRPC::Client.new3({:host => @hostname, :path => "/rpc",
+ :port => @port, :user => @user, :password => @password,
+ :timeout => 60})
+ debug("call")
+ result = nil
+ Timeout::timeout(5) do
+ result = call("aria2.getVersion")
+ end
+ raise Exception.new("Unable to get an appropriate response to our request") if result == nil
+ @version = result["version"]
+ @is_connected = true
+ rescue Exception => e
+ @@client = nil
+ puts "Can't establish a connection"
+ debug(e.message)
+ end
+ end
+
+=begin
+ use_custom_server:
+ Sets aria2 to connect to a custom server.
+=end
+ def use_custom_server(hostname, port, user, password)
+ @use_local_server = false
+ @hostname = hostname
+ @port = port
+ @user = user.to_s if !user.empty?
+ @password = password.to_s if !password.empty?
+ end
+
+=begin
+ use_local_server:
+ Sets aria2 to connect to a local (self-managed) server.
+=end
+ def use_local_server()
+ @use_local_server = true
+ @hostname = DEFAULT_HOST
+ @port = DEFAULT_PORT
+ @user = nil
+ @password = nil
+ end
+
+ def use_local_server?()
+ @use_local_server and @@pid > 0
+ end
+
+=begin
+ shutdown:
+ Disconnect and terminates the local server if it exists.
+=end
+ def shutdown()
+ debug("shutdown", @@pid)
+ begin
+ if @@pid > 0
+ FileUtils.rm(@confdir+"/aria2.pid", :force => true)
+ Process.kill("TERM", @@pid)
+ sleep 1 while Process.kill("CONT", @@pid)
+ end
+ rescue Errno::ESRCH
+ # Process does no more exist
+ rescue
+ p $!
+ end
+ @@pid = 0
+ @@client = nil
+ @is_connected = false
+ end
+
+ # Adds new HTTP(S)/FTP/BitTorrent/Magnet URI.
+ def add_uri(uris, options = nil, position = nil)
+ uris = [ uris ] if uris.class == String
+ gid = call("aria2.addUri", uris)
+ @new_downloads << gid if gid != nil
+ gid
+ end
+
+ # Adds BitTorrent download by uploading .torrent file.
+ def add_torrent(torrent, uris = nil, options = nil, position = nil)
+ data = read(torrent)
+ gid = call("aria2.addTorrent", XMLRPC::Base64.new(data))
+ @new_downloads << gid if gid != nil
+ gid
+ end
+
+ # Adds Metalink download by uploading .metalink file.
+ def add_metalink(metalink, options = nil, position = nil)
+ data = read(metalink)
+ gid = call("aria2.addMetalink", XMLRPC::Base64.new(data))
+ @new_downloads << gid if gid != nil
+ gid
+ end
+
+ # Removes the download denoted by @gid.
+ def remove(gid)
+ call("aria2.remove", gid)
+ end
+
+ # Returns download progress of the download denoted by @gid.
+ def tell_status(gid)
+ result = call("aria2.tellStatus", gid)
+ result = [] if result == nil
+ result
+ end
+
+ # Returns URIs used in the download denoted by @gid.
+ def get_uris(gid)
+ call("aria2.getUris", gid)
+ end
+
+ # Returns file list of the download denoted by @gid.
+ def get_files(gid)
+ call("aria2.getFiles", gid)
+ end
+
+ # Returns peer list of the download denoted by @gid.
+ def get_peers(gid)
+ call("aria2.getPeers", gid)
+ end
+
+ # Returns the list of active downloads.
+ def tell_active()
+ result = call("aria2.tellActive")
+ result = [] if !result
+ result
+ end
+
+ # Returns the list of waiting download.
+ def tell_waiting(offset, num)
+ result = call("aria2.tellWaiting", offset, num)
+ result = [] if !result
+ result
+ end
+
+ # Returns the list of stopped download.
+ def tell_stopped(offset, num)
+ return nil if @version < "1.8"
+ result = call("aria2.tellStopped", offset, num)
+ result = [] if result == nil
+ result
+ end
+
+ # Changes the position of the download denoted by @gid.
+ def change_position(gid, pos, from_current = false)
+ return nil if @version < "1.8"
+ how = from_current ? "POS_CUR" : "POS_RET"
+ call("aria2.changePosition", gid, pos, how)
+ end
+
+ # Returns options of the download denoted by @gid.
+ def get_options(gid)
+ return nil if @version < "1.8"
+ call("aria2.getOption", gid)
+ end
+
+ # Changes options of the download denoted by @gid dynamically.
+ def change_options(gid, options)
+ call("aria2.changeOption", gid, options)
+ end
+
+ # Returns global options.
+ def get_global_options()
+ return nil if @version < "1.8"
+ call("aria2.getGlobalOption")
+ end
+
+ # Changes global options dynamically.
+ def change_global_options(options)
+ call("aria2.changeGlobalOption", options)
+ end
+
+ # Purges completed/error/removed downloads to free memory.
+ def purge_download_result()
+ call("aria2.purgeDownloadResult")
+ end
+
+end
+
+
+
+class Eat::Aria2Listener < Eat::Aria2
+
+ include Singleton
+
+ def initialize()
+ super
+ @downloads = []
+ start
+ end
+
+ def start()
+ GLib::Timeout.add_seconds(1) do
+
+ active_downloads = []
+
+ if @is_connected
+
+ # Emit status signal for each active download
+ tell_active.each do |res|
+ gid = res["gid"]
+ @downloads << gid if !@downloads.find_index(gid)
+ active_downloads << gid
+ signal_emit("download_status", gid)
+ end
+
+ # Check/Cleanup unhandled new downloads
+ (@new_downloads - active_downloads).each do |gid|
+ @downloads << gid
+ end
+ @new_downloads.clear
+
+ # Emit specific signal for each inactive download
+ (@downloads - active_downloads).each do |gid|
+ # Get status
+ status = tell_status(gid)
+ status_msg = status["status"]
+ case status["status"]
+ when "complete" then signal_emit("download_completed", gid)
+ when "removed" then signal_emit("download_removed", gid)
+ when "error"
+ if status["errorCode"] == "5"
+ # Download was aborted due to "pause"
+ debug "pause lasted too long, download %s dropped" % gid
+ else
+ puts "Unhandled error (%s: %s)" % [gid, status["errorCode"]]
+ end
+ else puts "Unhandled status (%s: %s)" % [gid, status_msg]
+ end
+ end
+
+ end
+
+ @downloads = active_downloads
+ true
+ end
+ end
+
+end
+
+
+
+class Eat::Aria2Config
+
+ attr_reader :filename
+ attr_accessor :values
+
+ def initialize(filename="aria2.conf")
+ @filename = filename
+ @values = {}
+ parse
+ end
+
+ private
+
+ def default_values()
+ # Basic options
+ #@values["dir"] = "." # directory to store downloaded files
+ @values["log"] = "-" # file name of the log file, - means STDOUT
+ @values["max-concurrent-downloads"] = 5 # maximum number of parallel downloads for every static URI
+ @values["check-integrity"] = false # check file integrity, effect only in BitTorrent and Metalink
+
+ # HTTP/FTP options
+ #@values["all-proxy"] = "" # proxy server for all protocols
+ @values["connect-timeout"] = 60 # timeout to establish a connection
+ @values["lowest-speed-limit"] = 0 # close connection if download speed is lower than this value
+ @values["max-file-not-found"] = 0 # number of times to try a file reporting 404 before giving up
+ @values["max-tries"] = 5 # number of tries
+ @values["proxy-method"] = "get" # specify method to use in proxy request, get or tunnel
+ @values["remote-time"] = false # applies the file timestamp from the remote server
+ @values["split"] = 5 # number of connections to use for downloading a file
+ @values["timeout"] = 60 # timeout in seconds
+
+ # HTTP options
+ #@values["ca-certificate"] = "" # file name of certificate authorities to use
+ #@values["certificate"] = "" # file name of client certificate
+ @values["check-certificate"] = true # verify the peer using certificates
+ @values["http-auth-challenge"] = false # send auth header only when it is requested by the server
+ @values["http-user"] = "" # HTTP user
+ @values["http-passwd"] = "" # HTTP password
+ #@values["http-proxy"] = "" # proxy server for HTTP
+ #@values["https-proxy"] = "" # proxy server for HTTPS
+ #@values["private-key"] = "" # file name of private key to use
+ @values["referer"] = "" # set referer
+ @values["enable-http-keep-alive"] = true # enable HTTP/1.1 persistent connection
+ @values["enable-http-pipelining"] = false # enable HTTP/1.1 pipelining
+ @values["header"] = "" # header to append to HTTP request, can be used repeatedly
+ @values["load-cookies"] = "" # cookies file name to use, supports Firefox3 (sqlite) and Netscape
+ @values["save-cookies"] = "" # save cookies to file name in Netscape format
+ @values["use-head"] = false # use HEAD method for first request
+ @values["user-agent"] = "Eatmonkey/0.1.0 aria2/1.8.0"
+
+ # FTP options
+ @values["ftp-user"] = "anonymous" # FTP user
+ @values["ftp-passwd"] = "ARIA2USER@" # FTP password
+ @values["ftp-pasv"] = true # use passive mode
+ #@values["ftp-proxy"] = "" # proxy server for FTP
+ @values["ftp-type"] = "binary" # transfer type
+ @values["ftp-reuse-connection"] = true # reuse connection
+
+ # BitTorrent options
+ @values["bt-external-ip"] = "" # report IP address to tracker
+ @values["bt-hash-check-seed"] = true # continue to seed after file is complete and check integrity
+ @values["bt-max-open-files"] = 100 # number of files to open in each download
+ @values["bt-max-peers"] = 55 # number of maximum peers, 0 means unlimited
+ @values["bt-min-crypto-level"] = "plain" # minimum level of encryption, plain or arc4
+ @values["bt-prioritize-piece"] = "" # can contain head,tail useful for previewing files
+ @values["bt-require-crypto"] = false # accept and establish connection with crypto only
+ @values["bt-request-peer-speed-limit"] = "50K" # tries more peers if download speed is under value
+ @values["bt-save-metadata"] = false # save metadata to .torrent file
+ @values["bt-seed-unverified"] = false # seed without verifying piece hashes
+ @values["bt-stop-timeout"] = 0 # stop download if speed is 0 during given seconds, 0 does nothing
+ @values["bt-tracker-interval"] = 0 # interval in seconds between tracker requests, 0 does nothing
+ #@values["dht-entry-point"] = "" # set HOST:PORT as entry point to DHT network
+ #@values["dht-file-path"] = "" # file name of DHT routing table, $XDG_CONFIG_HOME/eatmonkey/dht.dat
+ @values["dht-listen-port"] = "6881-6999" # UDP listening port for DHT
+ @values["enable-dht"] = true # enable DHT functionality
+ @values["enable-peer-exchange"] = true # enable PEX extension
+ @values["follow-torrent"] = true # parse .torrent file and downloads mentionned files, can be mem
+ @values["listen-port"] = "6881-6999" # TCP listening port for BitTorrent
+ @values["max-overall-upload-limit"] = 0 # max overall upload speed in B/s, 0 means unlimited
+ @values["max-upload-limit"] = 0 # max upload speed per each download, 0 means unlimited
+ @values["peer-id-prefix"] = "Eatmonkey/0.1.0" # set peer ID, max 20 bytes
+ @values["seed-ratio"] = 1.0 # specify share ratio
+
+ # Metalink options
+ @values["follow-metalink"] = true # parse .metalink file and downloads mentionned files, can be mem
+ @values["metalink-servers"] = 5 # number of servers to connect to simultaneously
+ @values["metalink-language"] = "" # language of the file to download
+ @values["metalink-location"] = "" # preferred location of the server to use, can be JP,US
+ @values["metalink-os"] = "" # operating system of the file to download
+ @values["metalink-version"] = "" # version of the file to download
+ @values["metalink-preferred-protocol"] = "none" # preferred protocol to use, can be http,https,ftp
+ @values["metalink-enable-unique-protocol"] = true # use only one type of protocol for download
+
+ # XML-RPC options
+ @values["enable-xml-rpc"] = false
+ @values["xml-rpc-listen-all"] = false
+ @values["xml-rpc-listen-port"] = 6800
+ @values["xml-rpc-max-request-size"] = "2M"
+ @values["xml-rpc-passwd"] = ""
+ @values["xml-rpc-user"] = ""
+
+ # Advanced options
+ @values["allow-overwrite"] = false # re-download the file even if file exists
+ @values["allow-piece-length-change"] = false # continue download even if piece length is different from control file
+ @values["async-dns"] = true # asynchronous DNS
+ @values["auto-file-renaming"] = true # rename filename if file already exists
+ @values["auto-save-interval"] = 60 # save a .aria2 control file every SEC seconds
+ @values["conf-path"] = "" # change the configuration file path
+ @values["daemon"] = false # run as daemon
+ @values["disable-ipv6"] = false # disable IPv6
+ @values["enable-direct-io"] = true # enable direct I/O to lower CPU cycles while allocating/checking files
+ @values["file-allocation"] = "prealloc" # file allocation method, can be none, prealloc, falloc
+ @values["interface"] = "" # bind sockets to given interface, can be interface name, IP and hostname
+ @values["log-level"] = "debug" # log level, can be debug,info,notice,warn,error
+ @values["on-download-complete"] = "" # command to execute when download completes, cf. start
+ @values["on-download-error"] = "" # command to execute when download aborts, cf. start
+ @values["on-download-start"] = "" # command to execute when download starts, GID is passed to command
+ @values["on-download-stop"] = "" # command to execute when download stops, not executed if complete/error is set
+ @values["summary-interval"] = 60 # interval in seconds to output summary, 0 to supress the output
+ @values["force-sequential"] = false # download one URI after another
+ @values["max-overall-download-limit"] = 0 # max overall download speed in B/s, 0 means unlimited
+ @values["max-download-limit"] = 0 # max download speed per each download in B/s, 0 means unlimited
+ @values["no-file-allocation-limit"] = "5K" # no file allocation for files smaller than value
+ @values["parameterized-uri"] = false # enable parameterized URI support, e.g. {file1,file2} file[0-9]
+ @values["quiet"] = false # no console output
+ @values["realtime-chunk-checksum"] = true # validate chunk of data while downloading if checksums are provided
+ end
+
+ def parse()
+ @values.clear
+ default_values
+ begin
+ file = File.open(@filename)
+ while !file.eof and line = file.readline
+ next if line !~ /^([a-zA-Z0-9-]+)=(.*)$/
+ key = $1
+ val = $2.strip
+ if val == "true"
+ val = true
+ elsif val == "false"
+ val = false
+ elsif val =~ /^[0-9]+$/
+ val = val.to_i
+ elsif val =~ /^[0-9]+\.[0-9]+$/
+ val = val.to_f
+ end
+ @values[key] = val
+ end
+ file.close
+ rescue
+ end
+ end
+
+ public
+
+ def [](key)
+ @values[key]
+ end
+
+ def []=(key, val)
+ @values[key] = val
+ end
+
+ def filename=(filename)
+ @filename = filename
+ parse
+ end
+
+ def save()
+ begin
+ file = File.open(@filename, "w")
+ @values.each do |key,val|
+ file.puts("#{key}=#{val}")
+ end
+ file.close
+ rescue
+ p $!
+ end
+ end
+
+end
+
+
+
+if __FILE__ == $0
+ aria2 = Eat::Aria2.new
+ exit if !aria2.is_connected
+ puts "Version: aria2 "+aria2.version
+ #aria2.add_uri(["http://dlc.sun.com/torrents/info/osol-0906-x86.iso.torrent"])
+ begin
+ aria2.tell_active.each do |res|
+ gid = res["gid"]
+ puts " *** Set download max rate to 16KB ***"
+ aria2.change_options(gid, "max-download-limit" => "16000")
+ puts " *** Get status ***"
+ pp aria2.tell_status(gid)
+ puts " *** Get uris ***"
+ pp aria2.get_uris(gid)
+ puts " *** Get files ***"
+ pp aria2.get_files(gid)
+ puts " *** Get peers ***"
+ pp aria2.get_peers(gid).length
+ end
+ sleep 5
+ end while true
+end
+
diff --git a/src/eatmanager.rb b/src/eatmanager.rb
new file mode 100644
index 0000000..333e257
--- /dev/null
+++ b/src/eatmanager.rb
@@ -0,0 +1,302 @@
+#!/usr/bin/ruby -w
+
+require "gtk2"
+require "pango"
+
+require "eataria2"
+require "eatsettings"
+
+include Gtk
+
+
+
+module Eat
+end
+
+
+
+class Eat::Manager
+
+ def initialize()
+ # Setup aria2 listener
+ @aria2 = Eat::Aria2Listener.instance
+ @aria2.signal_connect("download_status") { |this, gid| update_row(gid) }
+ @aria2.signal_connect("download_completed") { |this, gid| update_row(gid) }
+ @aria2.signal_connect("download_removed") { |this, gid| update_row(gid) }
+
+ # Retrieve GUI widgets
+ builder = Builder.new
+ builder << "manager.ui"
+
+ @window = builder["window-manager"]
+ @treeview = builder["treeview-manager"]
+ @liststore = builder["liststore-manager"]
+ @statusbar = builder["statusbar-manager"]
+
+ @newdl_dialog = builder["dialog-new-download"]
+ @custom_uri = builder["custom-uri"]
+ @select_file = builder["select-file"]
+ @select_file_dialog = builder["filechooserdialog-new-download"]
+
+ # Setup actions
+ builder["action-add"].signal_connect("activate") { action_add }
+ builder["action-pause"].signal_connect("activate") { action_pause }
+ builder["action-resume"].signal_connect("activate") { action_resume }
+ builder["action-remove"].signal_connect("activate") { action_remove }
+ builder["action-settings"].signal_connect("activate") { show_settings_dialog }
+ builder["action-quit"].signal_connect("activate") { action_quit }
+ builder["action-about"].signal_connect("activate") { show_about_dialog }
+
+ # Setup window
+ @window.signal_connect('delete-event') { action_quit }
+
+ # Setup tree view
+ cell = CellRendererText.new
+ cell.ellipsize = Pango::ELLIPSIZE_END
+ column = builder["treeviewcolumn-completed"]
+ column.pack_start(cell, true)
+ column.add_attribute(cell, :text, 8)
+ cell = CellRendererText.new
+ cell.ellipsize = Pango::ELLIPSIZE_END
+ column = builder["treeviewcolumn-filepath"]
+ column.pack_start(cell, true)
+ column.add_attribute(cell, :text, 9)
+
+ # Setup statusbar
+ @statuscontext = { "main" => @statusbar.get_context_id("main context"),
+ "menu" => @statusbar.get_context_id("menu context") }
+ @statusbar.push(@statuscontext["main"], "Eatmonkey 0.1.0 - aria2 #{@aria2.version}")
+
+# builder["menuitem-add"].signal_connect("select") do
+# @statusbar.push(@statuscontext["menu"], "Add a new download")
+# end
+# builder["menuitem-add"].signal_connect("deselect") do
+# @statusbar.pop(@statuscontext["menu"])
+# end
+
+ # Setup new download dialog
+ file_filter = builder["filefilter-new-download"]
+ file_filter.name = "Torrents, Metalinks"
+ file_filter.add_pattern("*.torrent")
+ file_filter.add_pattern("*.metalink")
+ @select_file_dialog.filter = file_filter
+ @select_file.signal_connect('clicked') do
+ @select_file_dialog.unselect_all
+ res = @select_file_dialog.run
+ @custom_uri.text = @select_file_dialog.filename if res == Gtk::Dialog::RESPONSE_ACCEPT
+ @select_file_dialog.hide
+ end
+ end
+
+ def show()
+ @window.show_all
+ end
+
+ def update_row(gid)
+ row_iter = nil
+ gid_i = gid.to_i
+
+ result = @aria2.tell_status(gid)
+ return if result.empty?
+
+ @liststore.each do |model, path, iter|
+ next unless iter.get_value(0) == gid_i
+ row_iter = iter
+ break
+ end
+
+ if !row_iter
+ # Avoid adding rows with incomplete information
+ return if result["totalLength"] == "0" or result["status"] != "active"
+
+ # Add unknown active download to liststore
+ row_iter = @liststore.append
+ @liststore.set_value(row_iter, 0, gid_i)
+ uris = @aria2.get_uris(gid)
+ p uris
+ begin
+ # TODO Torrent downloads don't have a URI
+ @liststore.set_value(row_iter, 10, uris[0]["uri"])
+ rescue
+ end
+ end
+
+ case result["status"]
+ when "active" then
+ @liststore.set_value(row_iter, 1, result["connections"].to_i)
+ @liststore.set_value(row_iter, 2, result["completedLength"].to_i)
+ @liststore.set_value(row_iter, 3, result["uploadLength"].to_i)
+ @liststore.set_value(row_iter, 4, result["totalLength"].to_i)
+ @liststore.set_value(row_iter, 5, result["downloadSpeed"].to_i)
+ @liststore.set_value(row_iter, 6, result["uploadSpeed"].to_i)
+ @liststore.set_value(row_iter, 7, result["infoHash"])
+
+ completed = result["completedLength"].to_i
+ total = result["totalLength"].to_i
+ percent = total > 0 ? 100 * completed / total : 0
+ @liststore.set_value(row_iter, 8, percent)
+
+ result = @aria2.get_files(gid.to_s)
+ if result != nil and !result[0]["path"].empty?
+ @liststore.set_value(row_iter, 9, File.basename(result[0]["path"]))
+ end
+ when "complete" then
+ @liststore.set_value(row_iter, 8, 100)
+ # Update the name, useful for very small files for which
+ # this callback didn't run with the "active" state.
+ result = @aria2.get_files(gid.to_s)
+ if result != nil and !result[0]["path"].empty?
+ @liststore.set_value(row_iter, 9, File.basename(result[0]["path"]))
+ end
+ when "removed" then
+ @liststore.remove(row_iter)
+ when "error" then
+ @liststore.set_value(row_iter, 8, -1)
+ end
+ end
+
+ def show_about_dialog()
+ logo = Gdk::Pixbuf.new("/usr/share/pixmaps/eatmonkey-logo.png")
+ AboutDialog.show(@window,
+ "program-name" => "Eatmonkey",
+ "version" => "0.1.0",
+ "copyright" => "Copyright 2010 \xC2\xA9 Mike Massonnet",
+ "logo" => logo,
+ "comments" => "Stupid download manager for monkeys and Capuchins!" \
+ "\n\n" \
+ "Eatmonkey is a download manager that works exclusively with " \
+ "aria2, the ultra fast download utility. It has support for " \
+ "HTTP(s)/FTP, BitTorrent and Metalink files.")
+ end
+
+ def show_settings_dialog()
+ used_local_server = @aria2.use_local_server?
+
+ dialog = Eat::SettingsDialog.new(@window)
+ res = dialog.run
+
+ if res == Dialog::RESPONSE_OK
+
+ settings = Eat::Settings.instance
+
+ if settings["custom-server"]
+ # Switch to custom server
+ debug("switch to custom server")
+ @aria2.use_custom_server(settings["xmlrpc-host"], settings["xmlrpc-port"],
+ settings["xmlrpc-user"], settings["xmlrpc-passwd"])
+ @aria2.shutdown if used_local_server # TODO prevent stopping unfinished downloads
+ @aria2.connect(true)
+ else
+ # Switch to local server
+ if used_local_server
+ # TODO need to store active downloads to effectively restart them
+ # and avoid gid conflicts between old and new/readded downloads
+ res = 0
+ count = @aria2.tell_active.count
+ if count > 0
+ dialog = MessageDialog.new(@window,
+ Dialog::DESTROY_WITH_PARENT | Dialog::NO_SEPARATOR,
+ MessageDialog::QUESTION, MessageDialog::BUTTONS_YES_NO,
+ "Restart the aria2 XML-RPC Server?")
+ dialog.secondary_text = "To take the new changes into account aria2 has to be " \
+ "restarted, this will stop/resume the current downloads."
+ res = dialog.run
+ dialog.destroy
+ end
+ if res == Dialog::RESPONSE_YES or count == 0
+ debug("restart local server")
+ @aria2.shutdown
+ @aria2.connect(true)
+ end
+ else
+ debug("switch to local server")
+ @aria2.use_local_server
+ @aria2.connect(true)
+ end
+ end
+
+ end
+ end
+
+ private
+
+ def action_quit()
+ @aria2.shutdown # TODO make this optional
+ main_quit
+ end
+
+ def action_add()
+ @custom_uri.text = ""
+ @custom_uri.grab_focus
+ res = @newdl_dialog.run
+ @newdl_dialog.hide
+ uri = @custom_uri.text
+ if res == Dialog::RESPONSE_OK and !uri.empty?
+ # TODO check if it is a uri or a torrent/metalink file and use the right
+ # method addUri/addTorrent/addMetalink
+ puts "download file %s" % uri
+ gid = @aria2.add_uri(uri)
+ if gid != nil
+ puts "gid: %s" % gid
+
+ # Add new row to liststore
+ row_iter = @liststore.append
+ @liststore.set_value(row_iter, 0, gid.to_i)
+ @liststore.set_value(row_iter, 9, File.basename(uri))
+ @liststore.set_value(row_iter, 10, uri)
+ end
+ end
+ end
+
+ def action_pause()
+ # TODO stop the download and on resume start it again at position 0 to put
+ # it in the front of the queue => drop the lowest-speed-limit hack
+
+ # This is sorta a hack around aria2, the download speed limit
+ # is drown under the lowest-speed-limit option and will thus be
+ # stopped. After a short while it will render an error code 5
+ # and will need to be restarted, cf. action_resume().
+ @treeview.selection.selected_each do |model, path, iter|
+ gid = iter.get_value(0)
+ @aria2.change_options(gid.to_s,
+ "max-download-limit" => "500",
+ "max-upload-limit" => "500")
+ end
+ end
+
+ def action_resume()
+ @treeview.selection.selected_each do |model, path, iter|
+ gid = iter.get_value(0).to_s
+ status = @aria2.tell_status(gid)
+ case status["status"]
+ when "active"
+ @aria2.change_options(gid,
+ "max-download-limit" => "0",
+ "max-upload-limit" => "0")
+ when "error"
+ next if status["errorCode"] != "5"
+ # Restart the download and delete current row as new gid will be created
+ @aria2.add_uri(iter.get_value(10))
+ @liststore.remove(iter)
+ end
+ end
+ end
+
+ def action_remove()
+ @treeview.selection.selected_each do |model, path, iter|
+ gid = iter.get_value(0)
+ @aria2.remove(gid.to_s)
+ @liststore.remove(iter)
+ end
+ end
+
+end
+
+
+
+if __FILE__ == $0
+ manager = Eat::Manager.new
+ manager.show
+ main
+end
+
diff --git a/src/eatsettings.rb b/src/eatsettings.rb
new file mode 100644
index 0000000..397cdce
--- /dev/null
+++ b/src/eatsettings.rb
@@ -0,0 +1,223 @@
+#!/usr/bin/ruby -w
+
+require "gtk2"
+require "singleton"
+
+require "eataria2"
+
+include Gtk
+
+module Eat
+end
+
+
+
+class Eat::Settings
+
+ include Singleton
+
+ attr_reader :aria2
+ attr_accessor :values
+
+ def initialize()
+ @conf_dir = ENV["XDG_CONFIG_HOME"]+"/eatmonkey/"
+ @aria2 = Eat::Aria2Config.new(@conf_dir+"aria2.conf")
+ @values = {}
+ parse
+ end
+
+ private
+
+ def default_values()
+ # Download Directory
+ # g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD) or how not to waste time
+ if @aria2["dir"] == nil
+ if ENV["XDG_DOWNLOAD_DIR"] != nil
+ @aria2["dir"] = ENV["XDG_DOWNLOAD_DIR"]
+ else
+ dir = nil
+ begin
+ File.open(ENV["XDG_CONFIG_HOME"]+"/user-dirs.dirs") do |f|
+ f.grep(/^XDG_DOWNLOAD_DIR=/) do |line|
+ dir = line.sub(/^XDG_DOWNLOAD_DIR="(.*)"\n$/, '\1').sub('$HOME', ENV['HOME'])
+ end
+ end
+ rescue
+ end
+
+ if dir != nil
+ @aria2["dir"] = dir
+ elsif Dir.exists?(ENV["HOME"]+"/Downloads")
+ @aria2["dir"] = ENV["HOME"]+"/Downloads"
+ else
+ @aria2["dir"] = ENV["HOME"]
+ end
+ end
+ end
+
+ # XML-RPC Server
+ @values["custom-server"] = false
+ @values["xmlrpc-host"] = "127.0.0.1"
+ @values["xmlrpc-port"] = 6800
+ end
+
+ def parse()
+ @values.clear
+ default_values
+ begin
+ file = File.open(@conf_dir+"eatmonkeyrc")
+ while !file.eof and line = file.readline
+ next if line !~ /^([a-zA-Z0-9-]+)=(.*)$/
+ key = $1
+ val = $2.strip
+ if val == "true"
+ val = true
+ elsif val == "false"
+ val = false
+ elsif val =~ /^[0-9]+$/
+ val = val.to_i
+ end
+ @values[key] = val
+ end
+ file.close
+ rescue
+ end
+ end
+
+ public
+
+ def [](key)
+ @values[key]
+ end
+
+ def []=(key, val)
+ @values[key] = val
+ end
+
+ def save()
+ aria2.save
+ begin
+ file = File.open(@conf_dir+"eatmonkeyrc", "w")
+ @values.each do |key,val|
+ file.puts("#{key}=#{val}")
+ end
+ file.close
+ rescue
+ p $!
+ end
+ end
+
+end
+
+
+
+class Eat::SettingsDialog
+
+ attr_reader :dialog
+
+ def initialize(parent_window = nil)
+ builder = Builder.new
+ builder << "settings.ui"
+
+ @dialog = builder["dialog-settings"]
+ @xmlrpc = Hash.new
+ @xmlrpc[:vbox] = builder["xmlrpc-vbox"]
+ @xmlrpc[:custom_server] = builder["action-custom-server"]
+ @xmlrpc[:host] = builder["xmlrpc-host"]
+ @xmlrpc[:port] = builder["xmlrpc-port"]
+ @xmlrpc[:user] = builder["xmlrpc-user"]
+ @xmlrpc[:passwd] = builder["xmlrpc-passwd"]
+ @basic_options = Hash.new
+ @basic_options[:frame] = builder["basic-options-frame"]
+ @basic_options[:max_concurrent_downloads] = builder["max-concurrent-downloads"]
+ @basic_options[:max_download_speed] = builder["max-download-speed"]
+ @basic_options[:download_dir_button] = builder["download-dir-button"]
+ @bittorrent = Hash.new
+ @bittorrent[:frame] = builder["bittorrent-frame"]
+ @bittorrent[:max_upload_speed] = builder["max-upload-speed"]
+ @bittorrent[:max_peers] = builder["max-peers"]
+ @bittorrent[:seed_ratio] = builder["seed-ratio"]
+ @bittorrent[:enable_dht] = builder["enable-dht"]
+ @bittorrent[:enable_pex] = builder["enable-pex"]
+ @bittorrent[:require_encryption] = builder["require-encryption"]
+
+ # Setup parent window
+ @dialog.set_transient_for(parent_window)
+
+ # Setup actions
+ builder["action-custom-server"].signal_connect("toggled") { action_custom_server }
+
+ # Setup values
+ @settings = Eat::Settings.instance
+ @xmlrpc[:custom_server].active = @settings["custom-server"]
+ @xmlrpc[:host].text = @settings["xmlrpc-host"] if @settings["xmlrpc-host"] != nil
+ @xmlrpc[:port].value = @settings["xmlrpc-port"] if @settings["xmlrpc-port"] != nil
+ @xmlrpc[:user].text = @settings["xmlrpc-user"] if @settings["xmlrpc-user"] != nil
+ @xmlrpc[:passwd].text = @settings["xmlrpc-passwd"] if @settings["xmlrpc-passwd"] != nil
+ @basic_options[:max_concurrent_downloads].value = @settings.aria2["max-concurrent-downloads"]
+ @basic_options[:max_download_speed].value = @settings.aria2["max-overall-download-limit"]
+ @basic_options[:download_dir_button].current_folder = @settings.aria2["dir"]
+ @bittorrent[:max_upload_speed].value = @settings.aria2["max-overall-upload-limit"]
+ @bittorrent[:max_peers].value = @settings.aria2["bt-max-peers"]
+ @bittorrent[:seed_ratio].value = @settings.aria2["seed-ratio"]
+ @bittorrent[:enable_dht].active = @settings.aria2["enable-dht"]
+ @bittorrent[:enable_pex].active = @settings.aria2["enable-peer-exchange"]
+ @bittorrent[:require_encryption].active = @settings.aria2["bt-require-crypto"]
+
+ @dialog.vbox.show_all
+ end
+
+ def show()
+ @dialog.show_all
+ end
+
+ def run()
+ # Run dialog
+ res = @dialog.run
+
+ # Update settings
+ if res == Dialog::RESPONSE_OK
+ @settings["custom-server"] = @xmlrpc[:custom_server].active?
+ @settings["xmlrpc-host"] = @xmlrpc[:host].text
+ @settings["xmlrpc-port"] = @xmlrpc[:port].value_as_int
+ @settings["xmlrpc-user"] = @xmlrpc[:user].text
+ @settings["xmlrpc-passwd"] = @xmlrpc[:passwd].text
+ @settings.aria2["max-concurrent-downloads"] = @basic_options[:max_concurrent_downloads].value_as_int
+ @settings.aria2["max-overall-download-limit"] = @basic_options[:max_download_speed].value_as_int
+ @settings.aria2["dir"] = @basic_options[:download_dir_button].current_folder
+ @settings.aria2["max-overall-upload-limit"] = @bittorrent[:max_upload_speed].value_as_int
+ @settings.aria2["bt-max-peers"] = @bittorrent[:max_peers].value_as_int
+ @settings.aria2["seed-ratio"] = @bittorrent[:seed_ratio].value
+ @settings.aria2["enable-dht"] = @bittorrent[:enable_dht].active?
+ @settings.aria2["enable-peer-exchange"] = @bittorrent[:enable_pex].active?
+ @settings.aria2["bt-require-crypto"] = @bittorrent[:require_encryption].active?
+ @settings.save
+ end
+
+ begin
+ yield res
+ rescue
+ end
+ @dialog.hide
+
+ res
+ end
+
+ private
+
+ def action_custom_server()
+ active = @xmlrpc[:custom_server].active?
+ @xmlrpc[:vbox].set_sensitive(active)
+ @basic_options[:frame].set_sensitive(!active)
+ @bittorrent[:frame].set_sensitive(!active)
+ end
+
+end
+
+
+
+if __FILE__ == $0
+ dialog = Eat::SettingsDialog.new
+ dialog.run
+end
+
diff --git a/src/manager.ui b/src/manager.ui
new file mode 100644
index 0000000..ac19766
--- /dev/null
+++ b/src/manager.ui
@@ -0,0 +1,792 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkListStore" id="liststore-manager">
+ <columns>
+ <!-- column-name gid -->
+ <column type="gint"/>
+ <!-- column-name connections -->
+ <column type="gint"/>
+ <!-- column-name completed -->
+ <column type="gint"/>
+ <!-- column-name uploaded -->
+ <column type="gint"/>
+ <!-- column-name total -->
+ <column type="gint"/>
+ <!-- column-name download_speed -->
+ <column type="gint"/>
+ <!-- column-name upload_speed -->
+ <column type="gint"/>
+ <!-- column-name info_hash -->
+ <column type="gchararray"/>
+ <!-- column-name file_length -->
+ <column type="gint"/>
+ <!-- column-name file_path -->
+ <column type="gchararray"/>
+ <!-- column-name uri -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="liststore1"/>
+ <object class="GtkWindow" id="window-manager">
+ <property name="title" translatable="yes">Eatmonkey</property>
+ <property name="default_width">420</property>
+ <property name="default_height">320</property>
+ <child>
+ <object class="GtkVBox" id="vbox-manager">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar-manager">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem-file">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu-file">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-add">
+ <property name="visible">True</property>
+ <property name="related_action">action-add</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-pause">
+ <property name="visible">True</property>
+ <property name="related_action">action-pause</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-resume">
+ <property name="label">_Resume</property>
+ <property name="visible">True</property>
+ <property name="related_action">action-resume</property>
+ <property name="use_underline">True</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-remove">
+ <property name="visible">True</property>
+ <property name="related_action">action-remove</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-quit">
+ <property name="visible">True</property>
+ <property name="related_action">action-quit</property>
+ <property name="use_action_appearance">True</property>
+ <property name="accel_path"><Manager>/File/Quit</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem-edit">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">E_dit</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu-edit">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-settings">
+ <property name="visible">True</property>
+ <property name="related_action">action-settings</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem-view">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_View</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu-view">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem-showmenubar">
+ <property name="visible">True</property>
+ <property name="related_action">toggleaction-showmenubar</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem-showtoolbar">
+ <property name="visible">True</property>
+ <property name="related_action">toggleaction-showtoolbar</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem2">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioMenuItem" id="menuitem-compactview">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_Compact View</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">menuitem-detailview</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioMenuItem" id="menuitem-detailview">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_Details View</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">menuitem-compactview</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioMenuItem" id="menuitem-history">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_History</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">menuitem-compactview</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioMenuItem" id="menuitem-log">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label" translatable="yes">_Log</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">menuitem-compactview</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">H_elp</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-about">
+ <property name="visible">True</property>
+ <property name="related_action">action-about</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar-manager">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-add">
+ <property name="visible">True</property>
+ <property name="related_action">action-add</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-pause">
+ <property name="visible">True</property>
+ <property name="related_action">action-pause</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-resume">
+ <property name="visible">True</property>
+ <property name="related_action">action-resume</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-remove">
+ <property name="visible">True</property>
+ <property name="related_action">action-remove</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton-sep1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-settings">
+ <property name="visible">True</property>
+ <property name="related_action">action-settings</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-about">
+ <property name="visible">True</property>
+ <property name="related_action">action-about</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton-quit">
+ <property name="visible">True</property>
+ <property name="related_action">action-quit</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow-manager">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkTreeView" id="treeview-manager">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">liststore-manager</property>
+ <property name="headers_clickable">False</property>
+ <property name="search_column">0</property>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn-filepath">
+ <property name="resizable">True</property>
+ <property name="title">File path</property>
+ <property name="expand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn-completed">
+ <property name="resizable">True</property>
+ <property name="title">Completed</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStatusbar" id="statusbar-manager">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAction" id="action-add">
+ <property name="stock_id">gtk-add</property>
+ </object>
+ <object class="GtkAction" id="action-pause">
+ <property name="stock_id">gtk-media-pause</property>
+ </object>
+ <object class="GtkAction" id="action-remove">
+ <property name="stock_id">gtk-remove</property>
+ </object>
+ <object class="GtkAction" id="action-resume">
+ <property name="label">_Resume</property>
+ <property name="stock_id">gtk-media-play</property>
+ </object>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-media-play</property>
+ </object>
+ <object class="GtkAction" id="action-settings">
+ <property name="stock_id">gtk-preferences</property>
+ </object>
+ <object class="GtkAction" id="action-quit">
+ <property name="stock_id">gtk-quit</property>
+ </object>
+ <object class="GtkAction" id="action-about">
+ <property name="stock_id">gtk-about</property>
+ </object>
+ <object class="GtkToggleAction" id="toggleaction-showmenubar">
+ <property name="label">Show _menu bar</property>
+ <property name="sensitive">False</property>
+ <property name="active">True</property>
+ </object>
+ <object class="GtkToggleAction" id="toggleaction-showtoolbar">
+ <property name="label">Show _toolbar</property>
+ <property name="sensitive">False</property>
+ <property name="active">True</property>
+ </object>
+ <object class="GtkDialog" id="dialog-new-download">
+ <property name="title" translatable="yes">New download</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="icon_name">list-add</property>
+ <property name="type_hint">normal</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="transient_for">window-manager</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">6</property>
+ <property name="show_tabs">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkComboBox" id="combobox1">
+ <property name="model">liststore1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="border_width">6</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkFileChooserButton" id="filechooserbutton1">
+ <property name="visible">True</property>
+ <property name="create_folders">False</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select from file</property>
+ <property name="use_markup">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkEntry" id="custom-uri">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="select-file">
+ <property name="label">...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="image">image2</property>
+ <property name="image_position">right</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Open from URI/File</property>
+ <property name="use_markup">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label">page 1</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="border_width">6</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="label">gtk-clear</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select from clipboard</property>
+ <property name="use_markup">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label">page 2</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label">page 3</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">button2</action-widget>
+ <action-widget response="-6">button1</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-open</property>
+ <property name="icon-size">1</property>
+ </object>
+ <object class="GtkFileChooserDialog" id="filechooserdialog-new-download">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Select a file - Torrents, Metalinks</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="icon_name">document-open</property>
+ <property name="type_hint">normal</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="transient_for">dialog-new-download</property>
+ <property name="has_separator">False</property>
+ <property name="use_preview_label">False</property>
+ <property name="filter">filefilter-new-download</property>
+ <property name="create_folders">False</property>
+ <property name="preview_widget_active">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button4">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button5">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button4</action-widget>
+ <action-widget response="-3">button5</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkFileFilter" id="filefilter-new-download"/>
+</interface>
diff --git a/src/settings.ui b/src/settings.ui
new file mode 100644
index 0000000..43494cf
--- /dev/null
+++ b/src/settings.ui
@@ -0,0 +1,770 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="dialog-settings">
+ <property name="title" translatable="yes">Settings</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="icon_name">preferences-other</property>
+ <property name="type_hint">normal</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">6</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton-custom-server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="related_action">action-custom-server</property>
+ <property name="use_action_appearance">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="xmlrpc-alignment">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="xmlrpc-vbox">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Host:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="xmlrpc-host">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="xmlrpc-port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">5</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">adjustment1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="expander1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Username:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="xmlrpc-user">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Password:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="xmlrpc-passwd">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">•</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Authentication</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">aria2 XML-RPC Server</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="basic-options-frame">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="basic-options-vbox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Maximum number of concurrent downloads:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max-concurrent-downloads">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">adjustment2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Maximum download speed:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max-download-speed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">adjustment-max-download-speed</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">(0=infinite)</property>
+ <attributes>
+ <attribute name="size" value="7500"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Download directory:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="download-dir-button">
+ <property name="visible">True</property>
+ <property name="action">select-folder</property>
+ <property name="create_folders">False</property>
+ <property name="width_chars">14</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Download Options</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">General</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="bittorrent-frame">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Maximum upload speed:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max-upload-speed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">adjustment-max-upload-speed</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">(0=infinite)</property>
+ <attributes>
+ <attribute name="size" value="7500"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox9">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Maximum number of peers:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max-peers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">adjustment-max-peers</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">(0=infinite)</property>
+ <attributes>
+ <attribute name="size" value="7500"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Seed ratio:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="seed-ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">adjustment-seed-ratio</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">(0=infinite)</property>
+ <attributes>
+ <attribute name="size" value="7500"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="enable-dht">
+ <property name="label" translatable="yes">Enable DHT (Distributed Hash Table)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Distributed Hash Table allows to use torrents without a working tracker</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="enable-pex">
+ <property name="label" translatable="yes">Enable PEX (Peer Exchange)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Peer Exchange helps finding more peers through connected peers</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="require-encryption">
+ <property name="label" translatable="yes">Always require encryption</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">BitTorrent</property>
+ <property name="use_markup">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">BitTorrent</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">button2</action-widget>
+ <action-widget response="-6">button1</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="value">6800</property>
+ <property name="lower">1</property>
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkToggleAction" id="action-custom-server">
+ <property name="label">Connect to custom server</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="value">5</property>
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment-max-download-speed">
+ <property name="upper">9999</property>
+ <property name="step_increment">4</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment-max-upload-speed">
+ <property name="upper">9999</property>
+ <property name="step_increment">4</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment-seed-ratio">
+ <property name="value">1</property>
+ <property name="upper">99</property>
+ <property name="step_increment">0.1</property>
+ <property name="page_increment">0.1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment-max-peers">
+ <property name="value">55</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">5</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
More information about the Xfce4-commits
mailing list