diff --git a/plugins/WebUi/templates/deluge/index.html b/plugins/WebUi/templates/deluge/index.html
new file mode 100644
index 000000000..4c29278fc
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/index.html
@@ -0,0 +1,61 @@
+$def with (torrent_list, all_torrents)
+$:render.header(_('Torrent list'))
+
+
+
+ $:(sort_head('calc_state_str', 'S'))
+ $:(sort_head('queue_pos', '#'))
+ $:(sort_head('name', _('Name')))
+ $:(sort_head('total_size', _('Size')))
+ $:(sort_head('progress', _('Progress')))
+ $:(sort_head('num_seeds', _('Seeders')))
+ $:(sort_head('num_peers', _('Peers')))
+ $:(sort_head('download_rate', _('Download')))
+ $:(sort_head('upload_rate', _('Upload')))
+ $:(sort_head('eta', _('Eta')))
+ $:(sort_head('distributed_copies', _('Ava')))
+ $:(sort_head('ratio', _('Ratio')))
+
+$#4-space indentation is mandatory for for-loops in templetor!
+$for torrent in torrent_list:
+
+
+ |
+ $torrent.queue_pos |
+
+
+ $(crop(torrent.name, 40)) |
+ $fsize(torrent.total_size) |
+
+
+
+ $torrent.message $int(torrent.progress) %
+
+
+ |
+ $torrent.num_seeds ($torrent.total_seeds) |
+ $torrent.num_peers ($torrent.total_peers) |
+ $fspeed(torrent.download_rate) |
+ $fspeed(torrent.upload_rate) |
+ $torrent.eta |
+ $("%.3f" % torrent.distributed_copies) |
+ $("%.3f" % torrent.ratio) |
+
+
+
+
+
+
+$:render.part_button('GET', '/torrent/add', _('Add torrent'), 'tango/list-add.png')
+$:render.part_button('POST', '/pause_all', _('Pause all'), 'tango/pause.png')
+$:render.part_button('POST', '/resume_all', _('Resume all'), 'tango/start.png')
+
+
+
+$:part_stats()
+
+$:render.footer()
+
diff --git a/plugins/WebUi/templates/deluge/login.html b/plugins/WebUi/templates/deluge/login.html
new file mode 100644
index 000000000..0436e9076
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/login.html
@@ -0,0 +1,25 @@
+$def with (error)
+$:render.header(_('Login'))
+
+$if error > 0:
+
$_("Password is invalid,try again")
+
+
+
+$:render.footer()
diff --git a/plugins/WebUi/templates/deluge/part_button.html b/plugins/WebUi/templates/deluge/part_button.html
new file mode 100644
index 000000000..8c420560f
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/part_button.html
@@ -0,0 +1,26 @@
+$def with (method, url, title, image='')
+
+
+
diff --git a/plugins/WebUi/templates/deluge/part_stats.html b/plugins/WebUi/templates/deluge/part_stats.html
new file mode 100644
index 000000000..342b8049f
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/part_stats.html
@@ -0,0 +1,35 @@
+$def with (stats)
+
+
+
+
+$_('Auto refresh:')
+$if getcookie('auto_refresh') == '1':
+ ($getcookie('auto_refresh_secs')) $_('seconds')
+ $:render.part_button('GET', '/refresh/set', _('Set'), 'tango/preferences-system.png')
+ $:render.part_button('POST', '/refresh/off', _('Disable'), 'tango/process-stop.png')
+$else:
+ $_('Off')
+ $:render.part_button('POST', '/refresh/on', _('Enable'), 'tango/view-refresh.png')
+$#end
+
+
+
+
+
+ $_('Connections') : $stats.num_connections ($stats.max_num_connections)
+
+ $_('Down Speed') : $stats.download_rate ($stats.max_download)
+
+ $_('Up Speed') : $stats.upload_rate ($stats.max_upload)
+
+
+
+
+
+ ($_('About'))
+
+
+
+
+
diff --git a/plugins/WebUi/templates/deluge/refresh_form.html b/plugins/WebUi/templates/deluge/refresh_form.html
new file mode 100644
index 000000000..0ac2cffda
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/refresh_form.html
@@ -0,0 +1,11 @@
+$:render.header(_('Set Timeout'))
+
+
+
+$:render.footer()
diff --git a/plugins/WebUi/templates/deluge/sort_column_head.html b/plugins/WebUi/templates/deluge/sort_column_head.html
new file mode 100644
index 000000000..d354a6c4f
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/sort_column_head.html
@@ -0,0 +1,12 @@
+$def with (column_id, column_name, order, active_up, active_down)
+
+
+$column_name\
+$if active_up:
+
+$if active_down:
+
+
+ |
+
+
diff --git a/plugins/WebUi/templates/deluge/tab_meta.html b/plugins/WebUi/templates/deluge/tab_meta.html
new file mode 100644
index 000000000..e7f7f4bf2
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/tab_meta.html
@@ -0,0 +1,85 @@
+$def with (torrent)
+
+
+
+
+
+ $torrent.progress %
+ |
+
+
+
+$_('Downloaded'): |
+$torrent.calc_total_downloaded |
+
+$_('Uploaded'): |
+$torrent.calc_total_uploaded |
+
+
+$_('Seeders'): |
+$torrent.num_seeds ($torrent.total_seeds ) |
+
+$_('Share Ratio'): |
+$("%.3f" % torrent.ratio) |
+
+$_('Pieces'): |
+$torrent.num_pieces x $fsize(torrent.piece_length) |
+
+
+ |
+ |
+
+
+
+ |
+
+
+$_('Speed'): |
+$fspeed(torrent.download_rate) |
+
+$_('Speed'): |
+$fspeed(torrent.upload_rate) |
+
+$_('Peers'): |
+$torrent.num_peers ($torrent.total_peers ) |
+
+$_('ETA'): |
+$torrent.eta |
+
+$_('Availability'): |
+$("%.3f" % torrent.distributed_copies) |
+
+ |
+ |
+
+
+
+
+
+ |
+
+
+
+$_('Total Size'): |
+$fspeed(torrent.total_size) |
+
+$_('# Of Files'): |
+$torrent.num_files |
+
+$_('Tracker'): |
+$(crop(torrent.tracker, 30)) |
+
+$_('Tracker Status'): |
+$(crop(torrent.tracker_status, 30)) |
+
+$_('Next Announce'): |
+$torrent.next_announce |
+
+
+$_('Queue Position'): |
+$torrent.queue_pos |
+
+
+
+ |
diff --git a/plugins/WebUi/templates/deluge/torrent_add.html b/plugins/WebUi/templates/deluge/torrent_add.html
new file mode 100644
index 000000000..0ceb15aae
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/torrent_add.html
@@ -0,0 +1,23 @@
+$:render.header(_("Add Torrent"))
+
+$:render.footer()
diff --git a/plugins/WebUi/templates/deluge/torrent_delete.html b/plugins/WebUi/templates/deluge/torrent_delete.html
new file mode 100644
index 000000000..9c1c89e69
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/torrent_delete.html
@@ -0,0 +1,32 @@
+$def with (torrent_ids, torrent_list)
+$:render.header(_("Remove torrent"))
+
+$:render.footer()
\ No newline at end of file
diff --git a/plugins/WebUi/templates/deluge/torrent_info.html b/plugins/WebUi/templates/deluge/torrent_info.html
new file mode 100644
index 000000000..660273e1a
--- /dev/null
+++ b/plugins/WebUi/templates/deluge/torrent_info.html
@@ -0,0 +1,50 @@
+$def with (torrent)
+
+$:(render.header(torrent.message + '/' + torrent.name))
+
+
$_('Details')
+
+$:render.tab_meta(torrent)
+
+$if (torrent.action == 'start'):
+ $:render.part_button('POST', '/torrent/start/' + str(torrent.id), _('Resume'), 'tango/start.png')
+$else:
+ $:render.part_button('POST', '/torrent/stop/' + str(torrent.id), _('Pause'), 'tango/pause.png')
+
+
+$:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/list-remove.png')
+$:render.part_button('POST', '/torrent/reannounce/' + str(torrent.id), _('Reannounce'), 'tango/view-refresh.png')
+
+$:render.part_button('POST', '/torrent/queue/up/' + str(torrent.id), _('Queue Up'), 'tango/queue-up.png')
+$:render.part_button('POST', '/torrent/queue/down/' + str(torrent.id), _('Queue Down'), 'tango/queue-down.png')
+
+
+
+
+
+
+
+
+
+$:part_stats()
+
+$:render.footer()
diff --git a/plugins/WebUi/templates/hacking-templates.txt b/plugins/WebUi/templates/hacking-templates.txt
new file mode 100644
index 000000000..600ba907e
--- /dev/null
+++ b/plugins/WebUi/templates/hacking-templates.txt
@@ -0,0 +1,39 @@
+Quickstart:
+Just copy and rename an existing template.
+-The settings panel will see all directory's in this folder ,and let you choose your new template.
+-Clicking Ok in the settings panel will restart the webserver and reload your template.
+
+Limited "Subclassing":
+All templates are "subclassed" from the /deluge/ template.
+If a html file is not found in the template dir, the file from /deluge/ will be used.
+
+
+Notes:
+Please configure your editor to use 4-space indents instead of tabs.
+Or use scite and my config: http://mvoncken.sohosted.com/deluge/SciTEUser.properties.txt
+
+template language: http://webpy.org/templetor
+
+Exposed methods and variables (c&p from webserver_framework.py):
+ template.Template.globals.update({
+ 'sort_head': template_sort_head,
+ 'part_stats':template_part_stats,
+ 'crop': template_crop,
+ '_': _ , #gettext/translations
+ 'str': str, #because % in templetor is broken.
+ 'sorted': sorted,
+ 'get_config': get_config,
+ 'self_url': self_url,
+ 'fspeed': common.fspeed,
+ 'fsize': common.fsize,
+ 'render': ws.render, #for easy resuse of templates
+ 'rev': 'rev.%s' % (REVNO, ),
+ 'version': VERSION,
+ 'getcookie':getcookie,
+ 'get': lambda (var): getattr(web.input(**{var:None}), var) # unreadable :-(
+})
+
+I will update this file if there is interest in making templates.
+
+
+
diff --git a/plugins/WebUi/tests/test_all.py b/plugins/WebUi/tests/test_all.py
new file mode 100644
index 000000000..f6c750fea
--- /dev/null
+++ b/plugins/WebUi/tests/test_all.py
@@ -0,0 +1,382 @@
+"""
+Testing the REST api, not the units.
+unittest the right way feels so unpythonic :(
+!! BIG FAT WARNING !!: this test deletes active torrents .
+!! BIG FAT WARNING 2!!: this test hammers the tracker that is tested against.
+"""
+import unittest
+import cookielib, urllib2 , urllib
+import WebUi.webserver_common as ws
+import operator
+
+
+ws.init_05()
+print 'test-env=',ws.ENV
+
+
+
+#CONFIG:
+BASE_URL = 'http://localhost:8112'
+PWD = 'deluge'
+
+def get_status(id):
+ return ws.proxy.get_torrent_status(id,ws.TORRENT_KEYS)
+
+#BASE:
+#303 = see other
+#404 = not found
+#500 = server error
+#200 = OK, page exists.
+class TestWebUiBase(unittest.TestCase):
+ def setUp(self):
+ #cookie aware-opener that DOES NOT use redirects.
+ opener = urllib2.OpenerDirector()
+ self.cj = cookielib.CookieJar()
+ for handler in [urllib2.HTTPHandler(),urllib2.HTTPDefaultErrorHandler(),
+ urllib2.FileHandler(),urllib2.HTTPErrorProcessor(),
+ urllib2.HTTPCookieProcessor(self.cj)]:
+ opener.add_handler(handler)
+ #/opener
+ self.opener = opener
+
+ def open_url(self, page, post=None):
+ url = BASE_URL + page
+
+ if post == 1:
+ post = {'Force_a_post' : 'spam'}
+ if post:
+ post = urllib.urlencode(post)
+ r = self.opener.open(url , data = post)
+
+
+ #BUG: error-page does not return status 500, but status 200
+ #workaround...
+ data = r.read()
+ if '' in data:
+ error = IOError()
+ error.code = 500
+ #print data
+ raise error
+ if r.code <> 200:
+ fail('no code 200, error-code=%s' % r.code)
+ return r
+
+ def get_cookies(self):
+ return dict((c.name,c.value) for c in self.cj)
+ cookies = property(get_cookies)
+
+ def assert_status(self,status, page, post):
+ try :
+ r = self.open_url(page, post)
+ except IOError,e:
+ self.assertEqual(e.code, status)
+ else:
+ self.fail('page was found "%s" (%s)' % (page, r.code ))
+
+ def assert_404(self, page, post = None):
+ self.assert_status(404, page, post)
+
+ def assert_500(self, page, post = None):
+ self.assert_status(500, page, post)
+
+ def assert_303(self, page, redirect_to, post=None):
+ try :
+ r = self.open_url(page, post)
+ except IOError,e:
+ self.assertEqual(e.code, 303)
+ self.assertEqual(e.headers['Location'], redirect_to)
+ else:
+ #print r
+ self.fail('No 303!')
+
+ def assert_exists(self, page, post = None):
+ try :
+ r = self.open_url(page, post)
+ except IOError,e:
+ self.fail('page was not found "%s" (%s)' % (page, e.code))
+ else:
+ pass
+
+ first_torrent_id = property(lambda self: ws.proxy.get_session_state()[0])
+ first_torrent = property(lambda self: get_status(self.first_torrent_id))
+
+
+class TestNoAuth(TestWebUiBase):
+ def test303(self):
+ self.assert_303('/','/login')
+ self.assert_303('','/login')
+ self.assert_303('/index','/login')
+ #self.assert_303('/torrent/pause/','/login')
+ self.assert_303('/config','/login')
+ self.assert_303('/torrent/info/','/login')
+
+ def test404(self):
+ self.assert_404('/torrent/info')
+ self.assert_404('/garbage')
+ #self.assert_404('/static/garbage')
+ #self.assert_404('/template/static/garbage')
+ self.assert_404('/torrent/pause/', post=1)
+
+ def testOpen(self):
+ self.assert_exists('/login')
+ self.assert_exists('/about')
+
+ def testStatic(self):
+ self.assert_exists('/static/images/simple_line.jpg')
+ self.assert_exists('/static/images/tango/up.png')
+ #test 404
+
+ #test template-static
+
+
+
+class TestSession(TestWebUiBase):
+ def testLogin(self):
+ self.assert_303('/home','/login')
+ #invalid pwd:
+ self.assert_303('/login','/login?error=1',{'pwd':'invalid'})
+ #login
+ self.assert_303('/login','/index',{'pwd':PWD})
+ #now i'm logged-in!
+ #there are no sort-coockies yet so the default page is /index.
+ self.assert_303('/home','/index')
+ self.assert_exists('/index')
+ self.assert_exists('/config')
+ self.assert_exists('/torrent/add')
+ self.assert_303('/','/index')
+ self.assert_303('','/index')
+
+ #logout
+ self.assert_303('/logout','/login', post=1)
+ #really logged out?
+ self.assert_303('/','/login')
+ self.assert_303('','/login')
+ self.assert_303('/index','/login')
+ self.assert_303('/torrent/add','/login')
+ self.assert_exists('/about')
+
+
+ def testRefresh(self):
+ #starting pos
+ self.assert_303('/login','/index',{'pwd':PWD})
+ r = self.open_url('/index')
+ assert not 'auto_refresh' in self.cookies
+ assert not 'auto_refresh_secs' in self.cookies
+ assert not r.headers.has_key('Refresh')
+
+ #on:
+ self.assert_303('/refresh/on','/index', post=1)
+
+ assert 'auto_refresh' in self.cookies
+ assert 'auto_refresh_secs' in self.cookies
+ self.assertEqual(self.cookies['auto_refresh'],'1')
+ self.assertEqual(self.cookies['auto_refresh_secs'],'10')
+
+ r = self.open_url('/index')
+ assert r.headers['Refresh'] == '10 ; url=/index'
+
+ #set:
+ self.assert_303('/refresh/set','/index',{'refresh':'5'})
+ self.assertEqual(self.cookies['auto_refresh_secs'],'5')
+
+ r = self.open_url('/index')
+ assert r.headers['Refresh'] == '5 ; url=/index'
+ self.assert_500('/refresh/set',{'refresh':'a string'})
+
+ #off:
+ self.assert_303('/refresh/off','/index', post=1)
+ self.assertEqual(self.cookies['auto_refresh'],'0')
+ self.assertEqual(self.cookies['auto_refresh_secs'],'5')
+
+ r = self.open_url('/index')
+ assert not 'Refresh' in r.headers
+
+class TestIntegration(TestWebUiBase):
+ initialized = False
+ def setUp(self):
+ TestWebUiBase.setUp(self)
+
+ self.assert_303('/login','/index',{'pwd':PWD})
+ self.urls = sorted([
+ 'http://torrents.aelitis.com:88/torrents/azplatform2_1.13.zip.torrent',
+ 'http://torrents.aelitis.com:88/torrents/azplugins_2.1.4.jar.torrent',
+ 'http://torrents.aelitis.com:88/torrents/azautoseeder_0.1.1.jar.torrent'
+ ])
+
+ torrent_ids = ws.proxy.get_session_state()
+
+ #avoid hammering, investigate current torrent-list and do not re-add.
+ #correct means : 3 torrent's in list (for now)
+ if len(torrent_ids) <> 3:
+ #delete all, nice use case for refactoring delete..
+ torrent_ids = ws.proxy.get_session_state()
+ for torrent in torrent_ids:
+ ws.proxy.remove_torrent([torrent], False, False)
+
+ torrent_ids = ws.proxy.get_session_state()
+ self.assertEqual(torrent_ids, [])
+
+ #add 3 using url.
+ for url in self.urls:
+ self.assert_303('/torrent/add','/index',{'url':url,'torrent':None})
+
+ #added?
+ self.torrent_ids = ws.proxy.get_session_state()
+ self.assertEqual(len(self.torrent_ids), 3)
+
+ else:
+ #test correctness of existing-list
+ #The setup makes 0.6 fail everything, added an else..
+ for url in self.urls:
+ if ws.ENV.startswith('0.5'):
+ self.assert_500('/torrent/add',{'url':url,'torrent':None})
+ else:
+ self.assert_303('/torrent/add','/index',{'url':url,'torrent':None})
+
+ def testPauseResume(self):
+ #pause all
+ self.assert_303('/pause_all','/index', post=1)
+ #pause worked?
+ pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()]
+ for paused in pause_status:
+ self.assertEqual(paused, True)
+
+ #resume all
+ self.assert_303('/resume_all','/index', post=1)
+ #resume worked?
+ pause_status = [get_status(id)["user_paused"] for id in ws.proxy.get_session_state()]
+ for paused in pause_status:
+ self.assertEqual(paused,False)
+ #pause again.
+ self.assert_303('/pause_all','/index', post=1)
+
+ torrent_id = self.first_torrent_id
+ #single resume.
+ self.assert_303('/torrent/start/%s' % torrent_id ,'/index', post=1)
+ self.assertEqual(get_status(torrent_id)["user_paused"] ,False)
+ #single pause
+ self.assert_303('/torrent/stop/%s' % torrent_id,'/index', post=1)
+ self.assertEqual(get_status(torrent_id)["user_paused"] , True)
+
+ def testQueue(self):
+ #find last:
+ torrent_id = [id for id in ws.proxy.get_session_state()
+ if (get_status(id)['queue_pos'] ==3 )][0]
+
+ #queue
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 3)
+ #up:
+ self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1)
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 2)
+ self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1)
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 1)
+ self.assert_303('/torrent/queue/up/%s' % torrent_id,'/index', post=1)
+ #upper limit
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 1)
+ #down:
+ self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1)
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 2)
+ self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1)
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 3)
+ self.assert_303('/torrent/queue/down/%s' % torrent_id,'/index', post=1)
+ #down limit
+ torrent = get_status(torrent_id)
+ self.assertEqual(torrent['queue_pos'], 3)
+
+ def testMeta(self):
+ #info available?
+ for torrent_id in ws.proxy.get_session_state():
+ self.assert_exists('/torrent/info/%s' % torrent_id)
+ self.assert_exists('/torrent/delete/%s' % torrent_id)
+
+ #no info:
+ self.assert_500('/torrent/info/99999999')
+ self.assert_500('/torrent/delete/99999999')
+
+ def testAddRemove(self):
+ #add a duplicate:
+ self.assert_500('/torrent/add', post={'url':self.urls[0],'torrent':None})
+
+ #add a 4th using url
+
+ #delete
+
+ #add torrrent-file
+ #./test01.torrent
+
+
+ def test_do_redirect(self):
+ self.assert_303('/home','/index')
+ #1
+ self.assert_exists('/index?sort=download_rate&order=down')
+ self.assert_303('/home','/index?sort=download_rate&order=down')
+ assert self.cookies['sort'] == 'download_rate'
+ assert self.cookies['order'] == 'down'
+ #2
+ self.assert_exists('/index?sort=progress&order=up')
+ self.assert_303('/home','/index?sort=progress&order=up')
+ assert self.cookies['sort'] == 'progress'
+ assert self.cookies['order'] == 'up'
+ #redir after pause-POST? in /index.
+ self.assert_exists('/index?sort=name&order=down')
+ torrent_id = self.first_torrent_id
+ self.assert_303('/torrent/stop/%s' % torrent_id,
+ '/index?sort=name&order=down', post=1)
+ #redir in details 1
+ self.assert_303('/torrent/stop/%s?redir=/torrent/info/%s' %(torrent_id,torrent_id)
+ ,'/torrent/info/' + torrent_id, post = 1)
+ #redir in details 2
+ self.assert_303('/torrent/stop/%s' % torrent_id
+ ,'/torrent/info/' + torrent_id ,
+ post={'redir': '/torrent/info/' + torrent_id})
+
+ def testRemote(self):
+ pass
+
+ def test_redir_after_login(self):
+ pass
+
+ def testReannounce(self):
+ torrent_id = self.first_torrent_id
+ self.assert_303(
+ '/torrent/reannounce/%(id)s?redir=/torrent/info/%(id)s'
+ % {'id':torrent_id}
+ ,'/torrent/info/' + torrent_id, post = 1)
+
+ def testRecheck(self):
+ #add test before writing code..
+ #RELEASE-->disable
+ """
+ torrent_id = self.first_torrent_id
+ self.assert_303(
+ '/torrent/recheck/%(id)s?redir=/torrent/info/%(id)s'
+ % {'id':torrent_id}
+ ,'/torrent/info/' + torrent_id, post = 1)
+ """
+
+
+
+#
+
+if False:
+ suiteFew = unittest.TestSuite()
+
+ suiteFew.addTest(TestSession("testRefresh"))
+
+ unittest.TextTestRunner(verbosity=2).run(suiteFew)
+
+elif False:
+ suiteFew = unittest.TestSuite()
+ suiteFew.addTest(TestIntegration("testDoRedirect"))
+ unittest.TextTestRunner(verbosity=2).run(suiteFew)
+
+
+else:
+ unittest.main()
+
diff --git a/plugins/WebUi/version b/plugins/WebUi/version
new file mode 100644
index 000000000..3af99eeec
--- /dev/null
+++ b/plugins/WebUi/version
@@ -0,0 +1 @@
+180
diff --git a/plugins/WebUi/webserver_common.py b/plugins/WebUi/webserver_common.py
new file mode 100644
index 000000000..a169159fc
--- /dev/null
+++ b/plugins/WebUi/webserver_common.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) Martijn Voncken 2007
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, write to:
+# The Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+"""
+initializes config,render and proxy.
+All hacks go here, so this is a really ugly source-file..
+Support running in process0.5 ,run inside-gtk0.5 and run in process0.6
+"""
+
+import os
+import deluge
+import random
+import pickle
+import sys
+import base64
+from lib.webpy022 import template
+
+random.seed()
+webui_path = os.path.dirname(__file__)
+ENV = 'UNKNOWN'
+config_defaults = {
+ "port":8112,
+ "button_style":2,
+ "auto_refresh":False,
+ "auto_refresh_secs": 10,
+ "template":"advanced",
+ "pwd_salt":"2540626806573060601127357001536142078273646936492343724296134859793541603059837926595027859394922651189016967573954758097008242073480355104215558310954",
+ "pwd_md5":"\xea\x8d\x90\x98^\x9f\xa9\xe2\x19l\x7f\x1a\xca\x82u%",
+ "cache_templates":False,
+ "use_https":False
+}
+
+try:
+ _('translate something')
+except:
+ import gettext
+ gettext.install('~/')
+ #log.error('no translations :(')
+
+try:
+ config_dir = deluge.common.CONFIG_DIR
+except:
+ config_dir = os.path.expanduser("~/.config/deluge")
+
+config_file = os.path.join(config_dir,'webui.conf')
+session_file = os.path.join(config_dir,'webui.sessions')
+
+
+class subclassed_render(object):
+ """
+ try to use the html template in configured dir.
+ not available : use template in /deluge/
+ """
+ def __init__(self, template_dirname, cache=False):
+ self.base_template = template.render(
+ os.path.join(webui_path, 'templates/deluge/'),
+ cache=cache)
+
+ self.sub_template = template.render(
+ os.path.join(webui_path, 'templates/%s/' % template_dirname),
+ cache=cache)
+
+ def __getattr__(self, attr):
+ if hasattr(self.sub_template, attr):
+ return getattr(self.sub_template, attr)
+ else:
+ return getattr(self.base_template, attr)
+
+def init_process():
+ globals()['config'] = pickle.load(open(config_file))
+ globals()['render'] = subclassed_render(config.get('template'),
+ config.get('cache_templates'))
+
+def init_06():
+ import deluge.ui.client as proxy
+ from deluge.log import LOG as log
+ globals()['log'] = log
+
+ proxy.set_core_uri('http://localhost:58846') #How to configure this?
+
+ def add_torrent_filecontent(name , data_b64):
+ log.debug('monkeypatched add_torrent_filecontent:%s,len(data:%s))' %
+ (name , len(data_b64)))
+
+ name = name.replace('\\','/')
+ name = 'deluge06_' + str(random.random()) + '_' + name.split('/')[-1]
+ filename = os.path.join('/tmp', name)
+
+ log.debug('write: %s' % filename)
+ f = open(filename,"wb")
+ f.write(base64.b64decode(data_b64))
+ f.close()
+
+ proxy.add_torrent_file([filename])
+
+
+
+
+ proxy.add_torrent_filecontent = add_torrent_filecontent
+ log.debug('cfg-file %s' % config_file)
+ if not os.path.exists(config_file):
+ log.debug('create cfg file %s' % config_file)
+ #load&save defaults.
+ f = file(config_file,'wb')
+ pickle.dump(config_defaults,f)
+ f.close()
+
+ init_process()
+ globals()['proxy'] = proxy
+ globals()['ENV'] = '0.6'
+
+
+
+def init_05():
+ import dbus
+ init_process()
+ bus = dbus.SessionBus()
+ proxy = bus.get_object("org.deluge_torrent.dbusplugin"
+ , "/org/deluge_torrent/DelugeDbusPlugin")
+
+ globals()['proxy'] = proxy
+ globals()['ENV'] = '0.5_process'
+ init_logger()
+
+def init_gtk_05():
+ #appy possibly changed config-vars, only called in when runing inside gtk.
+ from dbus_interface import get_dbus_manager
+ globals()['proxy'] = get_dbus_manager()
+ globals()['config'] = deluge.pref.Preferences(config_file, False)
+ globals()['render'] = subclassed_render(config.get('template'),
+ config.get('cache_templates'))
+ globals()['ENV'] = '0.5_gtk'
+ init_logger()
+
+def init_logger():
+ #only for 0.5..
+ import logging
+ logging.basicConfig(level=logging.DEBUG,
+ format="[%(levelname)s] %(message)s")
+ globals()['log'] = logging
+
+
+#hacks to determine environment, TODO: clean up.
+if 'env=0.5' in sys.argv:
+ init_05()
+elif 'env=0.6' in sys.argv:
+ init_06()
+elif hasattr(deluge, 'ui'):
+ init_06()
+elif not hasattr(deluge,'pref'):
+ init_05()
+
+
+#constants
+REVNO = open(os.path.join(os.path.dirname(__file__),'revno')).read()
+VERSION = open(os.path.join(os.path.dirname(__file__),'version')).read()
+
+TORRENT_KEYS = ['distributed_copies', 'download_payload_rate',
+ 'eta', 'is_seed', 'name', 'next_announce',
+ 'num_files', 'num_peers', 'num_pieces', 'num_seeds', 'paused',
+ 'piece_length','progress', 'ratio', 'total_done', 'total_download',
+ 'total_payload_download', 'total_payload_upload', 'total_peers',
+ 'total_seeds', 'total_size', 'total_upload', 'total_wanted',
+ 'tracker_status', 'upload_payload_rate',
+ 'uploaded_memory','tracker','state','queue_pos','user_paused']
+
+STATE_MESSAGES = (_("Queued"),
+ _("Checking"),
+ _("Connecting"),
+ _("Downloading Metadata"),
+ _("Downloading"),
+ _("Finished"),
+ _("Seeding"),
+ _("Allocating"))
+
+SPEED_VALUES = [
+ (-1, 'Unlimited'),
+ (5, '5.0 Kib/sec'),
+ (10, '10.0 Kib/sec'),
+ (15, '15.0 Kib/sec'),
+ (25, '25.0 Kib/sec'),
+ (30, '30.0 Kib/sec'),
+ (50, '50.0 Kib/sec'),
+ (80, '80.0 Kib/sec'),
+ (300, '300.0 Kib/sec'),
+ (500, '500.0 Kib/sec')
+ ]
+
+#try:
+# SESSIONS = pickle.load(open(session_file))
+#except:
+SESSIONS = []
+
+
+
+
+
diff --git a/plugins/WebUi/webserver_framework.py b/plugins/WebUi/webserver_framework.py
new file mode 100644
index 000000000..b72acab66
--- /dev/null
+++ b/plugins/WebUi/webserver_framework.py
@@ -0,0 +1,424 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# webserver_framework.py
+#
+# Copyright (C) Martijn Voncken 2007
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, write to:
+# The Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+"""
+Todo's before stable:
+-__init__:kill->restart is not waiting for kill to be finished.
+--later/features:---
+-alternating rows?
+-set prio
+-clear finished?
+-torrent files.
+"""
+import lib.webpy022 as web
+
+from lib.webpy022.webapi import cookies, setcookie as w_setcookie
+from lib.webpy022.http import seeother, url
+from lib.webpy022 import template,changequery as self_url
+from lib.webpy022.utils import Storage
+from lib.static_handler import static_handler
+
+from deluge.common import fsize,fspeed
+
+import traceback
+import random
+from operator import attrgetter
+import datetime
+import pickle
+from md5 import md5
+from urlparse import urlparse
+
+from deluge import common
+from webserver_common import REVNO, VERSION, log
+import webserver_common as ws
+from debugerror import deluge_debugerror
+
+#init:
+web.webapi.internalerror = deluge_debugerror
+#/init
+debug_unicode = False
+#methods:
+def setcookie(key, val):
+ """add 30 days expires header for persistent cookies"""
+ return w_setcookie(key, val , expires=2592000)
+
+#really simple sessions, to bad i had to implement them myself.
+def start_session():
+ log.debug('start session')
+ session_id = str(random.random())
+ ws.SESSIONS.append(session_id)
+ #if len(ws.SESSIONS) > 20: #save max 20 sessions?
+ # ws.SESSIONS = ws.SESSIONS[-20:]
+ #not thread safe! , but a verry rare bug.
+ #f = open(ws.session_file,'wb')
+ #pickle.dump(ws.SESSIONS, f)
+ #f.close()
+ setcookie("session_id", session_id)
+
+def end_session():
+ session_id = getcookie("session_id")
+ #if session_id in ws.SESSIONS:
+ # ws.SESSIONS.remove(session_id)
+ #not thread safe! , but a verry rare bug.
+ #f = open(ws.session_file,'wb')
+ #pickle.dump(ws.SESSIONS, f)
+ #f.close()
+ setcookie("session_id","")
+
+def do_redirect():
+ """for redirects after a POST"""
+ vars = web.input(redir = None)
+ ck = cookies()
+ url_vars = {}
+
+ if vars.redir:
+ seeother(vars.redir)
+ return
+ #todo:cleanup
+ if ("order" in ck and "sort" in ck):
+ url_vars.update({'sort':ck['sort'] ,'order':ck['order'] })
+ if ("filter" in ck) and ck['filter']:
+ url_vars['filter'] = ck['filter']
+ if ("category" in ck) and ck['category']:
+ url_vars['category'] = ck['category']
+
+ seeother(url("/index", **url_vars))
+
+def error_page(error):
+ web.header("Content-Type", "text/html; charset=utf-8")
+ web.header("Cache-Control", "no-cache, must-revalidate")
+ print ws.render.error(error)
+
+def getcookie(key, default = None):
+ key = str(key).strip()
+ ck = cookies()
+ return ck.get(key, default)
+
+#deco's:
+def deluge_page_noauth(func):
+ """
+ add http headers
+ print result of func
+ """
+ def deco(self, name = None):
+ web.header("Content-Type", "text/html; charset=utf-8")
+ web.header("Cache-Control", "no-cache, must-revalidate")
+ res = func(self, name)
+ print res
+ deco.__name__ = func.__name__
+ return deco
+
+def check_session(func):
+ """
+ a decorator
+ return func if session is valid, else redirect to login page.
+ """
+ def deco(self, name = None):
+ log.debug('%s.%s(name=%s)' % (self.__class__.__name__, func.__name__,
+ name))
+ vars = web.input(redir_after_login = None)
+ ck = cookies()
+ if ck.has_key("session_id") and ck["session_id"] in ws.SESSIONS:
+ return func(self, name) #ok, continue..
+ elif vars.redir_after_login:
+ seeother(url("/login",redir=self_url()))
+ else:
+ seeother("/login") #do not continue, and redirect to login page
+ return deco
+
+def deluge_page(func):
+ return check_session(deluge_page_noauth(func))
+
+#combi-deco's:
+def auto_refreshed(func):
+ "decorator:adds a refresh header"
+ def deco(self, name = None):
+ if getcookie('auto_refresh') == '1':
+ web.header("Refresh", "%i ; url=%s" %
+ (int(getcookie('auto_refresh_secs',10)),self_url()))
+ return func(self, name)
+ deco.__name__ = func.__name__
+ return deco
+
+def remote(func):
+ "decorator for remote api's"
+ def deco(self, name = None):
+ try:
+ log.debug('%s.%s(%s)' ,self.__class__.__name__, func.__name__,name )
+ print func(self, name)
+ except Exception, e:
+ print 'error:%s' % e.message
+ print '-'*20
+ print traceback.format_exc()
+ deco.__name__ = func.__name__
+ return deco
+
+#utils:
+def check_pwd(pwd):
+ m = md5()
+ m.update(ws.config.get('pwd_salt'))
+ m.update(pwd)
+ return (m.digest() == ws.config.get('pwd_md5'))
+
+def get_stats():
+ stats = Storage({
+ 'download_rate':fspeed(ws.proxy.get_download_rate()),
+ 'upload_rate':fspeed(ws.proxy.get_upload_rate()),
+ 'max_download':ws.proxy.get_config_value('max_download_speed_bps'),
+ 'max_upload':ws.proxy.get_config_value('max_upload_speed_bps'),
+ 'num_connections':ws.proxy.get_num_connections(),
+ 'max_num_connections':ws.proxy.get_config_value('max_connections_global')
+ })
+ if stats.max_upload < 0:
+ stats.max_upload = _("Unlimited")
+ else:
+ stats.max_upload = fspeed(stats.max_upload)
+
+ if stats.max_download < 0:
+ stats.max_download = _("Unlimited")
+ else:
+ stats.max_download = fspeed(stats.max_download)
+
+ return stats
+
+
+def get_torrent_status(torrent_id):
+ """
+ helper method.
+ enhance ws.proxy.get_torrent_status with some extra data
+ """
+ status = Storage(ws.proxy.get_torrent_status(torrent_id,ws.TORRENT_KEYS))
+
+ #add missing values for deluge 0.6:
+ for key in ws.TORRENT_KEYS:
+ if not key in status:
+ status[key] = 0
+
+ status["id"] = torrent_id
+
+ url = urlparse(status.tracker)
+ if hasattr(url,'hostname'):
+ status.category = url.hostname or 'unknown'
+ else:
+ status.category = 'No-tracker'
+
+ #0.5-->0.6
+ status.download_rate = status.download_payload_rate
+ status.upload_rate = status.upload_payload_rate
+
+ #for naming the status-images
+ status.calc_state_str = "downloading"
+ if status.paused:
+ status.calc_state_str= "inactive"
+ elif status.is_seed:
+ status.calc_state_str = "seeding"
+
+ #action for torrent_pause
+ if status.user_paused:
+ status.action = "start"
+ else:
+ status.action = "stop"
+
+ if status.user_paused:
+ status.message = _("Paused")
+ elif status.paused:
+ status.message = _("Queued")
+ else:
+ status.message = (ws.STATE_MESSAGES[status.state])
+
+ #add some pre-calculated values
+ status.update({
+ "calc_total_downloaded" : (fsize(status.total_done)
+ + " (" + fsize(status.total_download) + ")"),
+ "calc_total_uploaded": (fsize(status.uploaded_memory
+ + status.total_payload_upload) + " ("
+ + fsize(status.total_upload) + ")"),
+ })
+
+ #no non-unicode string may enter the templates.
+ #FIXED,l was a translation bug..
+ if debug_unicode:
+ for k, v in status.iteritems():
+ if (not isinstance(v, unicode)) and isinstance(v, str):
+ try:
+ status[k] = unicode(v)
+ except:
+ raise Exception('Non Unicode for key:%s' % (k, ))
+ return status
+
+def get_categories(torrent_list):
+ trackers = [(torrent['category'] or 'unknown') for torrent in torrent_list]
+ categories = {}
+ for tracker in trackers:
+ categories[tracker] = categories.get(tracker,0) + 1
+ return categories
+
+def filter_torrent_state(torrent_list,filter_name):
+ filters = {
+ 'downloading': lambda t: (not t.paused and not t.is_seed)
+ ,'queued':lambda t: (t.paused and not t.user_paused)
+ ,'paused':lambda t: (t.user_paused)
+ ,'seeding':lambda t:(t.is_seed and not t.paused )
+ ,'traffic':lambda t: (t.download_rate > 0 or t.upload_rate > 0)
+ }
+ filter_func = filters[filter_name]
+ return [t for t in torrent_list if filter_func(t)]
+
+#/utils
+
+#template-defs:
+
+def get_category_choosers(torrent_list):
+ """
+ todo: split into 2 parts...
+ """
+ categories = get_categories(torrent_list)
+
+ filter_tabs = [Storage(title='All (%s)' % len(torrent_list),
+ filter='', category=None)]
+
+ #static filters
+ for title, filter_name in [
+ (_('Downloading'),'downloading') ,
+ (_('Queued'),'queued') ,
+ (_('Paused'),'paused') ,
+ (_('Seeding'),'seeding'),
+ (_('Traffic'),'traffic')
+ ]:
+ title += ' (%s)' % (
+ len(filter_torrent_state(torrent_list, filter_name)), )
+ filter_tabs.append(Storage(title=title, filter=filter_name))
+
+ categories = [x for x in get_categories(torrent_list).iteritems()]
+ categories.sort()
+
+ #trackers:
+ category_tabs = []
+ category_tabs.append(
+ Storage(title=_('Trackers'),category=''))
+ for title,count in categories:
+ category = title
+ title += ' (%s)' % (count, )
+ category_tabs.append(Storage(title=title, category=category))
+
+ return filter_tabs, category_tabs
+
+def category_tabs(torrent_list):
+ filter_tabs, category_tabs = get_category_choosers(torrent_list)
+ return ws.render.part_categories(filter_tabs, category_tabs)
+
+
+def template_crop(text, end):
+ if len(text) > end:
+ return text[0:end - 3] + '...'
+ return text
+
+def template_sort_head(id,name):
+ #got tired of doing these complex things inside templetor..
+ vars = web.input(sort = None, order = None)
+ active_up = False
+ active_down = False
+ order = 'down'
+
+ if vars.sort == id:
+ if vars.order == 'down':
+ order = 'up'
+ active_down = True
+ else:
+ active_up = True
+
+ return ws.render.sort_column_head(id, name, order, active_up, active_down)
+
+def template_part_stats():
+ return ws.render.part_stats(get_stats())
+
+def get_config(var):
+ return ws.config.get(var)
+
+irow = 0
+def altrow(reset = False):
+ global irow
+ if reset:
+ irow = 1
+ return
+ irow +=1
+ irow = irow % 2
+ return "altrow%s" % irow
+
+
+template.Template.globals.update({
+ 'sort_head': template_sort_head,
+ 'part_stats':template_part_stats,
+ 'category_tabs':category_tabs,
+ 'crop': template_crop,
+ '_': _ , #gettext/translations
+ 'str': str, #because % in templetor is broken.
+ 'int':int,
+ 'sorted': sorted,
+ 'altrow':altrow,
+ 'get_config': get_config,
+ 'self_url': self_url,
+ 'fspeed': common.fspeed,
+ 'fsize': common.fsize,
+ 'render': ws.render, #for easy resuse of templates
+ 'rev': 'rev.%s' % (REVNO, ),
+ 'version': VERSION,
+ 'getcookie':getcookie,
+ 'get': lambda (var): getattr(web.input(**{var:None}), var) # unreadable :-(
+})
+#/template-defs
+
+def create_webserver(urls, methods):
+ from lib.webpy022.request import webpyfunc
+ from lib.webpy022 import webapi
+ from lib.gtk_cherrypy_wsgiserver import CherryPyWSGIServer
+ import os
+
+ func = webapi.wsgifunc(webpyfunc(urls, methods, False))
+ server_address=("0.0.0.0", int(ws.config.get('port')))
+
+ server = CherryPyWSGIServer(server_address, func, server_name="localhost")
+ if ws.config.get('use_https'):
+ server.ssl_certificate = os.path.join(ws.webui_path,'ssl/deluge.pem')
+ server.ssl_private_key = os.path.join(ws.webui_path,'ssl/deluge.key')
+
+ print "http://%s:%d/" % server_address
+ return server
+
+#------
+__all__ = ['deluge_page_noauth', 'deluge_page', 'remote',
+ 'auto_refreshed', 'check_session',
+ 'do_redirect', 'error_page','start_session','getcookie'
+ ,'setcookie','create_webserver','end_session',
+ 'get_torrent_status', 'check_pwd','static_handler','get_categories'
+ ,'template','filter_torrent_state','log']
diff --git a/scripts/deluge b/scripts/deluge
index a8a607e32..978d2b283 100755
--- a/scripts/deluge
+++ b/scripts/deluge
@@ -46,6 +46,16 @@ import deluge._dbus as dbus
import deluge.interface
import deluge.pref
+print "checking for ubuntu..."
+if not deluge.common.windows_check():
+ if os.WEXITSTATUS(os.system('grep -iq "Ubuntu" /etc/issue')) == 0:
+ print "found and fixing ubuntu"
+ if os.environ.get("MOZILLA_FIVE_HOME") != "/usr/lib/firefox":
+ os.environ["MOZILLA_FIVE_HOME"] = "/usr/lib/firefox"
+ os.environ["LD_LIBRARY_PATH"] = "/usr/lib/firefox"
+ os.system("/usr/bin/deluge")
+ raise SystemExit
+
parser = OptionParser(usage="%prog [options] [torrents to add]",
version=deluge.common.PROGRAM_VERSION)
parser.add_option("-c", "--config", dest="config", help="Sets the configuration path")
diff --git a/setup.py b/setup.py
index 40e57f707..4edb2e2b4 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,7 @@
NAME = "deluge"
FULLNAME = "Deluge BitTorrent Client"
-VERSION = "0.5.7.9"
+VERSION = "0.5.7.95"
AUTHOR = "Zach Tibbitts, Alon Zakai, Marcos Pinto, Andrew Resch, Alex Dedul"
EMAIL = "zach@collegegeek.org, kripkensteiner@gmail.com, marcospinto@dipconsultants.com, alonzakai@gmail.com, rotmer@gmail.com"
DESCRIPTION = "A GTK BitTorrent client written in Python and C++"
@@ -170,6 +170,7 @@ else:
'-D__USE_W32_SOCKETS',
'-D_WIN32',
'-DWIN32',
+ '-DUNICODE',
'-DBOOST_ALL_NO_LIB',
'-D_FILE_OFFSET_BITS=64',
'-DBOOST_THREAD_USE_LIB',
diff --git a/src/bookmark.py b/src/bookmark.py
new file mode 100644
index 000000000..03b7f141f
--- /dev/null
+++ b/src/bookmark.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+#
+# bookmark.py
+#
+# Copyright (C) Marcos Pinto 2007
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, write to:
+# The Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+import cPickle
+import os.path
+import pygtk
+pygtk.require("2.0")
+import gtk.glade
+import common
+
+class BookmarkManager:
+ """class builds and displays our internal bookmark manager"""
+ def __init__(self, parent, toolbutton):
+ """show bookmark manager"""
+ self.bookmarks = []
+ self.toolbutton = toolbutton
+ self.menu = gtk.Menu()
+ self.parent = parent
+ self.widgets = gtk.glade.XML(common.get_glade_file("list_bookmarks.glade"))
+ self.list_dlg = self.widgets.get_widget("list_bookmarks_dialog")
+ self.treeview = self.widgets.get_widget("bookmarks_treeview")
+ self.add_bookmark_dialog = self.widgets.get_widget("add_bookmark_dialog")
+ self.edit_bookmark_dialog = self.widgets.get_widget("edit_bookmark_dialog")
+ if common.windows_check():
+ self.add_bookmark_dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ self.edit_bookmark_dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ else:
+ self.list_dlg.set_icon(common.get_logo(18))
+ self.edit_bookmark_dialog.set_icon(common.get_logo(18))
+ self.add_bookmark_dialog.set_icon(common.get_logo(18))
+ self.list_dlg.set_transient_for(self.parent.window)
+ self.signal_dic = { "on_button_cancel_clicked" : self.on_button_cancel_clicked,
+ "on_button_ok_clicked" : self.on_button_ok_clicked,
+ "on_button_add_clicked" : self.on_button_add_clicked,
+ "on_button_edit_clicked" : self.on_button_edit_clicked,
+ "on_button_remove_clicked" : self.on_button_remove_clicked,
+ "on_button_add_ok_clicked" : self.on_button_add_ok_clicked,
+ "on_button_add_cancel_clicked" : self.on_button_add_cancel_clicked,
+ "on_button_edit_ok_clicked" : self.on_button_edit_ok_clicked,
+ "on_button_edit_cancel_clicked" : self.on_button_edit_cancel_clicked,
+ "quit" : self.on_button_cancel_clicked
+ }
+ self.widgets.signal_autoconnect(self.signal_dic)
+ # Create a liststore for tier, url
+ self.liststore = gtk.ListStore(str, str)
+
+ # Create the columns
+ self.treeview.append_column(
+ gtk.TreeViewColumn("Name", gtk.CellRendererText(), text=0))
+ self.treeview.append_column(
+ gtk.TreeViewColumn("URL", gtk.CellRendererText(), text=1))
+
+ self.treeview.set_model(self.liststore)
+ self.liststore.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ self.load_bookmarks()
+
+ self.build_menu()
+
+ def show(self):
+ self.list_dlg.show()
+
+ def hide(self):
+ self.list_dlg.hide()
+ if common.windows_check():
+ path = os.path.join(common.CONFIG_DIR, "bookmarks.save")
+ else:
+ path = os.path.join(common.CONFIG_DIR, "mozilla", "bookmarks.save")
+ try:
+ bookmark_file = open(path, "wb")
+ cPickle.dump(self.bookmarks, bookmark_file)
+ bookmark_file.close()
+ except Exception, e:
+ print "Unable to save bookmarks file: %s", e
+
+ def load_bookmarks(self):
+ bookmarks = None
+ if common.windows_check():
+ path = os.path.join(common.CONFIG_DIR, "bookmarks.save")
+ else:
+ path = os.path.join(common.CONFIG_DIR, "mozilla", "bookmarks.save")
+
+ try:
+ bookmark_file = open(path, "rb")
+ bookmarks = cPickle.load(bookmark_file)
+ bookmark_file.close()
+ except Exception, e:
+ print "Unable to load bookmarks file: %s", e
+
+ if bookmarks == None:
+ return
+
+ self.liststore.clear()
+
+ for bookmark in bookmarks:
+ self.liststore.append([bookmark["name"], bookmark["url"]])
+
+ self.bookmarks = bookmarks
+
+ self.build_menu()
+
+ def build_menu(self):
+ """Builds the bookmark menu"""
+ self.menu = gtk.Menu()
+ menuitem = gtk.MenuItem("Bookmark This Page...")
+ menuitem.connect("activate", self.menuitem_activate)
+ self.menu.append(menuitem)
+ menuitem = gtk.SeparatorMenuItem()
+ self.menu.append(menuitem)
+ # Iterate through the bookmark list and make menuitems for them
+ def add_menuitem(model, path, row, data):
+ menuitem = gtk.MenuItem(model.get_value(row, 0))
+ menuitem.connect("activate", self.menuitem_activate)
+ self.menu.append(menuitem)
+
+ self.liststore.foreach(add_menuitem, None)
+ self.menu.show_all()
+ # Set the new menu on the toolbutton
+ self.toolbutton.set_menu(self.menu)
+
+ def add_bookmark(self, name, url):
+ """Adds a bookmark to the list"""
+ self.liststore.append([name, url])
+ self.build_menu()
+
+ def get_selected(self):
+ """Returns the selected bookmark"""
+ return self.treeview.get_selection().get_selected()[1]
+
+ def on_button_add_clicked(self, widget):
+ # Show the add bookmark dialog
+ self.add_bookmark_dialog.show()
+ self.widgets.get_widget("entry_add_name").grab_focus()
+
+ def on_button_edit_clicked(self, widget):
+ # Show the edit bookmark dialog
+ selected = self.get_selected()
+ if selected != None:
+ self.edit_bookmark_dialog.show()
+ self.widgets.get_widget("entry_edit_name").grab_focus()
+ self.widgets.get_widget("entry_edit_name").set_text(self.liststore.\
+ get_value(selected, 0))
+ self.widgets.get_widget("entry_edit_url").set_text(self.liststore.\
+ get_value(selected, 1))
+
+ def on_button_add_ok_clicked(self, widget):
+ # Get values from the entry widgets
+ name = self.widgets.get_widget("entry_add_name").get_text()
+ url = self.widgets.get_widget("entry_add_url").get_text()
+
+ self.add_bookmark(name, url)
+ # Clear the entry widget and hide the dialog
+ self.widgets.get_widget("entry_add_name").set_text("")
+ self.widgets.get_widget("entry_add_url").set_text("")
+ self.add_bookmark_dialog.hide()
+
+ def on_button_add_cancel_clicked(self, widget, arg=None):
+ # Clear the entry widget and hide the dialog
+ self.widgets.get_widget("entry_add_name").set_text("")
+ self.widgets.get_widget("entry_add_url").set_text("")
+ self.add_bookmark_dialog.hide()
+ if arg != None:
+ return True
+
+ def on_button_edit_ok_clicked(self, widget):
+ # Get values from the entry widgets
+ name = self.widgets.get_widget("entry_edit_name").get_text()
+ url = self.widgets.get_widget("entry_edit_url").get_text()
+ selected = self.get_selected()
+ if selected != None:
+ self.liststore.remove(selected)
+ self.add_bookmark(name, url)
+ self.build_menu()
+
+ # Clear the entry widget and hide the dialog
+ self.widgets.get_widget("entry_edit_name").set_text("")
+ self.widgets.get_widget("entry_edit_url").set_text("")
+ self.edit_bookmark_dialog.hide()
+
+ def on_button_edit_cancel_clicked(self, widget, arg=None):
+ # Clear the entry widget and hide the dialog
+ self.widgets.get_widget("entry_edit_name").set_text("")
+ self.widgets.get_widget("entry_edit_url").set_text("")
+ self.edit_bookmark_dialog.hide()
+ if arg != None:
+ return True
+
+ def on_button_remove_clicked(self, widget):
+ selected = self.get_selected()
+ if selected != None:
+ self.liststore.remove(selected)
+
+ self.build_menu()
+
+ def on_button_cancel_clicked(self, widget, arg=None):
+ self.load_bookmarks()
+ self.hide()
+ if arg != None:
+ return True
+
+ def on_button_ok_clicked(self, widget):
+ def each(model, path, iter, data):
+ bookmark = {}
+ bookmark["name"] = model.get_value(iter, 0)
+ bookmark["url"] = model.get_value(iter, 1)
+ self.bookmarks.append(bookmark)
+ self.bookmarks = []
+ self.liststore.foreach(each, None)
+ self.hide()
+
+ def menuitem_activate(self, widget):
+ text = widget.get_child().get_text()
+ def load_url(model, path, row, data):
+ if model.get_value(row, 0) == text:
+ # Grab the URL and load it
+ url = model.get_value(row, 1)
+ self.parent.txt_url.set_text(url)
+ self.parent.load_url()
+ if text == "Bookmark This Page...":
+ # Show the add bookmark dialog
+ self.add_bookmark_dialog.show()
+ self.widgets.get_widget("entry_add_name").grab_focus()
+ self.widgets.get_widget("entry_add_url").set_text(
+ self.parent.txt_url.get_text())
+ self.on_button_ok_clicked(widget)
+ return
+
+ self.liststore.foreach(load_url, None)
diff --git a/src/browser.py b/src/browser.py
new file mode 100755
index 000000000..a4a7bd503
--- /dev/null
+++ b/src/browser.py
@@ -0,0 +1,522 @@
+# -*- coding: utf-8 -*-
+#
+# browser.py
+#
+# Copyright (C) Marcos Pinto 2007
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, write to:
+# The Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+import os
+import common
+
+if common.windows_check():
+ import win32con
+ from ctypes import *
+ from ctypes.wintypes import *
+ from comtypes import IUnknown, GUID, COMMETHOD, COMObject
+ from comtypes.automation import IDispatch, VARIANT
+ from comtypes.hresult import *
+ from comtypes.client import CreateObject, GetModule
+ CreateObject("InternetExplorer.Application").Quit()
+ from comtypes.gen.SHDocVw import IWebBrowser2
+ kernel32 = windll.kernel32
+ user32 = windll.user32
+ atl = windll.atl
+else:
+ import gtkmozembed
+
+import pygtk
+pygtk.require("2.0")
+import gtk, gtk.keysyms, gtk.glade
+import gobject
+import common
+
+class Browser:
+ """class builds and displays our internal browser"""
+ def __init__(self, launch_site=None):
+ if common.windows_check():
+ self.widgets = gtk.glade.XML(common.get_glade_file("browserwin.glade"), domain='deluge')
+ else:
+ self.widgets = gtk.glade.XML(common.get_glade_file("browser.glade"), domain='deluge')
+ self.txt_url = self.widgets.get_widget("entry1")
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser")
+ self.txt_google = self.widgets.get_widget("entry2")
+ self.window = self.widgets.get_widget("window1")
+ self.window.set_default_size(940, 700)
+ self.window.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ self.window.set_focus(self.txt_url)
+ self.window.connect("key_press_event", self.key_pressed)
+ self.window.connect("delete_event", self.hide)
+ self.window.realize()
+ self.signal_dic = { "load_url" : self.load_url,
+ "search" : self.search,
+ "reload" : self.reload_url,
+ "go_back" : self.go_back,
+ "go_forward" : self.go_forward,
+ "go_back2" : self.go_back2,
+ "go_forward2" : self.go_forward2,
+ "stop_load" : self.stop_load,
+ "list_bookmarks" : self.bookmark_manager }
+ self.widgets.signal_autoconnect(self.signal_dic)
+
+ if not common.windows_check():
+ """do all the work nessecary setup the mozilla environment."""
+ self.window.set_icon(common.get_logo(48))
+ self.create_profile_directory()
+ self.create_prefs_js()
+ self.create_mime()
+ gtkmozembed.set_profile_path(common.CONFIG_DIR, "mozilla")
+ self.gtkmoz = gtkmozembed.MozEmbed()
+ self.gtkmoz2 = gtkmozembed.MozEmbed()
+ if launch_site:
+ self.gtkmoz.load_url(launch_site.strip())
+ else:
+ self.gtkmoz.load_url("http://deluge-torrent.org/google_search.htm")
+ self.gtkmoz2.load_url("http://deluge-torrent.org/google.php")
+ self.widgets.get_widget("frame1").add(self.gtkmoz)
+ self.widgets.get_widget("frame2").add(self.gtkmoz2)
+ self.txt_url.set_text(self.gtkmoz.get_location())
+
+ else:
+ #
+ # here begins all the COM nonsense necessary for IE to use a different
+ # user-agent string. we need to implement IOleClientSite, from which
+ # IE will ask for the user-agent string via IDispatch. to make it ask,
+ # we need to prod it via IOleControl.OnAmbientPropertyChange()
+ #
+ USER_AGENT = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) Deluge BitTorrent/0.5.8"
+ PROXY = "147932-web1.dipconsultants.com:3128"
+ class INTERNET_PROXY_INFO(Structure):
+ _fields_ = [
+ ('dwAccessType', c_long),
+ ('lpszProxy', c_char_p),
+ ('lpszProxyBypass', c_char_p)
+ ]
+ INTERNET_OPEN_TYPE_PROXY = 3
+ INTERNET_OPTION_PROXY = 38
+ InternetSetOption = windll.wininet.InternetSetOptionA
+ ProxyInfo = INTERNET_PROXY_INFO()
+ ProxyInfo.dwAccessType = INTERNET_OPEN_TYPE_PROXY
+ ProxyInfo.lpszProxy = "http=" + PROXY
+ ProxyInfo.lpszProxyBypass = "" # Any address with no dots isn't proxied
+ InternetSetOption(0, INTERNET_OPTION_PROXY, byref(ProxyInfo), sizeof(ProxyInfo))
+
+ class IOleClientSite(IUnknown):
+ _case_insensitive_ = True
+ _iid_ = GUID('{00000118-0000-0000-C000-000000000046}')
+ _idlflags_ = []
+
+ IOleClientSite._methods_ = [
+ COMMETHOD([], HRESULT, 'SaveObject'),
+ COMMETHOD([], HRESULT, 'GetMoniker',
+ ( ['in'], c_ulong, 'dwAssign' ),
+ ( ['in'], c_ulong, 'dwWhichMoniker' ),
+ ( ['out'], POINTER(POINTER(DWORD)), 'ppmk' )), # should be IMoniker but we don't use it
+ COMMETHOD([], HRESULT, 'GetContainer',
+ ( ['out'], POINTER(POINTER(DWORD)), 'ppContainer' )), # should be IOleContainer but we don't use it
+ COMMETHOD([], HRESULT, 'ShowObject'),
+ COMMETHOD([], HRESULT, 'OnShowWindow',
+ ( ['in'], c_int, 'fShow' )),
+ COMMETHOD([], HRESULT, 'RequestNewObjectLayout'),
+ ]
+
+ class IOleObject(IUnknown):
+ _case_insensitive_ = True
+ _iid_ = GUID('{00000112-0000-0000-C000-000000000046}')
+ _idlflags_ = []
+
+ IOleObject._methods_ = [
+ COMMETHOD([], HRESULT, 'SetClientSite',
+ ( ['in'], POINTER(IOleClientSite), 'pClientSite' )),
+ # ...
+ ]
+
+ class IOleControl(IUnknown):
+ _case_insensitive_ = True
+ _iid_ = GUID('{B196B288-BAB4-101A-B69C-00AA00341D07}')
+
+ IOleControl._methods_ = [
+ COMMETHOD([], HRESULT, 'GetControlInfo',
+ (['out'], POINTER(DWORD), 'pCI')), # should be CONTROLINFO but we don't use it
+ COMMETHOD([], HRESULT, 'OnMnemonic',
+ (['in'], POINTER(DWORD), 'pMsg')), # should be MSG but we don't use it
+ COMMETHOD([], HRESULT, 'OnAmbientPropertyChange',
+ (['in'], DWORD, 'dispID')), # should be DISPID but we don't use it
+ # ...
+ ]
+
+ DISPID_AMBIENT_USERAGENT = -5513
+
+ class IESite(COMObject):
+ _com_interfaces_ = [IDispatch, IOleClientSite]
+ def IDispatch_Invoke(self, this, memid, riid, lcid, wFlags, pDispParams,
+ pVarResult, pExcepInfo, puArgErr):
+ if memid == DISPID_AMBIENT_USERAGENT and pVarResult:
+ pVarResult[0].value = USER_AGENT
+ return S_OK
+
+ _ieSite = IESite()
+
+ #
+ # here ends the COM nonsense for user-agent.
+ #
+
+ self.container = self.widgets.get_widget('drawingarea1')
+ self.container2 = self.widgets.get_widget('drawingarea2')
+ self.container.realize()
+ self.container2.realize()
+ self.container.show()
+ self.container2.show()
+ self.container.set_property("can-focus", True)
+ self.widgets.get_widget('vpaned1').set_position(600)
+ self.container.connect("focus", self.on_container_focus)
+ self.container.connect("size-allocate", self.on_container_size)
+ self.container2.connect("size-allocate", self.on_container_size)
+
+ def makeBrowser(container, num):
+ """Create and return an instance of IE via AtlAxWin.
+ 'container' should be a gtk.DrawingArea which acts as IE's parent."""
+ # Create the IE control instance.
+ atl.AtlAxWinInit()
+ hInstance = kernel32.GetModuleHandleA(None)
+ parentHwnd = container.window.handle
+ self.atlAxWinHwnd = \
+ user32.CreateWindowExA(0, "AtlAxWin",
+ "{EAB22AC1-30C1-11CF-A7EB-0000C05BAE0B}", # IWebBrowser2
+ win32con.WS_VISIBLE | win32con.WS_CHILD |
+ win32con.WS_HSCROLL | win32con.WS_VSCROLL,
+ 0, 0, 100, 100, parentHwnd, None, hInstance, 0)
+
+ # Get the IWebBrowser2 interface for the IE control.
+ pBrowserUnk = POINTER(IUnknown)()
+ atl.AtlAxGetControl(self.atlAxWinHwnd, byref(pBrowserUnk))
+ # Wire up our client site, which provides the User-Agent as an ambient property.
+ oleObject = pBrowserUnk.QueryInterface(IOleObject)
+ oleObject.SetClientSite(_ieSite);
+ oleControl = pBrowserUnk.QueryInterface(IOleControl)
+ oleControl.OnAmbientPropertyChange(DISPID_AMBIENT_USERAGENT);
+
+ if num == 1:
+ self.pBrowser = pBrowserUnk.QueryInterface(IWebBrowser2)
+ self.pBrowser.Navigate("about:blank")
+ if launch_site:
+ self.pBrowser.Navigate(launch_site.strip())
+ else:
+ self.pBrowser.Navigate("http://deluge-torrent.org/google_search.htm")
+
+ elif num == 2:
+ self.pBrowser2 = pBrowserUnk.QueryInterface(IWebBrowser2)
+ self.pBrowser2.Navigate("about:blank")
+ self.pBrowser2.Navigate("http://deluge-torrent.org/google.php")
+ return gtk.gdk.window_foreign_new(long(self.atlAxWinHwnd))
+ # Create the IE controls.
+ self.ieParents = {
+ self.container: makeBrowser(self.container, 1),
+ self.container2: makeBrowser(self.container2, 2)
+ }
+
+
+ # Create the BookmarkManager
+ import bookmark
+ self.bookmarks = bookmark.BookmarkManager(self,
+ self.widgets.get_widget("toolbutton_bookmarks"))
+ self.window.show_all()
+ gobject.timeout_add(int(1000), self.update)
+
+ def on_widget_click(self, widget, data):
+ self.main.window.focus()
+
+ def on_container_size(self, widget, sizeAlloc):
+ self.ieParents[widget].move_resize(0, 0, sizeAlloc.width, sizeAlloc.height)
+
+ def on_container_focus(self, widget, data):
+ rect = RECT()
+ user32.GetWindowRect(self.atlAxWinHwnd, byref(rect))
+ ieHwnd = user32.WindowFromPoint(POINT(rect.left, rect.top))
+ user32.SetFocus(ieHwnd)
+
+ def create_profile_directory(widget=None):
+ """create the mozilla profile directory, if needed."""
+ if not common.windows_check():
+ path = os.path.join(common.CONFIG_DIR, "mozilla")
+ if not os.path.exists(path):
+ os.makedirs(path)
+ else:
+ pass
+
+ def create_mime(widget=None):
+ """create the mimeTypes.rdf file"""
+ if not common.windows_check():
+ mime_content = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+ mime_path = os.path.join(common.CONFIG_DIR, "mozilla", "mimeTypes.rdf")
+ f = open(mime_path, "wt")
+ f.write(mime_content)
+ f.close()
+ else:
+ pass
+
+ def create_prefs_js(widget=None):
+ """create the file prefs.js in the mozilla profile directory. this
+ does things like turn off the warning when navigating to https pages.
+ """
+ if not common.windows_check():
+ prefs_content = """\
+user_pref("security.warn_entering_secure", false);
+user_pref("security.warn_entering_weak", false);
+user_pref("security.warn_viewing_mixed", false);
+user_pref("security.warn_leaving_secure", false);
+user_pref("security.warn_submit_insecure", false);
+user_pref("security.warn_entering_secure.show_once", false);
+user_pref("security.warn_entering_weak.show_once", false);
+user_pref("security.warn_viewing_mixed.show_once", false);
+user_pref("security.warn_leaving_secure.show_once", false);
+user_pref("security.warn_submit_insecure.show_once", false);
+user_pref("security.enable_java", false);
+user_pref("browser.xul.error_pages.enabled", false);
+user_pref("general.useragent.vendor", %s);
+user_pref("general.useragent.vendorSub", %s);
+user_pref("network.proxy.http", "%s");
+user_pref("network.proxy.http_port", %s);
+user_pref("network.proxy.ssl", "%s");
+user_pref("network.proxy.ssl_port", %s);
+user_pref("network.proxy.type", 1);
+""" % (repr("Deluge BitTorrent"),
+ repr("0.5.8"),
+ '147932-web1.dipconsultants.com',
+ 3128,
+ '147932-web1.dipconsultants.com',
+ 3128)
+ prefs_path = os.path.join(common.CONFIG_DIR, "mozilla", "prefs.js")
+ f = open(prefs_path, "wt")
+ f.write(prefs_content)
+ f.close()
+ else:
+ pass
+
+ def update(self):
+ """updates the GUI"""
+ if self.window.get_focus() != self.txt_url:
+ if not common.windows_check():
+ self.txt_url.set_text(self.gtkmoz.get_location())
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser " \
+ + self.gtkmoz.get_title())
+ except:
+ pass
+ if self.gtkmoz.can_go_back():
+ self.widgets.get_widget("toolbutton_back").set_sensitive(True)
+ else:
+ self.widgets.get_widget("toolbutton_back").set_sensitive(False)
+ if self.gtkmoz.can_go_forward():
+ self.widgets.get_widget("toolbutton_forward").set_sensitive(True)
+ else:
+ self.widgets.get_widget("toolbutton_forward").set_sensitive(False)
+ if self.gtkmoz2.can_go_back():
+ self.widgets.get_widget("toolbutton_back2").set_sensitive(True)
+ else:
+ self.widgets.get_widget("toolbutton_back2").set_sensitive(False)
+ if self.gtkmoz2.can_go_forward():
+ self.widgets.get_widget("toolbutton_forward2").set_sensitive(True)
+ else:
+ self.widgets.get_widget("toolbutton_forward2").set_sensitive(False)
+ else:
+ self.txt_url.set_text(self.pBrowser.LocationURL)
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser " \
+ + self.pBrowser.LocationName)
+ except:
+ pass
+ return True
+
+ def key_pressed(self, widget, key):
+ """captures ctrl+ keys and sets focus accordingly, or quits"""
+ if key.keyval in (gtk.keysyms.L, gtk.keysyms.l) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ self.window.set_focus(self.txt_url)
+ elif key.keyval in (gtk.keysyms.K, gtk.keysyms.k) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ self.window.set_focus(self.txt_google)
+ elif key.keyval in (gtk.keysyms.R, gtk.keysyms.r) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ if not common.windows_check():
+ self.gtkmoz.reload(0)
+ else:
+ self.pBrowser.refresh()
+ elif key.keyval == gtk.keysyms.Return and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ if not common.windows_check():
+ self.gtkmoz.load_url("http://www.%s.com" % (self.txt_url.get_text()))
+ self.txt_url.set_text(self.gtkmoz.get_location())
+ else:
+ v = byref(VARIANT())
+ self.pBrowser.Navigate("http://www.%s.com" % (self.txt_url.get_text()), v, v, v, v)
+ elif key.keyval == gtk.keysyms.Return and (key.state & \
+ gtk.gdk.SHIFT_MASK) != 0:
+ if not common.windows_check():
+ self.gtkmoz.load_url("http://www.%s.net" % (self.txt_url.get_text()))
+ self.txt_url.set_text(self.gtkmoz.get_location())
+ else:
+ v = byref(VARIANT())
+ self.pBrowser.Navigate("http://www.%s.net" % (self.txt_url.get_text()), v, v, v, v)
+ elif key.keyval in (gtk.keysyms.Q, gtk.keysyms.q, gtk.keysyms.W, \
+ gtk.keysyms.w) and (key.state & gtk.gdk.CONTROL_MASK) != 0:
+ self.hide()
+
+
+ def search(self, widget=None):
+ """open a new search page"""
+ if not common.windows_check():
+ self.gtkmoz.load_url("http://www.google.com/cse?cx=0103316019315568500"
+ + "92%3Apfadwhze_jy&q=" + self.txt_google.get_text() + "&sa=Search&cof=FORID%3A1")
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser " \
+ + self.gtkmoz.get_title())
+ except:
+ pass
+ else:
+ v = byref(VARIANT())
+ self.pBrowser.Navigate("http://www.google.com/cse?cx=0103316019315568500"
+ + "92%3Apfadwhze_jy&q=" + self.txt_google.get_text() + "&sa=Search&cof=FORID%3A1", v, v, v, v)
+
+ def load_url(self, widget=None):
+ """open a new url"""
+ if not common.windows_check():
+ self.gtkmoz.load_url(self.txt_url.get_text())
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser " \
+ + self.gtkmoz.get_title())
+ except:
+ pass
+ else:
+ v = byref(VARIANT())
+ self.pBrowser.Navigate(self.txt_url.get_text(), v, v, v, v)
+
+ def reload_url(self, widget=None):
+ """refresh the current url"""
+ if not common.windows_check():
+ self.gtkmoz.reload(0)
+ else:
+ self.pBrowser.refresh()
+
+ def go_back(self, widget=None):
+ """go a page back"""
+ if not common.windows_check():
+ if self.gtkmoz.can_go_back():
+ self.gtkmoz.go_back()
+ self.txt_url.set_text(self.gtkmoz.get_location())
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser " \
+ + self.gtkmoz.get_title())
+ except:
+ pass
+ else:
+ self.pBrowser.GoBack()
+ self.txt_url.set_text(self.pBrowser.LocationURL)
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser" \
+ + self.pBrowser.LocationName)
+ except:
+ pass
+
+ def go_forward(self, widget=None):
+ """go a page ahead"""
+ if not common.windows_check():
+ if self.gtkmoz.can_go_forward():
+ self.gtkmoz.go_forward()
+ self.txt_url.set_text(self.gtkmoz.get_location())
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser " \
+ + self.gtkmoz.get_title())
+ except:
+ pass
+ else:
+ self.pBrowser.GoForward()
+ self.txt_url.set_text(self.pBrowser.LocationURL)
+ try:
+ self.widgets.get_widget("window1").set_title("Deluge Web Browser" \
+ + self.pBrowser.LocationName)
+ except:
+ pass
+
+ def go_back2(self, widget=None):
+ """go a page back"""
+ if not common.windows_check():
+ if self.gtkmoz2.can_go_back():
+ self.gtkmoz2.go_back()
+ else:
+ self.pBrowser2.GoBack()
+
+ def go_forward2(self, widget=None):
+ """go a page ahead"""
+ if not common.windows_check():
+ if self.gtkmoz2.can_go_forward():
+ self.gtkmoz2.go_forward()
+ else:
+ self.pBrowser2.GoForward()
+
+ def stop_load(self, widget=None):
+ """stop loading current page"""
+ if not common.windows_check():
+ self.gtkmoz.stop_load()
+ else:
+ self.pBrowser.Stop()
+
+ def bookmark_manager(self, widget=None):
+ """show bookmark manager"""
+ self.bookmarks.show()
+
+ def hide(self, widget=None, arg=None):
+ """close browser"""
+ self.window.hide()
+ return True
+
diff --git a/src/common.py b/src/common.py
index d0ef895ab..831b475e6 100644
--- a/src/common.py
+++ b/src/common.py
@@ -32,7 +32,7 @@ import os
import xdg.BaseDirectory
PROGRAM_NAME = "Deluge"
-PROGRAM_VERSION = "0.5.7.9"
+PROGRAM_VERSION = "0.5.7.95"
CLIENT_CODE = "DE"
CLIENT_VERSION = "".join(PROGRAM_VERSION.split('.'))+"0"*(4 - len(PROGRAM_VERSION.split('.')))
@@ -154,15 +154,21 @@ def get_logo(size):
size, size)
def open_url_in_browser(link):
- import threading
- import webbrowser
- class BrowserThread(threading.Thread):
- def __init__(self, link):
- threading.Thread.__init__(self)
- self.url = link
- def run(self):
- webbrowser.open(self.url)
- BrowserThread(link).start()
+ import pref
+ config = pref.Preferences(os.path.join(os.path.expanduser("~"), 'deluge', "prefs.state"))
+ if config.get("use_internal"):
+ import browser
+ browser.Browser(link)
+ else:
+ import threading
+ import webbrowser
+ class BrowserThread(threading.Thread):
+ def __init__(self, link):
+ threading.Thread.__init__(self)
+ self.url = link
+ def run(self):
+ webbrowser.open(self.url)
+ BrowserThread(link).start()
def is_url(url):
import re
@@ -244,7 +250,7 @@ version").read().strip()
Deluge"
new_release = ""
- if new_release > PROGRAM_VERSION:
+ if new_release > PROGRAM_VERSION:
import gtk
import dialogs
gtk.gdk.threads_enter()
@@ -255,8 +261,8 @@ of Deluge. Would you like to be taken to our download site?"))
open_url_in_browser('http://download.deluge-torrent.org/')
else:
pass
-
ReleaseThread().start()
+ return True
# Encryption States
class EncState:
diff --git a/src/core.py b/src/core.py
index 3bc3449fe..a1b1b5f81 100644
--- a/src/core.py
+++ b/src/core.py
@@ -321,7 +321,7 @@ class Manager:
# Pickle the state so if we experience a crash, the latest state is
# available
print "Pickling state..."
-
+
output = open(os.path.join(self.base_dir, STATE_FILENAME), 'wb')
pickle.dump(self.state, output)
output.close()
@@ -757,13 +757,19 @@ Space:") + " " + nice_free)
deluge_core.scrape_tracker(unique_ID)
def pause(self, unique_ID):
- deluge_core.pause(unique_ID)
+ try:
+ deluge_core.pause(unique_ID)
+ except:
+ print "pause failed\n"
def resume(self, unique_ID):
- deluge_core.resume(unique_ID)
- # We have to re-apply per torrent settings after resume. This has to
- # be done until ticket #118 in libtorrent is fixed.
- self.apply_prefs_per_torrent(unique_ID)
+ try:
+ deluge_core.resume(unique_ID)
+ # We have to re-apply per torrent settings after resume. This has to
+ # be done until ticket #118 in libtorrent is fixed.
+ self.apply_prefs_per_torrent(unique_ID)
+ except:
+ print "pause failed\n"
def pause_all(self):
if self.config.get('max_active_torrents') != 0:
diff --git a/src/deluge_core.cpp b/src/deluge_core.cpp
index f23475a2c..382fa6f50 100644
--- a/src/deluge_core.cpp
+++ b/src/deluge_core.cpp
@@ -837,11 +837,20 @@ static PyObject *torrent_pause(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "i", &unique_ID))
return NULL;
- long index = get_index_from_unique_ID(unique_ID);
- if (PyErr_Occurred())
- return NULL;
+ try {
+ long index = get_index_from_unique_ID(unique_ID);
+ if (PyErr_Occurred())
+ return NULL;
+ torrent_handle& h = M_torrents->at(index).handle;
+ if (h.is_valid()){
+ h.pause();
+ }
- M_torrents->at(index).handle.pause();
+ }
+ catch (invalid_handle&){
+ printf("invalid handle. something bad happened in libtorrent\n");
+ }
+ catch (...){}
Py_INCREF(Py_None); return Py_None;
}
@@ -853,11 +862,20 @@ static PyObject *torrent_resume(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "i", &unique_ID))
return NULL;
- long index = get_index_from_unique_ID(unique_ID);
- if (PyErr_Occurred())
- return NULL;
+ try {
+ long index = get_index_from_unique_ID(unique_ID);
+ if (PyErr_Occurred())
+ return NULL;
+ torrent_handle& h = M_torrents->at(index).handle;
+ if (h.is_valid()){
+ h.resume();
+ }
- M_torrents->at(index).handle.resume();
+ }
+ catch (invalid_handle&){
+ printf("invalid handle. something bad happened in libtorrent\n");
+ }
+ catch (...){}
Py_INCREF(Py_None); return Py_None;
}
@@ -1800,22 +1818,30 @@ static PyObject *torrent_get_trackers(PyObject *self, PyObject *args)
python_long unique_ID;
if (!PyArg_ParseTuple(args, "i", &unique_ID))
return NULL;
+ try {
+ long index = get_index_from_unique_ID(unique_ID);
+ if (PyErr_Occurred())
+ return NULL;
- long index = get_index_from_unique_ID(unique_ID);
- if (PyErr_Occurred())
- return NULL;
-
- torrent_handle& h = M_torrents->at(index).handle;
- std::string trackerslist;
- if (h.is_valid() && h.has_metadata())
- {
- for (std::vector::const_iterator i = h.trackers().begin();
- i != h.trackers().end(); ++i)
+ torrent_handle& h = M_torrents->at(index).handle;
+ std::string trackerslist;
+ if (h.is_valid() && h.has_metadata())
{
- trackerslist = trackerslist + i->url +"\n";
+ for (std::vector::const_iterator i = h.trackers().begin();
+ i != h.trackers().end(); ++i)
+ {
+ trackerslist = trackerslist + i->url +"\n";
+ }
+ return Py_BuildValue("s",trackerslist.c_str());
}
+ else{
+ Py_INCREF(Py_None); return Py_None;
}
- return Py_BuildValue("s",trackerslist.c_str());
+ }
+ catch (...){
+ printf("error getting trackers, probably invalid handle\n");
+ Py_INCREF(Py_None); return Py_None;
+ }
}
static PyObject *torrent_replace_trackers(PyObject *self, PyObject *args)
@@ -1849,7 +1875,7 @@ static PyObject *torrent_replace_trackers(PyObject *self, PyObject *args)
}
catch (invalid_handle&)
{
- printf("invalid handle. something bad happened in libtorrent\n");
+ printf("invalid handle. something bad happened in libtorrent\n");
}
catch (...) {}
Py_INCREF(Py_None); return Py_None;
diff --git a/src/dialogs.py b/src/dialogs.py
index 42054c980..4d0990a8b 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -46,7 +46,7 @@ class PreferencesDlg:
self.glade.get_widget("notebook").set_current_page(6)
self.dialog.set_position(gtk.WIN_POS_CENTER)
if not common.windows_check():
- self.dialog.set_icon(common.get_logo(32))
+ self.dialog.set_icon(common.get_logo(18))
self.glade.signal_autoconnect({
'toggle_ui': self.toggle_ui,
'on_btn_testport_clicked': self.TestPort,
@@ -153,9 +153,10 @@ class PreferencesDlg:
self.glade.get_widget("chk_clear_max_ratio_torrents").set_sensitive(self.preferences.get("auto_end_seeding"))
self.glade.get_widget("chk_clear_max_ratio_torrents").set_active(self.preferences.get("clear_max_ratio_torrents"))
self.glade.get_widget("chk_paused").set_active(self.preferences.get("start_paused"))
+ self.glade.get_widget("chk_show_search").set_active(self.preferences.get("show_search"))
+ self.glade.get_widget("chk_use_internal").set_active(self.preferences.get("use_internal"))
self.glade.get_widget("ratio_spinner").set_value(self.preferences.get("auto_seed_ratio"))
self.glade.get_widget("chk_dht").set_active(self.preferences.get("enable_dht"))
- self.glade.get_widget("spin_gui").set_value(self.preferences.get("gui_update_interval"))
self.glade.get_widget("chk_use_advanced_bar").set_active(self.preferences.get("use_advanced_bar"))
#smart dialog set sensitivities
@@ -274,7 +275,8 @@ class PreferencesDlg:
self.preferences.set("max_active_torrents", int(self.glade.get_widget("spin_torrents").get_value()))
self.preferences.set("queue_seeds_to_bottom", self.glade.get_widget("chk_seedbottom").get_active())
self.preferences.set("enable_dht", self.glade.get_widget("chk_dht").get_active())
- self.preferences.set("gui_update_interval", self.glade.get_widget("spin_gui").get_value())
+ self.preferences.set("show_search", self.glade.get_widget("chk_show_search").get_active())
+ self.preferences.set("use_internal", self.glade.get_widget("chk_use_internal").get_active())
self.preferences.set("clear_max_ratio_torrents", self.glade.get_widget("chk_clear_max_ratio_torrents").get_active())
self.preferences.set("queue_above_completed", self.glade.get_widget("chk_queue_above_completed").get_active())
self.preferences.set("start_paused", self.glade.get_widget("chk_paused").get_active())
@@ -402,7 +404,7 @@ class MergeDlg:
self.dialog = self.glade.get_widget("merge_dialog")
self.dialog.set_position(gtk.WIN_POS_CENTER)
if not common.windows_check():
- self.dialog.set_icon(common.get_logo(32))
+ self.dialog.set_icon(common.get_logo(18))
def show(self, window):
self.dialog.set_transient_for(window)
@@ -419,7 +421,7 @@ class FilesDlg:
self.dialog = self.glade.get_widget("file_dialog")
self.dialog.set_position(gtk.WIN_POS_CENTER)
if not common.windows_check():
- self.dialog.set_icon(common.get_logo(32))
+ self.dialog.set_icon(common.get_logo(18))
self.files_manager = files.FilesDialogManager(
self.glade.get_widget("file_view"),
@@ -461,7 +463,7 @@ def show_about_dialog(window):
abt.set_website("http://deluge-torrent.org")
abt.set_website_label("http://deluge-torrent.org")
if not common.windows_check():
- abt.set_icon(common.get_logo(32))
+ abt.set_icon(common.get_logo(18))
abt.set_logo(gtk.gdk.pixbuf_new_from_file(
common.get_pixmap("deluge-about.png")))
abt.show_all()
@@ -514,7 +516,7 @@ def show_file_open_dialog(parent, title=None):
chooser.add_filter(f1)
chooser.set_select_multiple(True)
if not common.windows_check():
- chooser.set_icon(common.get_logo(32))
+ chooser.set_icon(common.get_logo(18))
chooser.set_property("skip-taskbar-hint", True)
response = chooser.run()
@@ -530,7 +532,7 @@ def show_directory_chooser_dialog(parent, title):
chooser = gtk.FileChooserDialog(title, parent, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))
if not common.windows_check():
- chooser.set_icon(common.get_logo(32))
+ chooser.set_icon(common.get_logo(18))
chooser.set_property("skip-taskbar-hint", True)
config = pref.Preferences()
chooser.set_current_folder(config.get("choose_directory_dialog_path"))
diff --git a/src/interface.py b/src/interface.py
index d4dad2747..f51bbb8db 100644
--- a/src/interface.py
+++ b/src/interface.py
@@ -64,20 +64,32 @@ class DelugeGTK:
domain='deluge')
self.window = self.wtree.get_widget("main_window")
self.toolbar = self.wtree.get_widget("tb_left")
+ self.browserbutton_image = gtk.Image()
+ self.browserbutton_image.set_from_pixbuf(\
+ gtk.gdk.pixbuf_new_from_file_at_size(\
+ common.get_pixmap('browser.png'), 18, 18))
+ self.browserbutton = gtk.ToolButton(self.browserbutton_image, _("Browser"))
+ self.browserbutton_tip = gtk.Tooltips()
+ self.browserbutton.set_tooltip(self.browserbutton_tip, _("Launch Browser"))
+ self.browserbutton.connect("clicked", self.launch_browser_clicked)
+ self.wtree.get_widget("tb_left").add(self.browserbutton)
+ self.browserbutton.show_all()
self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0,
80)], gtk.gdk.ACTION_COPY)
self.window.connect("delete_event", self.close)
+ self.window.connect("key_press_event", self.key_pressed)
self.window.connect("drag_data_received", self.on_drag_data)
self.window.connect("window-state-event", self.window_state_event)
self.window.connect("configure-event", self.window_configure_event)
self.window.set_title(common.PROGRAM_NAME)
if not common.windows_check():
- self.window.set_icon(common.get_logo(32))
+ self.window.set_icon(common.get_logo(48))
# self.notebook is used by plugins
self.notebook = self.wtree.get_widget("torrent_info")
self.notebook.connect("switch-page", self.notebook_switch_page)
self.notebook.connect("page-reordered", self.notebook_page_reordered)
self.notebook.connect("page-added", self.notebook_page_added)
+
# Tabs
self.tab_details = tab_details.DetailsTabManager(self.wtree,
@@ -112,9 +124,6 @@ class DelugeGTK:
# Boolean set to true if window is not minimized and is "visible"
self.update_interface = True
- def new_release_check():
- common.new_release_check()
-
def send_info():
import time
@@ -130,9 +139,6 @@ class DelugeGTK:
else:
_run_script()
- if self.config.get("new_releases"):
- new_release_check()
-
if self.config.get("send_info"):
send_info()
@@ -159,6 +165,10 @@ class DelugeGTK:
return result
SetConsoleCtrlHandler(win_handler)
+ if self.config.get("show_search"):
+ import search
+ search.Search(self)
+
self.dht_timer = 0
self.dht_skip = False
self.memory_timer = 0
@@ -174,6 +184,15 @@ class DelugeGTK:
except AttributeError:
pass
+ def new_release_check():
+ try:
+ common.new_release_check()
+ except:
+ pass
+
+ if self.config.get("new_releases"):
+ new_release_check()
+
def connect_signals(self):
self.wtree.signal_autoconnect({
## File Menu
@@ -198,6 +217,9 @@ class DelugeGTK:
"availability_toggle": self.availability_toggle,
"share_toggle": self.share_toggle,
## Help Menu
+ "launch_homepage": self.launch_homepage,
+ "launch_community": self.launch_community,
+ "launch_faq": self.launch_faq,
"show_about_dialog": self.show_about_dialog,
"launchpad": self.launchpad,
"run_wizard": self.run_wizard,
@@ -232,9 +254,30 @@ class DelugeGTK:
def pause_all_clicked(self, arg=None):
self.manager.pause_all()
+ def launch_browser_clicked(self, arg=None):
+ import browser
+ browser.Browser()
+
def resume_all_clicked(self, arg=None):
self.manager.resume_all()
+ def key_pressed(self, widget, key):
+ """captures keys"""
+ if key.keyval == gtk.keysyms.Delete:
+ self.remove_torrent_clicked()
+ elif key.keyval in (gtk.keysyms.A, gtk.keysyms.a) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ self.add_torrent_clicked()
+ elif key.keyval in (gtk.keysyms.L, gtk.keysyms.l) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ self.add_torrent_url_clicked()
+ elif key.keyval in (gtk.keysyms.P, gtk.keysyms.p) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ self.tor_pause(widget)
+ elif key.keyval in (gtk.keysyms.R, gtk.keysyms.r) and (key.state & \
+ gtk.gdk.CONTROL_MASK) != 0:
+ self.tor_start(widget)
+
def build_tray_icon(self):
self.tray_icon = gtk.status_icon_new_from_icon_name('deluge')
@@ -1013,6 +1056,7 @@ window, please enter your password"))
self.load_plugins()
self.load_tabs_order()
+
try:
gobject.threads_init()
gtk.gdk.threads_enter()
@@ -1381,6 +1425,15 @@ nice_need + "\n" + _("Available Space:") + " " + nice_free)
def launchpad(self, obj=None):
common.open_url_in_browser('https://translations.launchpad.net/deluge/\
trunk/+pots/deluge')
+
+ def launch_faq(self, obj=None):
+ common.open_url_in_browser('http://deluge-torrent.org/faq.php')
+
+ def launch_community(self, obj=None):
+ common.open_url_in_browser('http://forum.deluge-torrent.org/')
+
+ def launch_homepage(self, obj=None):
+ common.open_url_in_browser('http://deluge-torrent.org/')
def add_torrent_clicked(self, obj=None):
torrent = dialogs.show_file_open_dialog(self.window)
@@ -1392,7 +1445,7 @@ trunk/+pots/deluge')
dlg = gtk.Dialog(_("Add torrent from URL"), self.window, 0,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OK, gtk.RESPONSE_OK))
dlg.set_default_response(gtk.RESPONSE_OK)
- dlg.set_icon(common.get_logo(32))
+ dlg.set_icon(common.get_logo(18))
label = gtk.Label(_("Enter the URL of the .torrent to download"))
entry = gtk.Entry()
entry.connect("activate", lambda w : dlg.response(gtk.RESPONSE_OK))
@@ -1424,7 +1477,7 @@ trunk/+pots/deluge')
glade = gtk.glade.XML(common.get_glade_file("dgtkpopups.glade"),
domain='deluge')
asker = glade.get_widget("remove_torrent_dlg")
- asker.set_icon(common.get_logo(32))
+ asker.set_icon(common.get_logo(18))
warning = glade.get_widget("warning")
warning.set_text(" ")
@@ -1553,8 +1606,7 @@ want to remove all seeding torrents?")):
"show_peers"))
self.wtree.get_widget("chk_download").set_active(self.config.get(
"show_dl"))
- self.wtree.get_widget("chk_upload").set_active(self.config.get(
- "show_ul"))
+ self.wtree.get_widget("chk_upload").set_active(self.config.get("show_ul"))
self.wtree.get_widget("chk_eta").set_active(self.config.get("show_eta"))
self.wtree.get_widget("chk_availability").set_active(self.config.get(
"show_availability"))
@@ -1568,7 +1620,8 @@ want to remove all seeding torrents?")):
get_active())
self.config.set("show_toolbar", self.wtree.get_widget("chk_toolbar").\
get_active())
- self.config.set("show_size", self.size_column.get_visible())
+ self.config.set("show_size", self.wtree.get_widget("chk_size").\
+ get_active())
self.config.set("show_status", self.status_column.get_visible())
self.config.set("show_seeders", self.seed_column.get_visible())
self.config.set("show_peers", self.peer_column.get_visible())
diff --git a/src/plugins.py b/src/plugins.py
index e4808df07..25db8f269 100644
--- a/src/plugins.py
+++ b/src/plugins.py
@@ -60,7 +60,7 @@ class PluginManager:
# to be by design.
mod = __import__(modname, globals(), locals(), [''])
if 'deluge_init' in dir(mod):
- if (modname != "TorrentPieces") and (modname != "SimpleRSS"):
+ if (modname != "TorrentPieces") and (modname != "SimpleRSS") and (modname != "TorrentSearch"):
print "Initialising plugin", modname
try:
mod.deluge_init(path)
diff --git a/src/pref.py b/src/pref.py
index 882d5351e..0086cf348 100644
--- a/src/pref.py
+++ b/src/pref.py
@@ -47,6 +47,8 @@ if common.windows_check():
"autoload" : False,
"open_folder_location": "",
"send_info" : True,
+ "use_internal" : True,
+ "show_search" : True,
"auto_end_seeding" : False,
"auto_seed_ratio" : 0,
"close_to_tray" : False,
@@ -164,6 +166,8 @@ else:
"auto_end_seeding" : False,
"auto_seed_ratio" : 0,
"close_to_tray" : False,
+ "use_internal" : True,
+ "show_search" : True,
"enable_files_dialog" : False,
"enable_multi_only" : True,
"queue_above_completed" : False,
diff --git a/src/search.py b/src/search.py
new file mode 100644
index 000000000..22e1b631f
--- /dev/null
+++ b/src/search.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+#
+# plugin.py
+#
+# Copyright (C) Marcos Pinto 2007
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, write to:
+# The Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+
+class Search:
+ def __init__(self, deluge_interface):
+ import gtk, gtk.glade, os
+ import deluge.common, deluge.dgtk, deluge.pref
+ self.interface = deluge_interface
+ self.config_file = os.path.join(deluge.common.CONFIG_DIR, "newsearch.conf")
+ self.config = deluge.pref.Preferences(self.config_file, False,
+ defaults={'Pirate Bay' : 'http://thepiratebay.org/search/${query}/0/3/0',
+ 'Google' : "http://www.google.com/cse?cx=010331601\
+931556850092%3Apfadwhze_jy&q=${query}&sa=Search&cof=FORID%3A1",
+ 'Mininova' : 'http://www.mininova.org/search/?search=${query}',
+ 'isoHunt' : "http://isohunt.com/torrents/?ihq=${query}"})
+ try:
+ self.config.load()
+ except IOError:
+ pass
+
+ glade = gtk.glade.XML(deluge.common.get_glade_file("searchdlg.glade"))
+ self.dlg = glade.get_widget("search_dialog")
+ if not deluge.common.windows_check():
+ self.dlg.set_icon(deluge.common.get_logo(18))
+ self.view = glade.get_widget("search_view")
+ model = gtk.ListStore(str, str)
+ self.view.set_model(model)
+ deluge.dgtk.add_text_column(self.view, _("Name"), 0, width=80)
+ deluge.dgtk.add_text_column(self.view, _("Search String"), 1)
+ self.field_name = glade.get_widget("field_name")
+ self.field_search = glade.get_widget("field_search")
+ self.button_add = glade.get_widget("button_addsearch")
+ self.button_del = glade.get_widget("button_delsearch")
+ dic = { "add_clicked" : self.add_clicked,
+ "del_clicked" : self.del_clicked,
+ "row_clicked" : self.row_clicked,
+ "text_changed" : self.text_changed }
+ glade.signal_autoconnect(dic)
+ self.view.get_selection().set_select_function(self.row_clicked)
+ ### Note: All other plugins should use self.interface.toolbar
+ ### when adding items to the toolbar
+ self.se = ''
+ self.toolbar = self.interface.wtree.get_widget("tb_left")
+ self.search_entry = gtk.Entry()
+ self.search_entry.connect("activate", self.torrent_search)
+ self.search_item = gtk.ToolItem()
+ self.search_item.add(self.search_entry)
+ self.search_icon = gtk.Image()
+ self.search_icon.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_SMALL_TOOLBAR)
+ self.menu_button = gtk.MenuToolButton(self.search_icon, _("Choose an Engine"))
+ self.menu_button.set_is_important(True)
+ self.menu_button.connect("clicked", self.torrent_search)
+ self.menu = gtk.Menu()
+ self.manage_item = gtk.ImageMenuItem(_("Manage Engines"))
+ self.image = gtk.Image()
+ self.image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_LARGE_TOOLBAR)
+ self.manage_item.set_image(self.image)
+ self.manage_item.connect("activate", self.configure)
+ self.menu.add(self.manage_item)
+ self.menu_button.set_menu(self.menu)
+ self.toolbar.insert(self.menu_button, -1)
+ self.toolbar.insert(self.search_item, -1)
+ self.populate_search_menu()
+ self.toolbar.show_all()
+ self.search_item.show_all()
+ self.menu_button.show_all()
+ self.menu.show_all()
+
+ def text_changed(self, args):
+ a = (self.field_name.get_text() != "")
+ b = (self.field_search.get_text() != "")
+ if(a and b):
+ self.button_add.set_sensitive(1)
+ else:
+ self.button_add.set_sensitive(0)
+
+ def add_clicked(self, args):
+ self.view.get_model().append([self.field_name.get_text(),
+ self.field_search.get_text()])
+ self.field_name.set_text("")
+ self.field_search.set_text("")
+
+ def del_clicked(self, args):
+ (model, selection) = self.view.get_selection().get_selected()
+ model.remove(selection)
+ self.button_del.set_sensitive(0)
+
+ def row_clicked(self, args):
+ self.button_del.set_sensitive(1)
+ return True
+
+ def configure(self, widget=None):
+ import gtk, gtk.glade
+ from deluge import common
+ self.dlg.show_all()
+ model = self.view.get_model()
+ model.clear()
+ for name in self.config.keys():
+ self.view.get_model().append( (name, self.config.get(name)) )
+ self.button_add.set_sensitive(0)
+ self.button_del.set_sensitive(0)
+ result = self.dlg.run()
+ self.dlg.hide_all()
+ if result == 1:
+ self.config.clear()
+ the_iter = model.get_iter_first()
+ while the_iter is not None:
+ self.config.set(model.get_value(the_iter, 0), model.get_value(the_iter, 1))
+ the_iter = model.iter_next(the_iter)
+ self.config.save(self.config_file)
+ self.populate_search_menu()
+
+
+ def torrent_search(self, widget=None):
+ if self.search_entry.get_text() != "":
+ from deluge import common
+ print "Searching with engine", self.se
+ url = self.config.get(self.se)
+ entry = self.search_entry.get_text()
+ entry = entry.replace(' ', '+')
+ url = url.replace('${query}', entry)
+ print 'URL =', url
+ print 'Entry =', entry
+ common.open_url_in_browser(url)
+
+ def populate_search_menu(self):
+ import gtk
+ self.menu_button.set_label(_("Choose an Engine"))
+ for child in self.menu.get_children():
+ self.menu.remove(child)
+ group = None
+ i = 0
+ for engine in self.config.keys():
+ rmi = gtk.RadioMenuItem(None, engine)
+ rmi.eng_name = engine
+ rmi.connect("activate", self.select_search, rmi.eng_name)
+ if (group != None):
+ rmi.set_group(group)
+ else:
+ group = rmi
+ rmi.set_active(1)
+ self.menu.insert(rmi, i)
+ i = i + 1
+ rmi.show()
+ self.menu.insert(self.manage_item, i)
+ self.menu.show()
+
+ def select_search(self, menuitem, engine_string):
+ self.menu_button.set_label(_("Search ") + engine_string)
+ self.se = engine_string
diff --git a/src/wizard.py b/src/wizard.py
index ac511132b..ee016bae3 100644
--- a/src/wizard.py
+++ b/src/wizard.py
@@ -55,6 +55,7 @@ class WizardGTK:
#add deluge logo to headers
self.window = self.wtree.get_widget("wizard")
self.manager = deluge.core
+ self.window.set_icon(deluge.common.get_logo(18))
pixmap = deluge.common.get_logo(48)
self.window.set_page_header_image(self.wtree.get_widget('label1'), \
pixmap)