summaryrefslogtreecommitdiffstats
path: root/maketorrent.py
diff options
context:
space:
mode:
Diffstat (limited to 'maketorrent.py')
-rwxr-xr-xmaketorrent.py577
1 files changed, 577 insertions, 0 deletions
diff --git a/maketorrent.py b/maketorrent.py
new file mode 100755
index 0000000..ba5d275
--- /dev/null
+++ b/maketorrent.py
@@ -0,0 +1,577 @@
+#!/usr/bin/env python
+
+# The contents of this file are subject to the BitTorrent Open Source License
+# Version 1.1 (the License). You may not copy or use this file, in either
+# source code or executable form, except in compliance with the License. You
+# may obtain a copy of the License at http://www.bittorrent.com/license/.
+#
+# Software distributed under the License is distributed on an AS IS basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+
+# Written by Matt Chisholm
+
+from __future__ import division
+
+from BitTorrent.platform import install_translation
+install_translation()
+
+import os
+import sys
+
+assert sys.version_info >= (2, 3), _("Install Python %s or greater") % '2.3'
+
+from threading import Event
+
+import gtk
+import gobject
+
+from BitTorrent.GUI import *
+from BitTorrent import Desktop
+from BitTorrent import version
+from BitTorrent import configfile
+from BitTorrent.defaultargs import get_defaults
+from BitTorrent.makemetafile import make_meta_files
+from BitTorrent.parseargs import makeHelp
+from BitTorrent.platform import btspawn
+from BitTorrent.ConvertedMetainfo import set_filesystem_encoding
+
+defaults = get_defaults('maketorrent')
+defconfig = dict([(name, value) for (name, value, doc) in defaults])
+del name, value, doc
+ui_options = ('torrent_dir','piece_size_pow2','tracker_list','use_tracker')
+
+def sfe_ef(e,s):
+ print s
+set_filesystem_encoding(defconfig['filesystem_encoding'], sfe_ef)
+
+EXTENSION = '.torrent'
+
+MAXIMUM_NODES = 8
+
+class MainWindow(Window):
+
+ def __init__(self, config):
+ Window.__init__(self)
+ self.mainwindow = self # temp hack to make modal win32 file choosers work
+ self.tooltips = gtk.Tooltips()
+ self.connect('destroy', self.quit)
+ self.set_title(_("%s torrent file creator %s")%(app_name, version))
+ self.set_border_width(SPACING)
+
+ self.config = config
+ self.tracker_list = []
+ if self.config['tracker_list']:
+ self.tracker_list = self.config['tracker_list'].split(',')
+
+ right_column_width=276
+ self.box = gtk.VBox(spacing=SPACING)
+
+ self.box.pack_start(lalign(gtk.Label(
+ _("Make torrent file for this file/directory:"))),
+ expand=False, fill=False)
+
+ self.filebox = gtk.Entry()
+ self.filebox.set_editable(False)
+ self.change_button = gtk.Button(_("Choose..."))
+ self.change_button.connect('clicked', self.choose_files)
+
+ hb = gtk.HBox(spacing=SPACING)
+ hb.pack_start(self.filebox, expand=True, fill=True)
+ hb.pack_end(self.change_button, expand=False, fill=False)
+
+ self.box.pack_start(hb, expand=False, fill=False, padding=0)
+
+ self.box.pack_start(lalign(gtk.Label(
+ _("(Directories will become batch torrents)"))),
+ expand=False, fill=False, padding=0)
+
+ self.box.pack_start(gtk.HSeparator(), expand=False, fill=False, padding=0)
+
+ self.table = gtk.Table(rows=3,columns=2,homogeneous=False)
+ self.table.set_col_spacings(SPACING)
+ self.table.set_row_spacings(SPACING)
+ y = 0
+
+ # Piece size
+ self.table.attach(ralign(gtk.Label(_("Piece size:"))),0,1,y,y+1,
+ xoptions=gtk.FILL, yoptions=0)
+
+ self.piece_size = gtk.combo_box_new_text()
+ self.piece_size.offset = 15
+ for i in range(7):
+ self.piece_size.append_text(str(Size(2**(i+self.piece_size.offset))))
+ self.piece_size.set_active(self.config['piece_size_pow2'] -
+ self.piece_size.offset)
+
+ self.piece_size_box = gtk.HBox(spacing=SPACING)
+ self.piece_size_box.pack_start(self.piece_size,
+ expand=False, fill=False)
+ self.table.attach(self.piece_size_box,1,2,y,y+1,
+ xoptions=gtk.FILL|gtk.EXPAND, yoptions=0)
+ y+=1
+
+
+ # Announce URL / Tracker
+ self.tracker_radio = gtk.RadioButton(group=None, label=_("Use _tracker:"))
+ self.tracker_radio.value = True
+
+ self.table.attach(self.tracker_radio,0,1,y,y+1,
+ xoptions=gtk.FILL, yoptions=0)
+ self.announce_entry = gtk.Entry()
+ self.announce_completion = gtk.EntryCompletion()
+ self.announce_entry.set_completion(self.announce_completion)
+ self.announce_completion.set_text_column(0)
+ self.build_completion()
+
+ self.tracker_radio.entry = self.announce_entry
+ if self.config['use_tracker'] == self.tracker_radio.value:
+ self.announce_entry.set_sensitive(True)
+ else:
+ self.announce_entry.set_sensitive(False)
+
+ if self.config['tracker_name']:
+ self.announce_entry.set_text(self.config['tracker_name'])
+ elif len(self.tracker_list):
+ self.announce_entry.set_text(self.tracker_list[0])
+ else:
+ self.announce_entry.set_text('http://my.tracker:6969/announce')
+
+ self.announce_entry.set_size_request(right_column_width,-1)
+ self.table.attach(self.announce_entry,1,2,y,y+1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0)
+ y+=1
+
+ # DHT / Trackerless
+ self.dht_radio = gtk.RadioButton(group=self.tracker_radio,
+ label=_("Use _DHT:"))
+ self.dht_radio.value = False
+
+ self.table.attach(align(self.dht_radio,0,0), 0,1,y,y+1,
+ xoptions=gtk.FILL|gtk.EXPAND, yoptions=0)
+
+ self.dht_nodes_expander = gtk.Expander(_("Nodes (optional):"))
+ self.dht_nodes_expander.connect('size-allocate', self.resize_to_fit)
+
+ self.dht_nodes = NodeList(self, 'router.bittorrent.com:6881')
+ self.dht_frame = gtk.Frame()
+ self.dht_frame.add(self.dht_nodes)
+ self.dht_frame.set_shadow_type(gtk.SHADOW_IN)
+ self.dht_nodes_expander.add(self.dht_frame)
+
+ self.table.attach(self.dht_nodes_expander,1,2,y,y+1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0)
+ self.dht_radio.entry = self.dht_nodes
+
+ if self.config['use_tracker'] == self.dht_radio.value:
+ self.dht_nodes.set_sensitive(True)
+ else:
+ self.dht_nodes.set_sensitive(False)
+
+ y+=1
+
+ for w in self.tracker_radio.get_group():
+ w.connect('toggled', self.toggle_tracker_dht)
+
+ for w in self.tracker_radio.get_group():
+ if w.value == bool(self.config['use_tracker']):
+ w.set_active(True)
+ else:
+ w.set_active(False)
+
+ # Hsep
+ self.table.attach(gtk.HSeparator(),0,2,y,y+1,yoptions=0)
+ y+=1
+
+ # Comment
+ self.comment_expander = gtk.Expander(_("Comments:"))
+ self.comment_expander.connect('size-allocate', self.resize_to_fit)
+
+ self.comment_buffer = gtk.TextBuffer()
+ self.comment_text = gtk.TextView()
+ self.comment_text.set_buffer(self.comment_buffer)
+ self.comment_text.set_wrap_mode(gtk.WRAP_WORD)
+ self.comment_scroll = gtk.ScrolledWindow()
+ self.comment_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
+ self.comment_scroll.set_shadow_type(gtk.SHADOW_IN)
+ self.comment_scroll.add(self.comment_text)
+
+ self.comment_expander.add(self.comment_scroll)
+ self.table.attach(self.comment_expander,0,2,y,y+1,
+ xoptions=gtk.FILL, yoptions=0)
+ y+=1
+
+ # add table
+ self.box.pack_start(self.table, expand=True, fill=True, padding=0)
+
+ # buttons
+
+ self.buttonbox = gtk.HBox(homogeneous=True, spacing=SPACING)
+
+ self.quitbutton = gtk.Button(stock=gtk.STOCK_CLOSE)
+##=======
+## # Piece size
+## self.table.attach(ralign(gtk.Label('Piece size:')),0,1,y,y+1,
+## xoptions=gtk.FILL, yoptions=0)
+
+
+## self.piece_size_box = gtk.HBox(spacing=SPACING)
+## self.piece_size_box.pack_start(self.piece_size, expand=False, fill=False)
+## self.table.attach(self.piece_size_box,1,2,y,y+1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0)
+## y+=1
+
+
+## self.box.pack_start(self.table, expand=True, fill=True)
+
+## self.buttonbox = gtk.HBox(homogeneous=True, spacing=SPACING)
+
+## self.quitbutton = gtk.Button(stock=gtk.STOCK_QUIT)
+##>>>>>>> remote
+ self.quitbutton.connect('clicked', self.quit)
+ self.buttonbox.pack_start(self.quitbutton, expand=True, fill=True)
+
+ self.buttonbox.pack_start(gtk.Label(''), expand=True, fill=True)
+
+ self.makebutton = IconButton(_("_Make"), stock=gtk.STOCK_EXECUTE)
+ self.makebutton.connect('clicked', self.make)
+ self.makebutton.set_sensitive(False)
+ self.buttonbox.pack_end(self.makebutton, expand=True, fill=True)
+
+ self.box.pack_end(self.buttonbox, expand=False, fill=False)
+
+ self.announce_entry.connect('changed', self.check_buttons)
+ self.filebox.connect('changed', self.check_buttons)
+ for w in self.tracker_radio.get_group():
+ w.connect('clicked', self.check_buttons)
+
+ self.box.pack_end(gtk.HSeparator(), expand=False, fill=False)
+
+ self.add(self.box)
+
+# HelpWindow(None, makeHelp('maketorrent', defaults))
+
+ self.box.show_all()
+## extraheight = self.dht_frame.size_request()[1] + \
+## self.comment_scroll.size_request()[1]
+## sr = self.box.size_request()
+## self.resize(sr[0] + SPACING*2, sr[1]+extraheight + SPACING*2)
+ self.resize_to_fit()
+ self.show_all()
+
+ def toggle_tracker_dht(self, widget):
+ if widget.get_active():
+ self.config['use_tracker'] = widget.value
+
+ for e in [self.announce_entry, self.dht_nodes]:
+ if widget.entry is e:
+ e.set_sensitive(True)
+ else:
+ e.set_sensitive(False)
+
+ def choose_files(self,widget):
+ fn = None
+ if self.config['torrent_dir']:
+ fn = self.config['torrent_dir']
+ else:
+ fn = Desktop.desktop
+
+ FileOrFolderSelection(self, fullname=fn,
+ got_multiple_location_func=self.add_files)
+
+ def add_files(self, names):
+ for name in names:
+ self.filebox.set_text(name)
+ torrent_dir = os.path.split(name)[0]
+ if torrent_dir[-1] != os.sep:
+ torrent_dir += os.sep
+ self.config['torrent_dir'] = torrent_dir
+
+ def get_piece_size_exponent(self):
+ i = self.piece_size.get_active()
+ exp = i+self.piece_size.offset
+ self.config['piece_size_pow2'] = exp
+ return exp
+
+ def get_file(self):
+ return self.filebox.get_text()
+
+ def get_announce(self):
+ if self.config['use_tracker']:
+ announce = self.announce_entry.get_text()
+ self.config['tracker_name'] = announce
+ else:
+ announce = self.dht_nodes.get_text()
+ return announce
+
+ def make(self, widget):
+ file_name = self.get_file()
+ piece_size_exponent = self.get_piece_size_exponent()
+ announce = self.get_announce()
+ comment = self.comment_buffer.get_text(
+ *self.comment_buffer.get_bounds())
+
+ if self.config['use_tracker']:
+ self.add_tracker(announce)
+ errored = False
+ if not errored:
+ d = ProgressDialog(self, [file_name,], announce,
+ piece_size_exponent, comment)
+ d.main()
+
+ def check_buttons(self, *widgets):
+ file_name = self.get_file()
+ tracker = self.announce_entry.get_text()
+ if file_name not in (None, ''):
+ if self.config['use_tracker']:
+ if len(tracker) >= len('http://x.cc'):
+ self.makebutton.set_sensitive(True)
+ else:
+ self.makebutton.set_sensitive(False)
+ else:
+ self.makebutton.set_sensitive(True)
+ else:
+ self.makebutton.set_sensitive(False)
+
+ def save_config(self):
+ def error_callback(error, string): print string
+ configfile.save_ui_config(self.config, 'maketorrent', ui_options, error_callback)
+
+ def quit(self, widget):
+ self.save_config()
+ self.destroy()
+
+ def add_tracker(self, tracker_name):
+ try:
+ self.tracker_list.pop(self.tracker_list.index(tracker_name))
+ except ValueError:
+ pass
+ self.tracker_list[0:0] = [tracker_name,]
+
+ self.config['tracker_list'] = ','.join(self.tracker_list)
+ self.build_completion()
+
+ def build_completion(self):
+ liststore = gtk.ListStore(gobject.TYPE_STRING)
+ for t in self.tracker_list:
+ liststore.append([t])
+ self.announce_completion.set_model(liststore)
+
+ def resize_to_fit(self, *args):
+ garbage, y = self.size_request()
+ x, garbage = self.get_size()
+ gobject.idle_add(self.resize, x, y)
+
+
+class AppWindow(MainWindow):
+ def quit(self, widget):
+ MainWindow.quit(self, widget)
+ gtk.main_quit()
+
+
+class NodeList(gtk.TreeView):
+ def __init__(self, parent, nodelist):
+ self.store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+ pre_size_list = ['router.bittorrent.com', '65536']
+ self.store.append(pre_size_list)
+
+ gtk.TreeView.__init__(self, self.store)
+ self.set_enable_search(False)
+
+ self.host_render = gtk.CellRendererText()
+ self.host_render.set_property('editable', True)
+ self.host_column = gtk.TreeViewColumn(_("_Host"), self.host_render,
+ text=0)
+ self.append_column(self.host_column)
+ self.host_render.connect('edited', self.store_host_value)
+
+ self.port_render = gtk.CellRendererText()
+ self.port_render.set_property('editable', True)
+ self.port_column = gtk.TreeViewColumn(_("_Port"), self.port_render,
+ text=1)
+ self.append_column(self.port_column)
+ self.port_render.connect('edited', self.store_port_value)
+
+ #self.columns_autosize()
+ #self.host_column.set_fixed_width(self.host_column.get_width())
+ #self.host_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ #self.realize()
+ #self.port_column.set_fixed_width(self.port_column.get_width())
+ #self.port_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+
+ self.store.clear()
+
+ for i, e in enumerate(nodelist.split(',')):
+ host, port = e.split(':')
+ self.store.append((host, port))
+
+ if i < MAXIMUM_NODES - 1:
+ self.store.append(('',''))
+
+ def store_host_value(self, cell, row, value):
+ parts = value.split('.')
+ if value != '':
+ for p in parts:
+ if not p.isalnum():
+ return
+ try:
+ value = value.encode('idna')
+ except UnicodeError:
+ return
+ it = self.store.get_iter_from_string(row)
+ self.store.set(it, 0, str(value))
+ self.check_row(row)
+
+ def store_port_value(self, cell, row, value):
+ if value != '':
+ try:
+ v = int(value)
+ if v > 65535:
+ value = 65535
+ if v < 0:
+ value = 0
+ except:
+ return # return on non-integer values
+ it = self.store.get_iter_from_string(row)
+ self.store.set(it, 1, str(value))
+ #curs = self.get_cursor()
+ #print curs
+ #print type(curs[0][0])
+
+ self.check_row(row)
+ #newpath = ((int(row)+1,), self.port_column)
+ #print newpath
+ #self.set_cursor(newpath)
+
+ def check_row(self, row):
+ # called after editing to see whether we should add a new
+ # blank row, or remove the now blank currently edited row.
+ it = self.store.get_iter_from_string(row)
+ host_value = self.store.get_value(it, 0)
+ port_value = self.store.get_value(it, 1)
+ if host_value and port_value and \
+ int(row) == len(self.store)-1 and \
+ int(row) < MAXIMUM_NODES -1 :
+ self.store.append(('',''))
+ elif host_value == '' and \
+ port_value == '' and \
+ int(row) != len(self.store)-1:
+ self.store.remove(it)
+
+ def get_nodes(self):
+ retlist = []
+ it = self.store.get_iter_first()
+ while it:
+ host = self.store.get_value(it, 0)
+ port = self.store.get_value(it, 1)
+ if host != '' and port != '':
+ retlist.append((host,port))
+ it = self.store.iter_next(it)
+ return retlist
+
+ def get_text(self):
+ nodelist = self.get_nodes()
+ return ','.join(['%s:%s'%node for node in nodelist])
+
+
+class ProgressDialog(gtk.Dialog):
+
+ def __init__(self, parent, file_list, announce, piece_length, comment):
+ gtk.Dialog.__init__(self, parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
+ self.set_size_request(400,-1)
+ self.set_border_width(SPACING)
+ self.set_title(_("Building torrents..."))
+ self.file_list = file_list
+ self.announce = announce
+ self.piece_length = piece_length
+ self.comment = comment
+
+ self.flag = Event() # ???
+
+ self.label = gtk.Label(_("Checking file sizes..."))
+ self.label.set_line_wrap(True)
+
+ self.vbox.set_spacing(SPACING)
+ self.vbox.pack_start(lalign(self.label), expand=False, fill=False)
+
+ self.progressbar = gtk.ProgressBar()
+ self.vbox.pack_start(self.progressbar, expand=False, fill=False)
+
+ self.cancelbutton = gtk.Button(stock=gtk.STOCK_CANCEL)
+ self.cancelbutton.connect('clicked', self.cancel)
+ self.action_area.pack_end(self.cancelbutton)
+
+ self.show_all()
+
+ self.done_button = gtk.Button(stock=gtk.STOCK_OK)
+ self.done_button.connect('clicked', self.cancel)
+
+ self.seed_button = gtk.Button(_("Start seeding"))
+ self.seed_button.connect('clicked', self.seed)
+
+ def main(self):
+ self.complete()
+
+ def seed(self, widget=None):
+ for f in self.file_list:
+ btspawn(None, 'bittorrent', f+EXTENSION, '--save_as', f)
+ self.cancel()
+
+ def cancel(self, widget=None):
+ self.flag.set()
+ self.destroy()
+
+ def set_progress_value(self, value):
+ self.progressbar.set_fraction(value)
+ self._update_gui()
+
+ def set_file(self, filename):
+ self.label.set_text(_("building ") + filename + EXTENSION)
+ self._update_gui()
+
+ def _update_gui(self):
+ while gtk.events_pending():
+ gtk.main_iteration(block=False)
+
+ def complete(self):
+ try:
+ make_meta_files(self.announce,
+ self.file_list,
+ flag=self.flag,
+ progressfunc=self.set_progress_value,
+ filefunc=self.set_file,
+ piece_len_pow2=self.piece_length,
+ comment=self.comment,
+ use_tracker=config['use_tracker'],
+ data_dir=config['data_dir'],
+ )
+ if not self.flag.isSet():
+ self.set_title(_("Done."))
+ self.label.set_text(_("Done building torrents."))
+ self.set_progress_value(1)
+ self.action_area.remove(self.cancelbutton)
+ self.action_area.pack_start(self.seed_button)
+ self.action_area.pack_start(self.done_button)
+ self.seed_button.show()
+ self.done_button.show()
+ except (OSError, IOError), e:
+ self.set_title(_("Error!"))
+ self.label.set_text(_("Error building torrents: ") + str(e))
+
+
+
+
+def run():
+ config, args = configfile.parse_configuration_and_args(defaults,
+ 'maketorrent', [], 0, None)
+ MainWindow(config)
+
+
+if __name__ == '__main__':
+ config, args = configfile.parse_configuration_and_args(defaults,
+ 'maketorrent', sys.argv[1:], 0, None)
+ AppWindow(config)
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ # gtk.mainloop not running
+ # exit and don't save config options
+ sys.exit(1)