Compare commits
128 Commits
archive/1.
...
deluge-1.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c413032c96 | ||
![]() |
0cc4efc455 | ||
![]() |
c5e5a3d4e7 | ||
![]() |
8a5aa3a150 | ||
![]() |
b0f9117a3d | ||
![]() |
040b4938e1 | ||
![]() |
bc34d864ff | ||
![]() |
4a07a33503 | ||
![]() |
933228a82a | ||
![]() |
a777233a7a | ||
![]() |
79fb4b260d | ||
![]() |
3f414f4bdf | ||
![]() |
04bebad82f | ||
![]() |
0808bdaa0f | ||
![]() |
fc5d436021 | ||
![]() |
eca8ced2c8 | ||
![]() |
6847db1304 | ||
![]() |
e8d75cffe3 | ||
![]() |
2de48097c0 | ||
![]() |
7bc8c9fa36 | ||
![]() |
d268ed4955 | ||
![]() |
7376a1e125 | ||
![]() |
df92d3c468 | ||
![]() |
190854dc2f | ||
![]() |
6e778aadf5 | ||
![]() |
8249ad86ea | ||
![]() |
631768f01b | ||
![]() |
b7f9fec075 | ||
![]() |
5672db086a | ||
![]() |
adbe77647a | ||
![]() |
d44a479177 | ||
![]() |
09f9c042e4 | ||
![]() |
cfa479ba3e | ||
![]() |
4fe3780528 | ||
![]() |
f96fb69fda | ||
![]() |
675aed2094 | ||
![]() |
e31eab5303 | ||
![]() |
1bf7422904 | ||
![]() |
740506343f | ||
![]() |
91dfbffd69 | ||
![]() |
bc0df7b6a7 | ||
![]() |
8bd576f636 | ||
![]() |
7a645486ab | ||
![]() |
14006f83b5 | ||
![]() |
f83a180de8 | ||
![]() |
535ad73c04 | ||
![]() |
4390e14485 | ||
![]() |
eb568a8cba | ||
![]() |
2b80b801b1 | ||
![]() |
107079fbab | ||
![]() |
57c1950d40 | ||
![]() |
40e700cf86 | ||
![]() |
f1f50dc447 | ||
![]() |
2f3edc0352 | ||
![]() |
959f6e550f | ||
![]() |
748a82a23b | ||
![]() |
009b34bccb | ||
![]() |
6cd794fa18 | ||
![]() |
b258a9c340 | ||
![]() |
d8447aea72 | ||
![]() |
d5c12a47c2 | ||
![]() |
0d7cf1af81 | ||
![]() |
64d94eb95f | ||
![]() |
28eda6caa0 | ||
![]() |
150c803d19 | ||
![]() |
d88fe0e894 | ||
![]() |
225afdec1f | ||
![]() |
4175289690 | ||
![]() |
d669e6e864 | ||
![]() |
1a70007697 | ||
![]() |
0208e59ad6 | ||
![]() |
1bf2fb47b7 | ||
![]() |
8deee64007 | ||
![]() |
42cceabd8e | ||
![]() |
6eb413dd1e | ||
![]() |
81b895bd1d | ||
![]() |
2edf1fc692 | ||
![]() |
8c489e86d2 | ||
![]() |
530fcf255b | ||
![]() |
af1b3a6d3a | ||
![]() |
04d344a133 | ||
![]() |
7226cbb53d | ||
![]() |
f168f7e18e | ||
![]() |
923cfaab5c | ||
![]() |
b3aa588650 | ||
![]() |
08b92148ca | ||
![]() |
914ae20e74 | ||
![]() |
c562024407 | ||
![]() |
6aa405187f | ||
![]() |
ad0b335648 | ||
![]() |
83690d5aaf | ||
![]() |
df30d4b5c9 | ||
![]() |
0296f6c6e9 | ||
![]() |
7bdabc207d | ||
![]() |
5e0d3bedef | ||
![]() |
e23a4c5da7 | ||
![]() |
d25b41f1de | ||
![]() |
07126decab | ||
![]() |
5ee25515c1 | ||
![]() |
f86b5dd0ad | ||
![]() |
d8a187a8f6 | ||
![]() |
1f52a3fc20 | ||
![]() |
c58e2481b9 | ||
![]() |
7b99298203 | ||
![]() |
2ff6b85771 | ||
![]() |
5c4b64d656 | ||
![]() |
36539cdd1d | ||
![]() |
afb12d7403 | ||
![]() |
f31ac4bdc8 | ||
![]() |
69a0dbca4d | ||
![]() |
eced7ab068 | ||
![]() |
d73a1abbe4 | ||
![]() |
ac5b5fdefb | ||
![]() |
1f1a0168e7 | ||
![]() |
86f9184320 | ||
![]() |
9b1ea3d8de | ||
![]() |
e7aad6a345 | ||
![]() |
aa8d18c081 | ||
![]() |
6bc4caa4e6 | ||
![]() |
68df5a389d | ||
![]() |
21c57e935e | ||
![]() |
9cd3e7cf56 | ||
![]() |
489d805cb4 | ||
![]() |
b974f91da8 | ||
![]() |
4b1eecf026 | ||
![]() |
01bda41ba8 | ||
![]() |
0e60a4903e | ||
![]() |
926d71c8a1 |
25
.gitattributes
vendored
@@ -1,25 +0,0 @@
|
||||
/libtorrent/ export-ignore
|
||||
/win32/ export-ignore
|
||||
/osx/ export-ignore
|
||||
docs/build/ export-ignore
|
||||
docs/source/ export-ignore
|
||||
/tests/ export-ignore
|
||||
deluge/scripts/ export-ignore
|
||||
setup.cfg export-ignore
|
||||
check_glade.sh export-ignore
|
||||
createicons.sh export-ignore
|
||||
create_potfiles_in.py export-ignore
|
||||
gettextize.sh export-ignore
|
||||
deluge/i18n/deluge.pot export-ignore
|
||||
deluge/ui/web/css/*-debug.css export-ignore
|
||||
deluge/ui/web/js/*-debug.js export-ignore
|
||||
deluge/ui/web/js/deluge-all/ export-ignore
|
||||
deluge/ui/web/js/ext-extensions/ export-ignore
|
||||
deluge/ui/web/gen_gettext.py export-ignore
|
||||
deluge/ui/web/build export-ignore
|
||||
deluge/ui/web/docs/ export-ignore
|
||||
|
||||
.gitattributes export-ignore
|
||||
.gitmodules export-ignore
|
||||
.gitignore export-ignore
|
||||
*.py diff=python
|
13
.gitignore
vendored
@@ -1,13 +0,0 @@
|
||||
*~
|
||||
build
|
||||
dist
|
||||
*egg-info
|
||||
*.egg
|
||||
*.log
|
||||
*.pyc
|
||||
*.tar.*
|
||||
_trial_temp
|
||||
deluge/i18n/*/
|
||||
*.desktop
|
||||
.build_data*
|
||||
osx/app
|
797
AUTHORS
@@ -1,797 +0,0 @@
|
||||
Authors:
|
||||
* Andrew Resch ('andar') <andrewresch@gmail.com>
|
||||
* Damien Churchill ('damoxc') <damoxc@gmail.com>
|
||||
|
||||
Main Developers:
|
||||
* Andrew Resch
|
||||
* Damien Churchill
|
||||
* John Garland ('johnnyg') <johnnybg+deluge@gmail.com>
|
||||
* Calum Lind ('cas') <calumlind+deluge@gmail.com>
|
||||
|
||||
libtorrent (http://www.libtorrent.org):
|
||||
* Arvid Norberg
|
||||
|
||||
Contributors (and Past Developers):
|
||||
* Zach Tibbitts <zach@collegegeek.org>
|
||||
* Alon Zakai ('Kripken') <kripkensteiner@gmail.com>
|
||||
* Marcos Mobley ('markybob') <markybob@gmail.com>
|
||||
* Alex Dedul
|
||||
* Sadrul Habib Chowdhury
|
||||
* Ido Abramovich <ido.deluge@gmail.com>
|
||||
* Martijn Voncken <mvoncken@gmail.com>
|
||||
* Mark Stahler ('kramed') <markstahler@gmail.com>
|
||||
* Pedro Algarvio ('s0undt3ch') <ufs@ufsoft.org>
|
||||
* Cristian Greco ('cgreco') <cristian@regolo.cc>
|
||||
* Chase Sterling ('gazpachoKing') <chase.sterling@gmail.com>
|
||||
|
||||
Plugin Developers:
|
||||
* Autoadd : Chase Sterling
|
||||
* Blocklist : John Garland
|
||||
* Execute : Damien Churchill
|
||||
* Extractor : Andrew Resch
|
||||
* Label : Martijn Voncken
|
||||
* Notifications : Pedro Algarvio
|
||||
* Scheduler : Andrew Resch
|
||||
* Webui : Damien Churchill
|
||||
|
||||
Images Authors:
|
||||
|
||||
* files: deluge/data/pixmaps/*.svg, *.png
|
||||
deluge/ui/web/icons/active.png, alert.png, all.png, checking.png, dht.png,
|
||||
downloading.png, inactive.png, queued.png, seeding.png, traffic.png
|
||||
exceptions: deluge/data/pixmaps/deluge.svg and derivatives
|
||||
copyright: Andrew Resch
|
||||
license: GPLv3
|
||||
|
||||
* files: deluge/data/pixmaps/deluge.svg and derivatives
|
||||
deluge/ui/web/icons/apple-pre-*.png, deluge*.png
|
||||
deluge/ui/web/images/deluge*.png
|
||||
copyright: Andrew Wedderburn
|
||||
license: GPLv3
|
||||
|
||||
* files: deluge/plugins/blocklist/blocklist/data/*.png
|
||||
deluge/data/pixmaps/tracker_warning16.png, tracker_all16.png, lock48.png
|
||||
copyright: Gnome Icon Theme
|
||||
license: GPLv2
|
||||
url: http://ftp.acc.umu.se/pub/GNOME/sources/gnome-icon-theme
|
||||
|
||||
* files: deluge/data/pixmaps/magnet.png
|
||||
copyright: Woothemes
|
||||
license: Freeware
|
||||
icon pack: WP Woothemes Ultimate
|
||||
url: http://www.woothemes.com/
|
||||
|
||||
* files: deluge/data/pixmaps/flags/*.png
|
||||
copyright: Mark James <mjames@gmail.com>
|
||||
license: Public Domain
|
||||
url: http://famfamfam.com/lab/icons/flags/
|
||||
|
||||
* files: deluge/ui/web/icons/*.png
|
||||
exceptions: apple-pre-*.png, active.png, alert.png, all.png, deluge.png, dht.png,
|
||||
downloading.png, inactive.png, queued.png, seeding.png, traffic.png
|
||||
copyright: Yusuke Kamiyamane <p@yusukekamiyamane.com>
|
||||
license: Creative Commons Attribution 3.0 License
|
||||
url: http://p.yusukekamiyamane.com/
|
||||
|
||||
* files: deluge/ui/web/images/spinner.gif, spinner-split.gif
|
||||
copyright: Steven Chim
|
||||
license: BSD license
|
||||
url: http://members.upc.nl/j.chim/ext/spinner2/ext-spinner.html
|
||||
|
||||
Translation Contributors:
|
||||
* files: deluge/i18n/*.po
|
||||
|
||||
Aaron Wang Shi
|
||||
abbigss
|
||||
ABCdatos
|
||||
Abcx
|
||||
Actam
|
||||
Adam
|
||||
adaminikisi
|
||||
adi_oporanu
|
||||
Adrian Goll
|
||||
afby
|
||||
Ahmades
|
||||
Ahmad Farghal
|
||||
Ahmad Gharbeia أحمد غربية
|
||||
akira
|
||||
Aki Sivula
|
||||
Alan Pepelko
|
||||
Alberto
|
||||
Alberto Ferrer
|
||||
alcatr4z
|
||||
AlckO
|
||||
Aleksej Korgenkov
|
||||
Alessio Treglia
|
||||
Alexander Ilyashov
|
||||
Alexander Matveev
|
||||
Alexander Saltykov
|
||||
Alexander Taubenkorb
|
||||
Alexander Telenga
|
||||
Alexander Yurtsev
|
||||
Alexandre Martani
|
||||
Alexandre Rosenfeld
|
||||
Alexandre Sapata Carbonell
|
||||
Alexey Osipov
|
||||
Alin Claudiu Radut
|
||||
allah
|
||||
AlSim
|
||||
Alvaro Carrillanca P.
|
||||
A.Matveev
|
||||
Andras Hipsag
|
||||
András Kárász
|
||||
Andrea Ratto
|
||||
Andreas Johansson
|
||||
Andreas Str
|
||||
André F. Oliveira
|
||||
AndreiF
|
||||
andrewh
|
||||
Angel Guzman Maeso
|
||||
Aníbal Deboni Neto
|
||||
animarval
|
||||
Antonio Cono
|
||||
antoniojreyes
|
||||
Anton Shestakov
|
||||
Anton Yakutovich
|
||||
antou
|
||||
Arkadiusz Kalinowski
|
||||
Artin
|
||||
artir
|
||||
Astur
|
||||
Athanasios Lefteris
|
||||
Athmane MOKRAOUI (ButterflyOfFire)
|
||||
Augusta Carla Klug
|
||||
Avoledo Marco
|
||||
axaard
|
||||
AxelRafn
|
||||
Axezium
|
||||
Ayont
|
||||
b3rx
|
||||
Bae Taegil
|
||||
Bajusz Tamás
|
||||
Balaam's Miracle
|
||||
Ballestein
|
||||
Bent Ole Fosse
|
||||
berto89
|
||||
bigx
|
||||
Bjorn Inge Berg
|
||||
blackbird
|
||||
Blackeyed
|
||||
blackmx
|
||||
BlueSky
|
||||
Blutheo
|
||||
bmhm
|
||||
bob00work
|
||||
boenki
|
||||
Bogdan Bădic-Spătariu
|
||||
bonpu
|
||||
Boone
|
||||
boss01
|
||||
Branislav Jovanović
|
||||
bronze
|
||||
brownie
|
||||
Brus46
|
||||
bumper
|
||||
butely
|
||||
BXCracer
|
||||
c0nfidencal
|
||||
Can Kaya
|
||||
Carlos Alexandro Becker
|
||||
cassianoleal
|
||||
Cédric.h
|
||||
César Rubén
|
||||
chaoswizard
|
||||
Chen Tao
|
||||
chicha
|
||||
Chien Cheng Wei
|
||||
Christian Kopac
|
||||
Christian Widell
|
||||
Christoffer Brodd-Reijer
|
||||
christooss
|
||||
CityAceE
|
||||
Clopy
|
||||
Clusty
|
||||
cnu
|
||||
Commandant
|
||||
Constantinos Koniaris
|
||||
Coolmax
|
||||
cosmix
|
||||
Costin Chirvasuta
|
||||
CoVaLiDiTy
|
||||
cow_2001
|
||||
Crispin Kirchner
|
||||
crom
|
||||
Cruster
|
||||
Cybolic
|
||||
Dan Bishop
|
||||
Danek
|
||||
Dani
|
||||
Daniel Demarco
|
||||
Daniel Ferreira
|
||||
Daniel Frank
|
||||
Daniel Holm
|
||||
Daniel Høyer Iversen
|
||||
Daniel Marynicz
|
||||
Daniel Nylander
|
||||
Daniel Patriche
|
||||
Daniel Schildt
|
||||
Daniil Sorokin
|
||||
Dante Díaz
|
||||
Daria Michalska
|
||||
DarkenCZ
|
||||
Darren
|
||||
Daspah
|
||||
David Eurenius
|
||||
davidhjelm
|
||||
David Machakhelidze
|
||||
Dawid Dziurdzia
|
||||
Daya Adianto
|
||||
dcruz
|
||||
Deady
|
||||
Dereck Wonnacott
|
||||
Devgru
|
||||
Devid Antonio FiloniDevilDogTG
|
||||
di0rz`
|
||||
Dialecti Valsamou
|
||||
Diego Medeiros
|
||||
Dkzoffy
|
||||
Dmitrij D. Czarkoff
|
||||
Dmitriy Geels
|
||||
Dmitry Olyenyov
|
||||
Dominik Kozaczko
|
||||
Dominik Lübben
|
||||
doomster
|
||||
Dorota Król
|
||||
Doyen Philippe
|
||||
Dread Knight
|
||||
DreamSonic
|
||||
duan
|
||||
Duong Thanh An
|
||||
DvoglavaZver
|
||||
dwori
|
||||
dylansmrjones
|
||||
Ebuntor
|
||||
Edgar Alejandro Jarquin Flores
|
||||
Eetu
|
||||
ekerazha
|
||||
Elias Julkunen
|
||||
elparia
|
||||
Emberke
|
||||
Emiliano Goday Caneda
|
||||
EndelWar
|
||||
eng.essam
|
||||
enubuntu
|
||||
ercangun
|
||||
Erdal Ronahi
|
||||
ergin üresin
|
||||
Eric
|
||||
Éric Lassauge
|
||||
Erlend Finvåg
|
||||
Errdil
|
||||
ethan shalev
|
||||
Evgeni Spasov
|
||||
ezekielnin
|
||||
Fabian Ordelmans
|
||||
Fabio Mazanatti
|
||||
Fábio Nogueira
|
||||
FaCuZ
|
||||
Felipe Lerena
|
||||
Fernando Pereira
|
||||
fjetland
|
||||
Florian Schäfer
|
||||
FoBoS
|
||||
Folke
|
||||
Force
|
||||
fosk
|
||||
fragarray
|
||||
freddeg
|
||||
Frédéric Perrin
|
||||
Fredrik Kilegran
|
||||
FreeAtMind
|
||||
Fulvio Ciucci
|
||||
Gabor Kelemen
|
||||
Galatsanos Panagiotis
|
||||
Gaussian
|
||||
gdevitis
|
||||
Georg Brzyk
|
||||
George Dumitrescu
|
||||
Georgi Arabadjiev
|
||||
Georg Sieber
|
||||
Gerd Radecke
|
||||
Germán Heusdens
|
||||
Gianni Vialetto
|
||||
Gigih Aji Ibrahim
|
||||
Giorgio Wicklein
|
||||
Giovanni Rapagnani
|
||||
Giuseppe
|
||||
gl
|
||||
glen
|
||||
granjerox
|
||||
Green Fish
|
||||
greentea
|
||||
Greyhound
|
||||
G. U.
|
||||
Guillaume BENOIT
|
||||
Guillaume Pelletier
|
||||
Gustavo Henrique Klug
|
||||
gutocarvalho
|
||||
Guybrush88
|
||||
Hans Rødtang
|
||||
HardDisk
|
||||
Hargas Gábor
|
||||
Heitor Thury Barreiros Barbosa
|
||||
helios91940
|
||||
helix84
|
||||
Helton Rodrigues
|
||||
Hendrik Luup
|
||||
Henrique Ferreiro
|
||||
Henry Goury-Laffont
|
||||
Hezy Amiel
|
||||
hidro
|
||||
hoball
|
||||
hokten
|
||||
Holmsss
|
||||
hristo.num
|
||||
Hubert Życiński
|
||||
Hyo
|
||||
Iarwain
|
||||
ibe
|
||||
ibear
|
||||
Id2ndR
|
||||
Igor Zubarev
|
||||
IKON (Ion)
|
||||
imen
|
||||
Ionuț Jula
|
||||
Isabelle STEVANT
|
||||
István Nyitrai
|
||||
Ivan Petrovic
|
||||
Ivan Prignano
|
||||
IvaSerge
|
||||
jackmc
|
||||
Jacks0nxD
|
||||
Jack Shen
|
||||
Jacky Yeung
|
||||
Jacques Stadler
|
||||
Janek Thomaschewski
|
||||
Jan Kaláb
|
||||
Jan Niklas Hasse
|
||||
Jasper Groenewegen
|
||||
Javi Rodríguez
|
||||
Jayasimha (ಜಯಸಿಂಹ)
|
||||
jeannich
|
||||
Jeff Bailes
|
||||
Jesse Zilstorff
|
||||
Joan Duran
|
||||
João Santos
|
||||
Joar Bagge
|
||||
Joe Anderson
|
||||
Joel Calado
|
||||
Johan Linde
|
||||
John Garland
|
||||
Jojan
|
||||
jollyr0ger
|
||||
Jonas Bo Grimsgaard
|
||||
Jonas Granqvist
|
||||
Jonas Slivka
|
||||
Jonathan Zeppettini
|
||||
Jørgen
|
||||
Jørgen Tellnes
|
||||
josé
|
||||
José Geraldo Gouvêa
|
||||
José Iván León Islas
|
||||
José Lou C.
|
||||
Jose Sun
|
||||
Jr.
|
||||
Jukka Kauppinen
|
||||
Julián Alarcón
|
||||
julietgolf
|
||||
Jusic
|
||||
Justzupi
|
||||
Kaarel
|
||||
Kai Thomsen
|
||||
Kalman Tarnay
|
||||
Kamil Páral
|
||||
Kane_F
|
||||
kaotiks@gmail.com
|
||||
Kateikyoushii
|
||||
kaxhinaz
|
||||
Kazuhiro NISHIYAMA
|
||||
Kerberos
|
||||
Keresztes Ákos
|
||||
kevintyk
|
||||
kiersie
|
||||
Kimbo^
|
||||
Kim Lübbe
|
||||
kitzOgen
|
||||
Kjetil Rydland
|
||||
kluon
|
||||
kmikz
|
||||
Knedlyk
|
||||
koleoptero
|
||||
Kőrösi Krisztián
|
||||
Kouta
|
||||
Krakatos
|
||||
Krešo Kunjas
|
||||
kripken
|
||||
Kristaps
|
||||
Kristian Øllegaard
|
||||
Kristoffer Egil Bonarjee
|
||||
Krzysztof Janowski
|
||||
Krzysztof Zawada
|
||||
Larry Wei Liu
|
||||
laughterwym
|
||||
Laur Mõtus
|
||||
lazka
|
||||
leandrud
|
||||
lê bình
|
||||
Le Coz Florent
|
||||
Leo
|
||||
liorda
|
||||
LKRaider
|
||||
LoLo_SaG
|
||||
Long Tran
|
||||
Lorenz
|
||||
Low Kian Seong
|
||||
Luca Andrea Rossi
|
||||
Luca Ferretti
|
||||
Lucky LIX
|
||||
Luis Gomes
|
||||
Luis Reis
|
||||
Łukasz Wyszyński
|
||||
luojie-dune
|
||||
maaark
|
||||
Maciej Chojnacki
|
||||
Maciej Meller
|
||||
Mads Peter Rommedahl
|
||||
Major Kong
|
||||
Malaki
|
||||
malde
|
||||
Malte Lenz
|
||||
Mantas Kriaučiūnas
|
||||
Mara Sorella
|
||||
Marcin
|
||||
Marcin Falkiewicz
|
||||
marcobra
|
||||
Marco da Silva
|
||||
Marco de Moulin
|
||||
Marco Rodrigues
|
||||
Marcos
|
||||
Marcos Escalier
|
||||
Marcos Mobley
|
||||
Marcus Ekstrom
|
||||
Marek Dębowski
|
||||
Mário Buči
|
||||
Mario Munda
|
||||
Marius Andersen
|
||||
Marius Hudea
|
||||
Marius Mihai
|
||||
Mariusz Cielecki
|
||||
Mark Krapivner
|
||||
marko-markovic
|
||||
Markus Brummer
|
||||
Markus Sutter
|
||||
Martin
|
||||
Martin Dybdal
|
||||
Martin Iglesias
|
||||
Martin Lettner
|
||||
Martin Pihl
|
||||
Masoud Kalali
|
||||
mat02
|
||||
Matej Urbančič
|
||||
Mathias-K
|
||||
Mathieu Arès
|
||||
Mathieu D. (MatToufoutu)
|
||||
Mathijs
|
||||
Matrik
|
||||
Matteo Renzulli
|
||||
Matteo Settenvini
|
||||
Matthew Gadd
|
||||
Matthias Benkard
|
||||
Matthias Mailänder
|
||||
Mattias Ohlsson
|
||||
Mauro de Carvalho
|
||||
Max Molchanov
|
||||
Me
|
||||
MercuryCC
|
||||
Mert Bozkurt
|
||||
Mert Dirik
|
||||
MFX
|
||||
mhietar
|
||||
mibtha
|
||||
Michael Budde
|
||||
Michael Kaliszka
|
||||
Michalis Makaronides
|
||||
Michał Tokarczyk
|
||||
Miguel Pires da Rosa
|
||||
Mihai Capotă
|
||||
Miika Metsälä
|
||||
Mikael Fernblad
|
||||
Mike Sierra
|
||||
mikhalek
|
||||
Milan Prvulović
|
||||
Milo Casagrande
|
||||
Mindaugas
|
||||
Miroslav Matejaš
|
||||
misel
|
||||
mithras
|
||||
Mitja Pagon
|
||||
M.Kitchen
|
||||
Mohamed Magdy
|
||||
moonkey
|
||||
MrBlonde
|
||||
muczy
|
||||
Münir Ekinci
|
||||
Mustafa Temizel
|
||||
mvoncken
|
||||
Mytonn
|
||||
NagyMarton
|
||||
neaion
|
||||
Neil Lin
|
||||
Nemo
|
||||
Nerijus Arlauskas
|
||||
Nicklas Larsson
|
||||
Nicolaj Wyke
|
||||
Nicola Piovesan
|
||||
Nicolas Sabatier
|
||||
Nicolas Velin
|
||||
Nightfall
|
||||
NiKoB
|
||||
Nikolai M. Riabov
|
||||
Niko_Thien
|
||||
niska
|
||||
Nithir
|
||||
noisemonkey
|
||||
nomemohes
|
||||
nosense
|
||||
null
|
||||
Nuno Estêvão
|
||||
Nuno Santos
|
||||
nxxs
|
||||
nyo
|
||||
obo
|
||||
Ojan
|
||||
Olav Andreas Lindekleiv
|
||||
oldbeggar
|
||||
Olivier FAURAX
|
||||
orphe
|
||||
osantana
|
||||
Osman Tosun
|
||||
OssiR
|
||||
otypoks
|
||||
ounn
|
||||
Oz123
|
||||
Özgür BASKIN
|
||||
Pablo Carmona A.
|
||||
Pablo Ledesma
|
||||
Pablo Navarro Castillo
|
||||
Paco Molinero
|
||||
Pål-Eivind Johnsen
|
||||
pano
|
||||
Paolo Naldini
|
||||
Paracelsus
|
||||
Patryk13_03
|
||||
Patryk Skorupa
|
||||
PattogoTehen
|
||||
Paul Lange
|
||||
Pavcio
|
||||
Paweł Wysocki
|
||||
Pedro Brites Moita
|
||||
Pedro Clemente Pereira Neto
|
||||
Pekka "PEXI" Niemistö
|
||||
Penegal
|
||||
Penzo
|
||||
perdido
|
||||
Peter Kotrcka
|
||||
Peter Skov
|
||||
Peter Van den Bosch
|
||||
Petter Eklund
|
||||
Petter Viklund
|
||||
phatsphere
|
||||
Phenomen
|
||||
Philipi
|
||||
Philippides Homer
|
||||
phoenix
|
||||
pidi
|
||||
Pierre Quillery
|
||||
Pierre Rudloff
|
||||
Pierre Slamich
|
||||
Pietrao
|
||||
Piotr Strębski
|
||||
Piotr Wicijowski
|
||||
Pittmann Tamás
|
||||
Playmolas
|
||||
Prescott
|
||||
Prescott_SK
|
||||
pronull
|
||||
Przemysław Kulczycki
|
||||
Pumy
|
||||
pushpika
|
||||
PY
|
||||
qubicllj
|
||||
r21vo
|
||||
Rafał Barański
|
||||
rainofchaos
|
||||
Rajbir
|
||||
ras0ir
|
||||
Rat
|
||||
rd1381
|
||||
Renato
|
||||
Rene Hennig
|
||||
Rene Pärts
|
||||
Ricardo Duarte
|
||||
Richard
|
||||
Robert Hrovat
|
||||
Roberth Sjonøy
|
||||
Robert Lundmark
|
||||
Robin Jakobsson
|
||||
Robin Kåveland
|
||||
Rodrigo Donado
|
||||
Roel Groeneveld
|
||||
rohmaru
|
||||
Rolf Christensen
|
||||
Rolf Leggewie
|
||||
Roni Kantis
|
||||
Ronmi
|
||||
Rostislav Raykov
|
||||
royto
|
||||
RuiAmaro
|
||||
Rui Araújo
|
||||
Rui Moura
|
||||
Rune Svendsen
|
||||
Rusna
|
||||
Rytis
|
||||
Sabirov Mikhail
|
||||
salseeg
|
||||
Sami Koskinen
|
||||
Samir van de Sand
|
||||
Samuel Arroyo Acuña
|
||||
Samuel R. C. Vale
|
||||
Sanel
|
||||
Santi
|
||||
Santi Martínez Cantelli
|
||||
Sardan
|
||||
Sargate Kanogan
|
||||
Sarmad Jari
|
||||
Saša Bodiroža
|
||||
sat0shi
|
||||
Saulius Pranckevičius
|
||||
Savvas Radevic
|
||||
Sebastian Krauß
|
||||
Sebastián Porta
|
||||
Sedir
|
||||
Sefa Denizoğlu
|
||||
sekolands
|
||||
Selim Suerkan
|
||||
semsomi
|
||||
Sergii Golovatiuk
|
||||
setarcos
|
||||
Sheki
|
||||
Shironeko
|
||||
Shlomil
|
||||
silfiriel
|
||||
Simone Tolotti
|
||||
Simone Vendemia
|
||||
sirkubador
|
||||
Sławomir Więch
|
||||
slip
|
||||
slyon
|
||||
smoke
|
||||
Sonja
|
||||
spectral
|
||||
spin_555
|
||||
spitf1r3
|
||||
Spiziuz
|
||||
Spyros Theodoritsis
|
||||
SqUe
|
||||
Squigly
|
||||
srtck
|
||||
Stefan Horning
|
||||
Stefano Maggiolo
|
||||
Stefano Roberto Soleti
|
||||
steinberger
|
||||
Stéphane Travostino
|
||||
Stephan Klein
|
||||
Steven De Winter
|
||||
Stevie
|
||||
Stian24
|
||||
stylius
|
||||
Sukarn Maini
|
||||
Sunjae Park
|
||||
Susana Pereira
|
||||
szymon siglowy
|
||||
takercena
|
||||
TAS
|
||||
Taygeto
|
||||
temy4
|
||||
texxxxxx
|
||||
thamood
|
||||
Thanos Chatziathanassiou
|
||||
Tharawut Paripaiboon
|
||||
Theodoor
|
||||
Théophane Anestis
|
||||
Thor Marius K. Høgås
|
||||
Tiago Silva
|
||||
Tiago Sousa
|
||||
Tikkel
|
||||
tim__b
|
||||
Tim Bordemann
|
||||
Tim Fuchs
|
||||
Tim Kornhammar
|
||||
Timo
|
||||
Timo Jyrinki
|
||||
Timothy Babych
|
||||
TitkosRejtozo
|
||||
Tom
|
||||
Tomas Gustavsson
|
||||
Tomas Valentukevičius
|
||||
Tomasz Dominikowski
|
||||
Tomislav Plavčić
|
||||
Tom Mannerhagen
|
||||
Tommy Mikkelsen
|
||||
Tom Verdaat
|
||||
Tony Manco
|
||||
Tor Erling H. Opsahl
|
||||
Toudi
|
||||
tqm_z
|
||||
Trapanator
|
||||
Tribaal
|
||||
Triton
|
||||
TuniX12
|
||||
Tuomo Sipola
|
||||
turbojugend_gr
|
||||
Turtle.net
|
||||
twilight
|
||||
tymmej
|
||||
Ulrik
|
||||
Umarzuki Mochlis
|
||||
unikob
|
||||
Vadim Gusev
|
||||
Vagi
|
||||
Valentin Bora
|
||||
Valmantas Palikša
|
||||
VASKITTU
|
||||
Vassilis Skoullis
|
||||
vetal17
|
||||
vicedo
|
||||
viki
|
||||
villads hamann
|
||||
Vincent Garibal
|
||||
Vincent Ortalda
|
||||
vinchi007
|
||||
Vinícius de Figueiredo Silva
|
||||
Vinzenz Vietzke
|
||||
virtoo
|
||||
virtual_spirit
|
||||
Vitor Caike
|
||||
Vitor Lamas Gatti
|
||||
Vladimir Lazic
|
||||
Vladimir Sharshov
|
||||
Wanderlust
|
||||
Wander Nauta
|
||||
Ward De Ridder
|
||||
WebCrusader
|
||||
webdr
|
||||
Wentao Tang
|
||||
wilana
|
||||
Wilfredo Ernesto Guerrero Campos
|
||||
Wim Champagne
|
||||
World Sucks
|
||||
Xabi Ezpeleta
|
||||
Xavi de Moner
|
||||
XavierToo
|
||||
XChesser
|
||||
Xiaodong Xu
|
||||
xyb
|
||||
Yaron
|
||||
Yasen Pramatarov
|
||||
YesPoX
|
||||
Yuren Ju
|
||||
Yves MATHIEU
|
||||
zekopeko
|
||||
zhuqin
|
||||
Zissan
|
||||
Γιάννης Κατσαμπίρης
|
||||
Артём Попов
|
||||
Миша
|
||||
Шаймарданов Максим
|
||||
蔡查理
|
33
DEPENDS
@@ -1,33 +0,0 @@
|
||||
=== Core ===
|
||||
* python >= 2.5
|
||||
* twisted >= 8.1
|
||||
* twisted-web >= 8.1
|
||||
* pyopenssl
|
||||
* simplejson (if python < 2.6)
|
||||
* setuptools
|
||||
* gettext
|
||||
* intltool
|
||||
* pyxdg
|
||||
* chardet
|
||||
* geoip-database (optional)
|
||||
* setproctitle (optional)
|
||||
* pillow (optional)
|
||||
* python-geoip (optional)
|
||||
|
||||
* libtorrent (rasterbar) >= 0.14
|
||||
|
||||
* If building libtorrent:
|
||||
* boost >= 1.34.1
|
||||
* openssl
|
||||
* zlib
|
||||
|
||||
=== Gtk ===
|
||||
* pygtk >= 2.12
|
||||
* librsvg
|
||||
* xdg-utils
|
||||
* python-notify (optional)
|
||||
* pygame (optional)
|
||||
* python-appindicator (optional)
|
||||
|
||||
=== Web ===
|
||||
* mako
|
13
LICENSE
@@ -1,16 +1,3 @@
|
||||
Deluge is licensed under the GNU General Public License version 3 with the
|
||||
addition of the following special exception:
|
||||
|
||||
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.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
|
12
MANIFEST.in
@@ -1,12 +0,0 @@
|
||||
recursive-include docs/man *
|
||||
recursive-include deluge *
|
||||
recursive-include win32 *
|
||||
|
||||
recursive-exclude deluge *.egg-link
|
||||
exclude deluge/ui/web/gen_gettext.py
|
||||
exclude deluge/ui/web/css/*-debug.css
|
||||
exclude deluge/ui/web/js/build.sh
|
||||
exclude deluge/ui/web/js/Deluge*.js
|
||||
exclude deluge/ui/web/js/*-debug.js
|
||||
prune deluge/ui/web/docs
|
||||
prune deluge/scripts
|
86
README
@@ -2,56 +2,92 @@
|
||||
Deluge BitTorrent Client
|
||||
==========================
|
||||
|
||||
Homepage: http://deluge-torrent.org
|
||||
|
||||
Authors:
|
||||
Andrew Resch
|
||||
Damien Churchill
|
||||
Marcos Pinto
|
||||
Martijn Voncken
|
||||
Sadrul Habib Chowdhury
|
||||
|
||||
Homepage: http://deluge-torrent.org
|
||||
|
||||
For contributors and past developers see:
|
||||
AUTHORS
|
||||
|
||||
==========================
|
||||
Installation Instructions:
|
||||
License
|
||||
==========================
|
||||
|
||||
For detailed instructions see: http://dev.deluge-torrent.org/wiki/Installing/Source
|
||||
|
||||
Ensure build dependencies are installed, see DEPENDS for a full listing.
|
||||
|
||||
Build and install by running:
|
||||
|
||||
$ python setup.py build
|
||||
$ sudo python setup.py install
|
||||
Deluge is under the GNU GPLv3 license.
|
||||
Icon data/pixmaps/deluge.svg and derivatives in data/icons are copyright
|
||||
Andrew Wedderburn and are under the GNU GPLv3.
|
||||
All other icons in data/pixmaps are copyright Andrew Resch and are under
|
||||
the GNU GPLv3.
|
||||
|
||||
==========================
|
||||
Contact/Support:
|
||||
==========================
|
||||
|
||||
Forum: http://forum.deluge-torrent.org
|
||||
IRC Channel: #deluge on irc.freenode.net
|
||||
We have two options available for support:
|
||||
|
||||
Our Forum, at http://forum.deluge-torrent.org
|
||||
or
|
||||
Our IRC Channel, at #deluge on Freenode
|
||||
|
||||
==========================
|
||||
Installation Instructions:
|
||||
==========================
|
||||
|
||||
First, make sure you have the proper build dependencies installed. On a normal
|
||||
Debian or Ubuntu system:
|
||||
|
||||
sudo apt-get install g++ make python-all-dev python-all python-dbus \
|
||||
python-gtk2 python-notify librsvg2-common python-xdg python-support \
|
||||
subversion libboost-dev libboost-python-dev libboost-iostreams-dev \
|
||||
libboost-thread-dev libboost-date-time-dev libboost-filesystem-dev \
|
||||
libboost-serialization-dev libssl-dev zlib1g-dev python-setuptools
|
||||
|
||||
The names of the packages may vary depending on your OS / distro.
|
||||
|
||||
Once you have the needed libraries installed, build and install by running:
|
||||
|
||||
$ python setup.py build
|
||||
$ sudo python setup.py install
|
||||
|
||||
|
||||
==========================
|
||||
FAQ
|
||||
==========================
|
||||
|
||||
For the full FAQ see: http://dev.deluge-torrent.org/wiki/Faq
|
||||
|
||||
How to start the various user-interfaces
|
||||
|
||||
Gtk:
|
||||
deluge or deluge-gtk
|
||||
deluge --ui gtk
|
||||
Console:
|
||||
deluge-console
|
||||
deluge --ui null
|
||||
Web:
|
||||
deluge-web
|
||||
deluge --ui web
|
||||
Go to http://localhost:8112/ default-password = "deluge"
|
||||
|
||||
How do I start the daemon?
|
||||
|
||||
I started "deluge" but i don't see the gtk-ui
|
||||
|
||||
The deluge command remembers the last interface it started. Be explicit and type one of the full "deluge -u <interface>" commands listed above.
|
||||
|
||||
Why is deluge still listed in my system tray even after I close it ?
|
||||
|
||||
You closed the gtk user-interface but you did not close the daemon. Choose "Quit & Shutdown Daemon" to close both Daemon and gtk-ui.
|
||||
|
||||
How do I start the daemon ?
|
||||
|
||||
deluged
|
||||
|
||||
How do I start the daemon with logging to console ?
|
||||
|
||||
deluged -d
|
||||
|
||||
I can't connect to the daemon from another machine
|
||||
|
||||
See: http://dev.deluge-torrent.org/wiki/UserGuide/ThinClient
|
||||
* Configure the daemon to allow remote connections
|
||||
* Restart the daemon.
|
||||
|
||||
Note: do not do this on a public ip , use the webui for unsecure networks.
|
||||
|
||||
I upgraded from 0.5 and plugin x is missing
|
||||
|
||||
1.0 is a rewrite, all old 0.5 plugins have to be rewritten.
|
||||
|
@@ -1,21 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Fixes glade files which may have set gtk stock labels set to translatable
|
||||
for x in `find . -name '*.glade' |grep -v '.git\|build'` ; do \
|
||||
for y in gtk-add gtk-apply gtk-bold gtk-cancel gtk-cdrom gtk-clear \
|
||||
gtk-close gtk-color-picker gtk-connect gtk-convert gtk-copy gtk-cut \
|
||||
gtk-delete gtk-dialog-error gtk-dialog-info gtk-dialog-question \
|
||||
gtk-dialog-warning gtk-dnd gtk-dnd-multiple gtk-edit gtk-execute gtk-find \
|
||||
gtk-find-and-replace gtk-floppy gtk-goto-bottom gtk-goto-first \
|
||||
gtk-goto-last gtk-goto-top gtk-go-back gtk-go-down gtk-go-forward \
|
||||
gtk-go-up gtk-help gtk-home gtk-index gtk-italic gtk-jump-to \
|
||||
gtk-justify-center gtk-justify-fill gtk-justify-left gtk-missing-image \
|
||||
gtk-new gtk-no gtk-ok gtk-open gtk-paste gtk-preferences gtk-print \
|
||||
gtk-print-preview gtk-properties gtk-quit gtk-redo gtk-refresh \
|
||||
gtk-remove gtk-revert-to-saved gtk-save gtk-save-as gtk-select-color \
|
||||
gtk-select-font gtk-sort-descending gtk-spell-check gtk-stop \
|
||||
gtk-strikethrough gtk-undelete gtk-underline gtk-undo gtk-yes \
|
||||
gtk-zoom-100 gtk-zoom-fit gtk-zoom-in gtk-zoom-out; do \
|
||||
sed -i "s/<property\ name\=\"label\"\ translatable\=\"yes\">$y<\/property>/<property\ name\=\"label\"\ translatable\=\"no\">$y<\/property>/g" $x; \
|
||||
done;\
|
||||
done
|
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
# Paths to exclude
|
||||
EXCLUSIONS = [
|
||||
"deluge/scripts",
|
||||
"deluge/i18n",
|
||||
]
|
||||
|
||||
POTFILE_IN = "deluge/i18n/POTFILES.in"
|
||||
|
||||
pattern = "deluge\/plugins\/.*\/build"
|
||||
compiled = re.compile(pattern)
|
||||
|
||||
sys.stdout.write("Creating " + POTFILE_IN + " ... ")
|
||||
sys.stdout.flush()
|
||||
to_translate = []
|
||||
for (dirpath, dirnames, filenames) in os.walk("deluge"):
|
||||
for filename in filenames:
|
||||
if os.path.splitext(filename)[1] in (".py", ".glade", ".in") \
|
||||
and dirpath not in EXCLUSIONS \
|
||||
and not compiled.match(dirpath):
|
||||
to_translate.append(os.path.join(dirpath, filename))
|
||||
|
||||
f = open(POTFILE_IN, "wb")
|
||||
for line in to_translate:
|
||||
f.write(line + "\n")
|
||||
|
||||
f.close()
|
||||
|
||||
print "Done"
|
5
debian/changelog
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
deluge-torrent (1.1.2-1) unstable; urgency=low
|
||||
|
||||
* 1.1.2 final
|
||||
|
||||
-- Andrew Resch (andar) <andrewresch@gmail.com> Sat, 31 Jan 2008 16:31:14 -0800
|
5
debian/changelog.debian-lenny
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
deluge-torrent (0.6.0-svn3235-1) lenny; urgency=low
|
||||
|
||||
* Daily Build
|
||||
|
||||
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800
|
5
debian/changelog.debian-sid
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
deluge-torrent (0.6.0-svn3235-1) unstable; urgency=low
|
||||
|
||||
* Daily Build
|
||||
|
||||
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800
|
5
debian/changelog.ubuntu-gutsy
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
deluge-torrent (0.6.0-svn3235-1) gutsy; urgency=low
|
||||
|
||||
* Daily Build
|
||||
|
||||
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800
|
5
debian/changelog.ubuntu-hardy
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
deluge-torrent (0.6.0-svn3235-1) hardy; urgency=low
|
||||
|
||||
* Daily Build
|
||||
|
||||
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800
|
1
debian/compat
vendored
Normal file
@@ -0,0 +1 @@
|
||||
5
|
21
debian/control
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
Source: deluge-torrent
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Andrew Resch (andar) <andrewresch@gmail.com>
|
||||
Build-Depends: debhelper (>= 5.0.37.2), python-all-dev (>= 2.3.5-11), python-all, python-support (>= 0.5.3), libboost-dev (>= 1.34.1), libboost-thread-dev (>= 1.34.1), libboost-date-time-dev (>= 1.34.1), libboost-filesystem-dev (>= 1.34.1), libboost-python-dev (>= 1.34.1), libboost-iostreams-dev (>= 1.34.1), zlib1g-dev, libssl-dev, dpatch, python-setuptools
|
||||
Standards-Version: 3.7.2
|
||||
|
||||
Package: deluge-torrent
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${python:Depends}, python-gtk2, python-glade2, python-xdg, python-notify, notification-daemon | notification-daemon-xfce, python-dbus, librsvg2-common, python-pyopenssl, python-setuptools, python-pkg-resources
|
||||
Conflicts: deluge-torrent-common
|
||||
Replaces: deluge-torrent-common
|
||||
Description: A Bittorrent client written in Python/PyGTK
|
||||
Deluge is a Bittorrent client, created using Python and GTK+.
|
||||
.
|
||||
Deluge is intended to bring a native, full-featured client to Linux GTK
|
||||
desktop environments such as Gnome and XFCE.
|
||||
.
|
||||
It uses Rasterbar's version of libtorrent.
|
||||
.
|
||||
Homepage: http://www.deluge-torrent.org/
|
19
debian/control.debian-lenny
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Source: deluge-torrent
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Andrew Resch (andar) <andrewresch@gmail.com>
|
||||
Build-Depends: debhelper (>= 5.0.37.2), python-all-dev (>= 2.3.5-11), python-all, python-support (>= 0.5.3), libboost-dev (>= 1.33.1), libboost-thread-dev (>= 1.33.1), libboost-date-time-dev (>= 1.33.1), libboost-filesystem-dev (>= 1.33.1), libboost-serialization-dev (>= 1.33.1), libboost-program-options-dev (>= 1.33.1), libboost-regex-dev (>= 1.33.1), libboost-python-dev (>= 1.33.1), zlib1g-dev, libssl-dev, dpatch, python-setuptools
|
||||
Standards-Version: 3.7.2
|
||||
|
||||
Package: deluge-torrent
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${python:Depends}, python-gtk2, python-glade2, python-xdg, python-notify, notification-daemon | notification-daemon-xfce, python-dbus, librsvg2-common, python-pyopenssl, python-setuptools
|
||||
Description: A Bittorrent client written in Python/PyGTK
|
||||
Deluge is a Bittorrent client, created using Python and GTK+.
|
||||
.
|
||||
Deluge is intended to bring a native, full-featured client to Linux GTK
|
||||
desktop environments such as Gnome and XFCE.
|
||||
.
|
||||
It uses Rasterbar's version of libtorrent.
|
||||
.
|
||||
Homepage: http://www.deluge-torrent.org/
|
19
debian/control.debian-sid
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Source: deluge-torrent
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Andrew Resch (andar) <andrewresch@gmail.com>
|
||||
Build-Depends: debhelper (>= 5.0.37.2), python-all-dev (>= 2.3.5-11), python-all, python-support (>= 0.5.3), libboost1.36-dev (>= 1.36), libboost-thread1.36-dev (>= 1.36), libboost-date-time1.36-dev (>= 1.36), libboost-filesystem1.36-dev (>= 1.36), libboost-serialization1.36-dev (>= 1.36), libboost-program-options1.36-dev (>= 1.36), libboost-regex1.36-dev (>= 1.36), libboost-python1.36-dev (>= 1.36), zlib1g-dev, libssl-dev, dpatch, python-setuptools
|
||||
Standards-Version: 3.7.2
|
||||
|
||||
Package: deluge-torrent
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${python:Depends}, python-gtk2, python-glade2, python-xdg, python-notify, notification-daemon | notification-daemon-xfce, python-dbus, librsvg2-common, python-pyopenssl, python-setuptools
|
||||
Description: A Bittorrent client written in Python/PyGTK
|
||||
Deluge is a Bittorrent client, created using Python and GTK+.
|
||||
.
|
||||
Deluge is intended to bring a native, full-featured client to Linux GTK
|
||||
desktop environments such as Gnome and XFCE.
|
||||
.
|
||||
It uses Rasterbar's version of libtorrent.
|
||||
.
|
||||
Homepage: http://www.deluge-torrent.org/
|
99
debian/copyright
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
This package was debianized by Marcos Pinto (markybob) <markybob@gmail.com> on
|
||||
Fri, 31 Jul 2008 22:03:13 +0100.
|
||||
|
||||
It was downloaded from http://www.deluge-torrent.org/
|
||||
|
||||
Upstream Authors & Copyright:
|
||||
Andrew Resch
|
||||
Marcos Pinto
|
||||
Sadrul Habib Chowdhury
|
||||
Martijn Voncken
|
||||
|
||||
License:
|
||||
|
||||
This package 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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This package 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 package; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, 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.
|
||||
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License can be found in `/usr/share/common-licenses/GPL'.
|
||||
|
||||
libtorrent is (C) 2003-2008 Arvid Norberg arvid@cs.umu.se and its
|
||||
python bindings were initially written by Daniel Wallin in 2006.
|
||||
It is distributed unders terms of the BSD License below:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"libtorrent/include/libtorrent/asio*" are (C) 2003-2008 Christopher
|
||||
M. Kohlhoff <chris@kohlhoff.com> and distributed under terms of the Boost
|
||||
Software License, Version 1.0 :
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
"deluge/i18n/*" are (C) 2006 Rosetta Contributors and Canonical Ltd 2006 and distributed
|
||||
under the same license as the deluge software.
|
||||
|
||||
The Debian packaging is (C) 2006-2008, Marcos Pinto (markybob)
|
||||
<markybob@gmail.com> and is licensed under GPL3, see above.
|
2
debian/manpages
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
deluge/docs/man/deluge.1
|
||||
deluge/docs/man/deluged.1
|
3
debian/menu
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
?package(deluge-torrent): needs="X11" section="Applications/Network/File Transfer" \
|
||||
title="Deluge BitTorrent Client" longtitle="Bittorrent client written in Python/PyGTK" \
|
||||
command="/usr/bin/deluge" icon="/usr/share/pixmaps/deluge.png"
|
0
debian/patches/00list
vendored
Normal file
1
debian/pyversions
vendored
Normal file
@@ -0,0 +1 @@
|
||||
2.5
|
1
debian/pyversions.ubuntu-gutsy
vendored
Normal file
@@ -0,0 +1 @@
|
||||
2.5
|
1
debian/pyversions.ubuntu-hardy
vendored
Normal file
@@ -0,0 +1 @@
|
||||
2.5
|
75
debian/rules
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
# Dpatch targets
|
||||
include /usr/share/dpatch/dpatch.make
|
||||
|
||||
# Available python (using debian/pyversions) and destdir
|
||||
PYVERS = 2.5
|
||||
DESTDIR = $(CURDIR)/debian/deluge-torrent
|
||||
|
||||
# We need to known the target arch to enable/disable amd64 hack
|
||||
ARCH = $(shell dpkg-architecture -qDEB_BUILD_ARCH_CPU)
|
||||
ARCH64 = ia64 amd64 alpha kfreebsd-amd64 ppc64
|
||||
|
||||
CFLAGS = -Wall -g
|
||||
ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
|
||||
CFLAGS += -O0
|
||||
else
|
||||
CFLAGS += -O2
|
||||
endif
|
||||
# python-libtorrent need to define AMD64 to work fine on a 64 bits system
|
||||
ifneq (,$(findstring $(ARCH),$(ARCH64)))
|
||||
CFLAGS += -DAMD64
|
||||
endif
|
||||
|
||||
build: patch-stamp $(PYVERS:%=build-stamp%)
|
||||
build-stamp%: patch-stamp
|
||||
dh_testdir
|
||||
CFLAGS="$(CFLAGS)" python$* setup.py build
|
||||
touch $@
|
||||
|
||||
clean: unpatch
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
rm -rf build/
|
||||
find . -name \*.pyc | xargs rm -f
|
||||
rm -rf build-stamp*
|
||||
dh_clean
|
||||
|
||||
install: build install-prereq $(PYVERS:%=install-%) install-finish
|
||||
install-prereq:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_clean -k
|
||||
dh_installdirs
|
||||
install-%:
|
||||
python$* setup.py install --root=$(DESTDIR) --prefix=/usr --no-compile
|
||||
install-finish:
|
||||
# Desktop menu
|
||||
rm -rf $(DESTDIR)/usr/share/applications
|
||||
install -D -m644 $(CURDIR)/deluge/data/share/applications/deluge.desktop $(DESTDIR)/usr/share/applications/deluge.desktop
|
||||
|
||||
|
||||
binary-indep: build install
|
||||
binary-arch: build install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_installchangelogs
|
||||
dh_installdocs
|
||||
dh_installmenu
|
||||
dh_strip
|
||||
dh_compress
|
||||
dh_fixperms
|
||||
dh_pysupport
|
||||
dh_desktop
|
||||
dh_installdeb
|
||||
dh_shlibdeps
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
.PHONY: build clean binary-indep binary-arch binary install
|
595
deluge/SimpleXMLRPCServer.py
Normal file
@@ -0,0 +1,595 @@
|
||||
"""Simple XML-RPC Server.
|
||||
|
||||
This module can be used to create simple XML-RPC servers
|
||||
by creating a server and either installing functions, a
|
||||
class instance, or by extending the SimpleXMLRPCServer
|
||||
class.
|
||||
|
||||
It can also be used to handle XML-RPC requests in a CGI
|
||||
environment using CGIXMLRPCRequestHandler.
|
||||
|
||||
A list of possible usage patterns follows:
|
||||
|
||||
1. Install functions:
|
||||
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_function(pow)
|
||||
server.register_function(lambda x,y: x+y, 'add')
|
||||
server.serve_forever()
|
||||
|
||||
2. Install an instance:
|
||||
|
||||
class MyFuncs:
|
||||
def __init__(self):
|
||||
# make all of the string functions available through
|
||||
# string.func_name
|
||||
import string
|
||||
self.string = string
|
||||
def _listMethods(self):
|
||||
# implement this method so that system.listMethods
|
||||
# knows to advertise the strings methods
|
||||
return list_public_methods(self) + \
|
||||
['string.' + method for method in list_public_methods(self.string)]
|
||||
def pow(self, x, y): return pow(x, y)
|
||||
def add(self, x, y) : return x + y
|
||||
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_introspection_functions()
|
||||
server.register_instance(MyFuncs())
|
||||
server.serve_forever()
|
||||
|
||||
3. Install an instance with custom dispatch method:
|
||||
|
||||
class Math:
|
||||
def _listMethods(self):
|
||||
# this method must be present for system.listMethods
|
||||
# to work
|
||||
return ['add', 'pow']
|
||||
def _methodHelp(self, method):
|
||||
# this method must be present for system.methodHelp
|
||||
# to work
|
||||
if method == 'add':
|
||||
return "add(2,3) => 5"
|
||||
elif method == 'pow':
|
||||
return "pow(x, y[, z]) => number"
|
||||
else:
|
||||
# By convention, return empty
|
||||
# string if no help is available
|
||||
return ""
|
||||
def _dispatch(self, method, params):
|
||||
if method == 'pow':
|
||||
return pow(*params)
|
||||
elif method == 'add':
|
||||
return params[0] + params[1]
|
||||
else:
|
||||
raise 'bad method'
|
||||
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_introspection_functions()
|
||||
server.register_instance(Math())
|
||||
server.serve_forever()
|
||||
|
||||
4. Subclass SimpleXMLRPCServer:
|
||||
|
||||
class MathServer(SimpleXMLRPCServer):
|
||||
def _dispatch(self, method, params):
|
||||
try:
|
||||
# We are forcing the 'export_' prefix on methods that are
|
||||
# callable through XML-RPC to prevent potential security
|
||||
# problems
|
||||
func = getattr(self, 'export_' + method)
|
||||
except AttributeError:
|
||||
raise Exception('method "%s" is not supported' % method)
|
||||
else:
|
||||
return func(*params)
|
||||
|
||||
def export_add(self, x, y):
|
||||
return x + y
|
||||
|
||||
server = MathServer(("localhost", 8000))
|
||||
server.serve_forever()
|
||||
|
||||
5. CGI script:
|
||||
|
||||
server = CGIXMLRPCRequestHandler()
|
||||
server.register_function(pow)
|
||||
server.handle_request()
|
||||
"""
|
||||
|
||||
# Written by Brian Quinlan (brian@sweetapp.com).
|
||||
# Based on code written by Fredrik Lundh.
|
||||
|
||||
import xmlrpclib
|
||||
from xmlrpclib import Fault
|
||||
import SocketServer
|
||||
import BaseHTTPServer
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
|
||||
"""resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
|
||||
|
||||
Resolves a dotted attribute name to an object. Raises
|
||||
an AttributeError if any attribute in the chain starts with a '_'.
|
||||
|
||||
If the optional allow_dotted_names argument is false, dots are not
|
||||
supported and this function operates similar to getattr(obj, attr).
|
||||
"""
|
||||
|
||||
if allow_dotted_names:
|
||||
attrs = attr.split('.')
|
||||
else:
|
||||
attrs = [attr]
|
||||
|
||||
for i in attrs:
|
||||
if i.startswith('_'):
|
||||
raise AttributeError(
|
||||
'attempt to access private attribute "%s"' % i
|
||||
)
|
||||
else:
|
||||
obj = getattr(obj,i)
|
||||
return obj
|
||||
|
||||
def list_public_methods(obj):
|
||||
"""Returns a list of attribute strings, found in the specified
|
||||
object, which represent callable attributes"""
|
||||
|
||||
return [member for member in dir(obj)
|
||||
if not member.startswith('_') and
|
||||
callable(getattr(obj, member))]
|
||||
|
||||
def remove_duplicates(lst):
|
||||
"""remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
|
||||
|
||||
Returns a copy of a list without duplicates. Every list
|
||||
item must be hashable and the order of the items in the
|
||||
resulting list is not defined.
|
||||
"""
|
||||
u = {}
|
||||
for x in lst:
|
||||
u[x] = 1
|
||||
|
||||
return u.keys()
|
||||
|
||||
class SimpleXMLRPCDispatcher:
|
||||
"""Mix-in class that dispatches XML-RPC requests.
|
||||
|
||||
This class is used to register XML-RPC method handlers
|
||||
and then to dispatch them. There should never be any
|
||||
reason to instantiate this class directly.
|
||||
"""
|
||||
|
||||
def __init__(self, allow_none, encoding):
|
||||
self.funcs = {}
|
||||
self.instance = None
|
||||
self.allow_none = allow_none
|
||||
self.encoding = encoding
|
||||
|
||||
def register_instance(self, instance, allow_dotted_names=False):
|
||||
"""Registers an instance to respond to XML-RPC requests.
|
||||
|
||||
Only one instance can be installed at a time.
|
||||
|
||||
If the registered instance has a _dispatch method then that
|
||||
method will be called with the name of the XML-RPC method and
|
||||
its parameters as a tuple
|
||||
e.g. instance._dispatch('add',(2,3))
|
||||
|
||||
If the registered instance does not have a _dispatch method
|
||||
then the instance will be searched to find a matching method
|
||||
and, if found, will be called. Methods beginning with an '_'
|
||||
are considered private and will not be called by
|
||||
SimpleXMLRPCServer.
|
||||
|
||||
If a registered function matches a XML-RPC request, then it
|
||||
will be called instead of the registered instance.
|
||||
|
||||
If the optional allow_dotted_names argument is true and the
|
||||
instance does not have a _dispatch method, method names
|
||||
containing dots are supported and resolved, as long as none of
|
||||
the name segments start with an '_'.
|
||||
|
||||
*** SECURITY WARNING: ***
|
||||
|
||||
Enabling the allow_dotted_names options allows intruders
|
||||
to access your module's global variables and may allow
|
||||
intruders to execute arbitrary code on your machine. Only
|
||||
use this option on a secure, closed network.
|
||||
|
||||
"""
|
||||
|
||||
self.instance = instance
|
||||
self.allow_dotted_names = allow_dotted_names
|
||||
|
||||
def register_function(self, function, name = None):
|
||||
"""Registers a function to respond to XML-RPC requests.
|
||||
|
||||
The optional name argument can be used to set a Unicode name
|
||||
for the function.
|
||||
"""
|
||||
|
||||
if name is None:
|
||||
name = function.__name__
|
||||
self.funcs[name] = function
|
||||
|
||||
def register_introspection_functions(self):
|
||||
"""Registers the XML-RPC introspection methods in the system
|
||||
namespace.
|
||||
|
||||
see http://xmlrpc.usefulinc.com/doc/reserved.html
|
||||
"""
|
||||
|
||||
self.funcs.update({'system.listMethods' : self.system_listMethods,
|
||||
'system.methodSignature' : self.system_methodSignature,
|
||||
'system.methodHelp' : self.system_methodHelp})
|
||||
|
||||
def register_multicall_functions(self):
|
||||
"""Registers the XML-RPC multicall method in the system
|
||||
namespace.
|
||||
|
||||
see http://www.xmlrpc.com/discuss/msgReader$1208"""
|
||||
|
||||
self.funcs.update({'system.multicall' : self.system_multicall})
|
||||
|
||||
def _marshaled_dispatch(self, data, dispatch_method = None):
|
||||
"""Dispatches an XML-RPC method from marshalled (XML) data.
|
||||
|
||||
XML-RPC methods are dispatched from the marshalled (XML) data
|
||||
using the _dispatch method and the result is returned as
|
||||
marshalled data. For backwards compatibility, a dispatch
|
||||
function can be provided as an argument (see comment in
|
||||
SimpleXMLRPCRequestHandler.do_POST) but overriding the
|
||||
existing method through subclassing is the prefered means
|
||||
of changing method dispatch behavior.
|
||||
"""
|
||||
|
||||
try:
|
||||
params, method = xmlrpclib.loads(data)
|
||||
|
||||
# generate response
|
||||
if dispatch_method is not None:
|
||||
response = dispatch_method(method, params)
|
||||
else:
|
||||
response = self._dispatch(method, params)
|
||||
# wrap response in a singleton tuple
|
||||
response = (response,)
|
||||
response = xmlrpclib.dumps(response, methodresponse=1,
|
||||
allow_none=self.allow_none, encoding=self.encoding)
|
||||
except Fault, fault:
|
||||
response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
|
||||
encoding=self.encoding)
|
||||
except:
|
||||
# report exception back to server
|
||||
response = xmlrpclib.dumps(
|
||||
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)),
|
||||
encoding=self.encoding, allow_none=self.allow_none,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def system_listMethods(self):
|
||||
"""system.listMethods() => ['add', 'subtract', 'multiple']
|
||||
|
||||
Returns a list of the methods supported by the server."""
|
||||
|
||||
methods = self.funcs.keys()
|
||||
if self.instance is not None:
|
||||
# Instance can implement _listMethod to return a list of
|
||||
# methods
|
||||
if hasattr(self.instance, '_listMethods'):
|
||||
methods = remove_duplicates(
|
||||
methods + self.instance._listMethods()
|
||||
)
|
||||
# if the instance has a _dispatch method then we
|
||||
# don't have enough information to provide a list
|
||||
# of methods
|
||||
elif not hasattr(self.instance, '_dispatch'):
|
||||
methods = remove_duplicates(
|
||||
methods + list_public_methods(self.instance)
|
||||
)
|
||||
methods.sort()
|
||||
return methods
|
||||
|
||||
def system_methodSignature(self, method_name):
|
||||
"""system.methodSignature('add') => [double, int, int]
|
||||
|
||||
Returns a list describing the signature of the method. In the
|
||||
above example, the add method takes two integers as arguments
|
||||
and returns a double result.
|
||||
|
||||
This server does NOT support system.methodSignature."""
|
||||
|
||||
# See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
|
||||
|
||||
return 'signatures not supported'
|
||||
|
||||
def system_methodHelp(self, method_name):
|
||||
"""system.methodHelp('add') => "Adds two integers together"
|
||||
|
||||
Returns a string containing documentation for the specified method."""
|
||||
|
||||
method = None
|
||||
if self.funcs.has_key(method_name):
|
||||
method = self.funcs[method_name]
|
||||
elif self.instance is not None:
|
||||
# Instance can implement _methodHelp to return help for a method
|
||||
if hasattr(self.instance, '_methodHelp'):
|
||||
return self.instance._methodHelp(method_name)
|
||||
# if the instance has a _dispatch method then we
|
||||
# don't have enough information to provide help
|
||||
elif not hasattr(self.instance, '_dispatch'):
|
||||
try:
|
||||
method = resolve_dotted_attribute(
|
||||
self.instance,
|
||||
method_name,
|
||||
self.allow_dotted_names
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Note that we aren't checking that the method actually
|
||||
# be a callable object of some kind
|
||||
if method is None:
|
||||
return ""
|
||||
else:
|
||||
import pydoc
|
||||
return pydoc.getdoc(method)
|
||||
|
||||
def system_multicall(self, call_list):
|
||||
"""system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
|
||||
[[4], ...]
|
||||
|
||||
Allows the caller to package multiple XML-RPC calls into a single
|
||||
request.
|
||||
|
||||
See http://www.xmlrpc.com/discuss/msgReader$1208
|
||||
"""
|
||||
|
||||
results = []
|
||||
for call in call_list:
|
||||
method_name = call['methodName']
|
||||
params = call['params']
|
||||
|
||||
try:
|
||||
# XXX A marshalling error in any response will fail the entire
|
||||
# multicall. If someone cares they should fix this.
|
||||
results.append([self._dispatch(method_name, params)])
|
||||
except Fault, fault:
|
||||
results.append(
|
||||
{'faultCode' : fault.faultCode,
|
||||
'faultString' : fault.faultString}
|
||||
)
|
||||
except:
|
||||
results.append(
|
||||
{'faultCode' : 1,
|
||||
'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
|
||||
)
|
||||
return results
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
"""Dispatches the XML-RPC method.
|
||||
|
||||
XML-RPC calls are forwarded to a registered function that
|
||||
matches the called XML-RPC method name. If no such function
|
||||
exists then the call is forwarded to the registered instance,
|
||||
if available.
|
||||
|
||||
If the registered instance has a _dispatch method then that
|
||||
method will be called with the name of the XML-RPC method and
|
||||
its parameters as a tuple
|
||||
e.g. instance._dispatch('add',(2,3))
|
||||
|
||||
If the registered instance does not have a _dispatch method
|
||||
then the instance will be searched to find a matching method
|
||||
and, if found, will be called.
|
||||
|
||||
Methods beginning with an '_' are considered private and will
|
||||
not be called.
|
||||
"""
|
||||
|
||||
func = None
|
||||
try:
|
||||
# check to see if a matching function has been registered
|
||||
func = self.funcs[method]
|
||||
except KeyError:
|
||||
if self.instance is not None:
|
||||
# check for a _dispatch method
|
||||
if hasattr(self.instance, '_dispatch'):
|
||||
return self.instance._dispatch(method, params)
|
||||
else:
|
||||
# call instance method directly
|
||||
try:
|
||||
func = resolve_dotted_attribute(
|
||||
self.instance,
|
||||
method,
|
||||
self.allow_dotted_names
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if func is not None:
|
||||
return func(*params)
|
||||
else:
|
||||
raise Exception('method "%s" is not supported' % method)
|
||||
|
||||
class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
"""Simple XML-RPC request handler class.
|
||||
|
||||
Handles all HTTP POST requests and attempts to decode them as
|
||||
XML-RPC requests.
|
||||
"""
|
||||
|
||||
# Class attribute listing the accessible path components;
|
||||
# paths not on this list will result in a 404 error.
|
||||
rpc_paths = ('/', '/RPC2')
|
||||
|
||||
def is_rpc_path_valid(self):
|
||||
if self.rpc_paths:
|
||||
return self.path in self.rpc_paths
|
||||
else:
|
||||
# If .rpc_paths is empty, just assume all paths are legal
|
||||
return True
|
||||
|
||||
def do_POST(self):
|
||||
"""Handles the HTTP POST request.
|
||||
|
||||
Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
||||
which are forwarded to the server's _dispatch method for handling.
|
||||
"""
|
||||
|
||||
# Check that the path is legal
|
||||
if not self.is_rpc_path_valid():
|
||||
self.report_404()
|
||||
return
|
||||
|
||||
try:
|
||||
# Get arguments by reading body of request.
|
||||
# We read this in chunks to avoid straining
|
||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
||||
# begin to have problems (bug #792570).
|
||||
max_chunk_size = 10*1024*1024
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
L.append(self.rfile.read(chunk_size))
|
||||
size_remaining -= len(L[-1])
|
||||
data = ''.join(L)
|
||||
|
||||
# In previous versions of SimpleXMLRPCServer, _dispatch
|
||||
# could be overridden in this class, instead of in
|
||||
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
||||
# check to see if a subclass implements _dispatch and dispatch
|
||||
# using that method if present.
|
||||
response = self.server._marshaled_dispatch(
|
||||
data, getattr(self, '_dispatch', None)
|
||||
)
|
||||
except: # This should only happen if the module is buggy
|
||||
# internal error, report as HTTP server error
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
else:
|
||||
# got a valid XML RPC response
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/xml")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
def report_404 (self):
|
||||
# Report a 404 error
|
||||
self.send_response(404)
|
||||
response = 'No such page'
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
def log_request(self, code='-', size='-'):
|
||||
"""Selectively log an accepted request."""
|
||||
|
||||
if self.server.logRequests:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
|
||||
|
||||
class SimpleXMLRPCServer(SocketServer.TCPServer,
|
||||
SimpleXMLRPCDispatcher):
|
||||
"""Simple XML-RPC server.
|
||||
|
||||
Simple XML-RPC server that allows functions and a single instance
|
||||
to be installed to handle requests. The default implementation
|
||||
attempts to dispatch XML-RPC calls to the functions or instance
|
||||
installed in the server. Override the _dispatch method inhereted
|
||||
from SimpleXMLRPCDispatcher to change this behavior.
|
||||
"""
|
||||
|
||||
allow_reuse_address = True
|
||||
|
||||
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
|
||||
logRequests=True, allow_none=False, encoding=None):
|
||||
self.logRequests = logRequests
|
||||
|
||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
||||
SocketServer.TCPServer.__init__(self, addr, requestHandler)
|
||||
|
||||
# [Bug #1222790] If possible, set close-on-exec flag; if a
|
||||
# method spawns a subprocess, the subprocess shouldn't have
|
||||
# the listening socket open.
|
||||
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
||||
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
||||
flags |= fcntl.FD_CLOEXEC
|
||||
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
||||
|
||||
class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
|
||||
"""Simple handler for XML-RPC data passed through CGI."""
|
||||
|
||||
def __init__(self, allow_none=False, encoding=None):
|
||||
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
||||
|
||||
def handle_xmlrpc(self, request_text):
|
||||
"""Handle a single XML-RPC request"""
|
||||
|
||||
response = self._marshaled_dispatch(request_text)
|
||||
|
||||
print('Content-Type: text/xml')
|
||||
print('Content-Length: %d' % len(response))
|
||||
print(
|
||||
sys.stdout.write(response))
|
||||
|
||||
def handle_get(self):
|
||||
"""Handle a single HTTP GET request.
|
||||
|
||||
Default implementation indicates an error because
|
||||
XML-RPC uses the POST method.
|
||||
"""
|
||||
|
||||
code = 400
|
||||
message, explain = \
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
|
||||
|
||||
response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
|
||||
{
|
||||
'code' : code,
|
||||
'message' : message,
|
||||
'explain' : explain
|
||||
}
|
||||
print('Status: %d %s' % (code, message))
|
||||
print('Content-Type: text/html')
|
||||
print('Content-Length: %d' % len(response))
|
||||
print(
|
||||
sys.stdout.write(response))
|
||||
|
||||
def handle_request(self, request_text = None):
|
||||
"""Handle a single XML-RPC request passed through a CGI post method.
|
||||
|
||||
If no XML data is given then it is read from stdin. The resulting
|
||||
XML-RPC response is printed to stdout along with the correct HTTP
|
||||
headers.
|
||||
"""
|
||||
|
||||
if request_text is None and \
|
||||
os.environ.get('REQUEST_METHOD', None) == 'GET':
|
||||
self.handle_get()
|
||||
else:
|
||||
# POST data is normally available through stdin
|
||||
if request_text is None:
|
||||
request_text = sys.stdin.read()
|
||||
|
||||
self.handle_xmlrpc(request_text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Running XML-RPC server on port 8000')
|
||||
server = SimpleXMLRPCServer(("localhost", 8000))
|
||||
server.register_function(pow)
|
||||
server.register_function(lambda x,y: x+y, 'add')
|
||||
server.serve_forever()
|
@@ -1,20 +0,0 @@
|
||||
from new import classobj
|
||||
from deluge.core.core import Core
|
||||
from deluge.core.daemon import Daemon
|
||||
|
||||
class RpcApi:
|
||||
pass
|
||||
|
||||
def scan_for_methods(obj):
|
||||
methods = {
|
||||
'__doc__': 'Methods available in %s' % obj.__name__.lower()
|
||||
}
|
||||
for d in dir(obj):
|
||||
if not hasattr(getattr(obj,d), '_rpcserver_export'):
|
||||
continue
|
||||
methods[d] = getattr(obj, d)
|
||||
cobj = classobj(obj.__name__.lower(), (object,), methods)
|
||||
setattr(RpcApi, obj.__name__.lower(), cobj)
|
||||
|
||||
scan_for_methods(Core)
|
||||
scan_for_methods(Daemon)
|
@@ -1,60 +0,0 @@
|
||||
#
|
||||
# _libtorrent.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge 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 deluge. 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.
|
||||
#
|
||||
#
|
||||
|
||||
"""
|
||||
This module is used to handle the importing of libtorrent.
|
||||
|
||||
We use this module to control what versions of libtorrent this version of Deluge
|
||||
supports.
|
||||
|
||||
** Usage **
|
||||
|
||||
>>> from deluge._libtorrent import lt
|
||||
|
||||
"""
|
||||
|
||||
REQUIRED_VERSION = "0.14.9.0"
|
||||
|
||||
def check_version(lt):
|
||||
from deluge.common import VersionSplit
|
||||
if VersionSplit(lt.version) < VersionSplit(REQUIRED_VERSION):
|
||||
raise ImportError("This version of Deluge requires libtorrent >=%s!" % REQUIRED_VERSION)
|
||||
|
||||
try:
|
||||
import deluge.libtorrent as lt
|
||||
check_version(lt)
|
||||
except ImportError:
|
||||
import libtorrent as lt
|
||||
check_version(lt)
|
478
deluge/common.py
@@ -17,72 +17,21 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
"""Common functions for various parts of Deluge to use."""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import platform
|
||||
import sys
|
||||
import chardet
|
||||
|
||||
import pkg_resources
|
||||
import gettext
|
||||
import locale
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
from deluge.error import *
|
||||
from deluge.log import LOG as log
|
||||
|
||||
# Do a little hack here just in case the user has json-py installed since it
|
||||
# has a different api
|
||||
if not hasattr(json, "dumps"):
|
||||
json.dumps = json.write
|
||||
json.loads = json.read
|
||||
|
||||
def dump(obj, fp, **kw):
|
||||
fp.write(json.dumps(obj))
|
||||
|
||||
def load(fp, **kw):
|
||||
return json.loads(fp.read())
|
||||
|
||||
json.dump = dump
|
||||
json.load = load
|
||||
|
||||
# Initialize gettext
|
||||
try:
|
||||
if hasattr(locale, "bindtextdomain"):
|
||||
locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
||||
if hasattr(locale, "textdomain"):
|
||||
locale.textdomain("deluge")
|
||||
gettext.install("deluge", pkg_resources.resource_filename("deluge", "i18n"), unicode=True)
|
||||
except Exception, e:
|
||||
log.error("Unable to initialize gettext/locale!")
|
||||
log.exception(e)
|
||||
import __builtin__
|
||||
__builtin__.__dict__["_"] = lambda x: x
|
||||
import xdg, xdg.BaseDirectory
|
||||
|
||||
LT_TORRENT_STATE = {
|
||||
"Queued": 0,
|
||||
@@ -92,7 +41,6 @@ LT_TORRENT_STATE = {
|
||||
"Finished": 4,
|
||||
"Seeding": 5,
|
||||
"Allocating": 6,
|
||||
"Checking Resume Data": 7,
|
||||
0: "Queued",
|
||||
1: "Checking",
|
||||
2: "Downloading Metadata",
|
||||
@@ -100,7 +48,6 @@ LT_TORRENT_STATE = {
|
||||
4: "Finished",
|
||||
5: "Seeding",
|
||||
6: "Allocating",
|
||||
7: "Checking Resume Data"
|
||||
}
|
||||
|
||||
TORRENT_STATE = [
|
||||
@@ -117,15 +64,11 @@ FILE_PRIORITY = {
|
||||
0: "Do Not Download",
|
||||
1: "Normal Priority",
|
||||
2: "High Priority",
|
||||
3: "High Priority",
|
||||
4: "High Priority",
|
||||
5: "High Priority",
|
||||
6: "High Priority",
|
||||
7: "Highest Priority",
|
||||
5: "Highest Priority",
|
||||
"Do Not Download": 0,
|
||||
"Normal Priority": 1,
|
||||
"High Priority": 5,
|
||||
"Highest Priority": 7
|
||||
"High Priority": 2,
|
||||
"Highest Priority": 5
|
||||
}
|
||||
|
||||
def get_version():
|
||||
@@ -138,36 +81,41 @@ def get_version():
|
||||
"""
|
||||
return pkg_resources.require("Deluge")[0].version
|
||||
|
||||
def get_revision():
|
||||
"""
|
||||
The svn revision of the build if available
|
||||
|
||||
:returns: the svn revision, or ""
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
revision = ""
|
||||
try:
|
||||
f = open(pkg_resources.resource_filename("deluge", os.path.join("data", "revision")))
|
||||
revision = f.read()
|
||||
f.close()
|
||||
except IOError, e:
|
||||
pass
|
||||
|
||||
return revision
|
||||
|
||||
def get_default_config_dir(filename=None):
|
||||
"""
|
||||
:param filename: if None, only the config path is returned, if provided, a path including the filename will be returned
|
||||
:type filename: string
|
||||
:returns: a file path to the config directory and optional filename
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
if windows_check():
|
||||
appDataPath = os.environ.get("APPDATA")
|
||||
if not appDataPath:
|
||||
import _winreg
|
||||
hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
|
||||
appDataReg = _winreg.QueryValueEx(hkey, "AppData")
|
||||
appDataPath = appDataReg[0]
|
||||
_winreg.CloseKey(hkey)
|
||||
if filename:
|
||||
return os.path.join(appDataPath, "deluge", filename)
|
||||
return os.path.join(os.environ.get("APPDATA"), "deluge", filename)
|
||||
else:
|
||||
return os.path.join(appDataPath, "deluge")
|
||||
return os.path.join(os.environ.get("APPDATA"), "deluge")
|
||||
else:
|
||||
from xdg.BaseDirectory import save_config_path
|
||||
try:
|
||||
if filename:
|
||||
return os.path.join(save_config_path("deluge"), filename)
|
||||
else:
|
||||
return save_config_path("deluge")
|
||||
except OSError, e:
|
||||
log.error("Unable to use default config directory, exiting... (%s)", e)
|
||||
sys.exit(1)
|
||||
if filename:
|
||||
return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename)
|
||||
else:
|
||||
return xdg.BaseDirectory.save_config_path("deluge")
|
||||
|
||||
def get_default_download_dir():
|
||||
"""
|
||||
@@ -175,21 +123,10 @@ def get_default_download_dir():
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
download_dir = ""
|
||||
if not windows_check():
|
||||
from xdg.BaseDirectory import xdg_config_home
|
||||
try:
|
||||
with open(os.path.join(xdg_config_home, 'user-dirs.dirs'), 'r') as _file:
|
||||
for line in _file:
|
||||
if not line.startswith('#') and line.startswith('XDG_DOWNLOAD_DIR'):
|
||||
download_dir = os.path.expandvars(line.partition("=")[2].rstrip().strip('"'))
|
||||
break
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
if not download_dir:
|
||||
download_dir = os.path.join(os.path.expanduser("~"), 'Downloads')
|
||||
return download_dir
|
||||
if windows_check():
|
||||
return os.path.expanduser("~")
|
||||
else:
|
||||
return os.environ.get("HOME")
|
||||
|
||||
def windows_check():
|
||||
"""
|
||||
@@ -226,7 +163,6 @@ def get_pixmap(fname):
|
||||
Provides easy access to files in the deluge/data/pixmaps folder within the Deluge egg
|
||||
|
||||
:param fname: the filename to look for
|
||||
:type fname: string
|
||||
:returns: a path to a pixmap file included with Deluge
|
||||
:rtype: string
|
||||
|
||||
@@ -234,38 +170,33 @@ def get_pixmap(fname):
|
||||
return pkg_resources.resource_filename("deluge", os.path.join("data", \
|
||||
"pixmaps", fname))
|
||||
|
||||
def open_file(path, timestamp=None):
|
||||
def open_file(path):
|
||||
"""
|
||||
Opens a file or folder using the system configured program
|
||||
|
||||
:param path: the path to the file or folder to open
|
||||
:type path: string
|
||||
:param timestamp: the timestamp of the event that requested to open
|
||||
:type timestamp: int
|
||||
|
||||
"""
|
||||
if windows_check():
|
||||
os.startfile(path.decode("utf8"))
|
||||
elif osx_check():
|
||||
subprocess.Popen(["open", "%s" % path])
|
||||
os.startfile("%s" % path)
|
||||
else:
|
||||
if timestamp is None:
|
||||
timestamp = int(time.time())
|
||||
env = os.environ.copy()
|
||||
env["DESKTOP_STARTUP_ID"] = "%s-%u-%s-xdg_open_TIME%d" % \
|
||||
(os.path.basename(sys.argv[0]), os.getpid(), os.uname()[1], timestamp)
|
||||
subprocess.Popen(["xdg-open", "%s" % path], env=env)
|
||||
subprocess.Popen(["xdg-open", "%s" % path])
|
||||
|
||||
def open_url_in_browser(url):
|
||||
"""
|
||||
Opens a url in the desktop's default browser
|
||||
|
||||
:param url: the url to open
|
||||
:type url: string
|
||||
|
||||
"""
|
||||
import threading
|
||||
import webbrowser
|
||||
webbrowser.open(url)
|
||||
class BrowserThread(threading.Thread):
|
||||
def __init__(self, url):
|
||||
threading.Thread.__init__(self)
|
||||
self.url = url
|
||||
def run(self):
|
||||
webbrowser.open(self.url)
|
||||
BrowserThread(url).start()
|
||||
|
||||
## Formatting text functions
|
||||
|
||||
@@ -273,8 +204,7 @@ def fsize(fsize_b):
|
||||
"""
|
||||
Formats the bytes value into a string with KiB, MiB or GiB units
|
||||
|
||||
:param fsize_b: the filesize in bytes
|
||||
:type fsize_b: int
|
||||
:param fsize_b: int, the filesize in bytes
|
||||
:returns: formatted string in KiB, MiB or GiB units
|
||||
:rtype: string
|
||||
|
||||
@@ -286,43 +216,18 @@ def fsize(fsize_b):
|
||||
"""
|
||||
fsize_kb = fsize_b / 1024.0
|
||||
if fsize_kb < 1024:
|
||||
return "%.1f %s" % (fsize_kb, _("KiB"))
|
||||
return "%.1f KiB" % fsize_kb
|
||||
fsize_mb = fsize_kb / 1024.0
|
||||
if fsize_mb < 1024:
|
||||
return "%.1f %s" % (fsize_mb, _("MiB"))
|
||||
return "%.1f MiB" % fsize_mb
|
||||
fsize_gb = fsize_mb / 1024.0
|
||||
return "%.1f %s" % (fsize_gb, _("GiB"))
|
||||
|
||||
def fsize_short(fsize_b):
|
||||
"""
|
||||
Formats the bytes value into a string with K, M or G units
|
||||
|
||||
:param fsize_b: the filesize in bytes
|
||||
:type fsize_b: int
|
||||
:returns: formatted string in K, M or G units
|
||||
:rtype: string
|
||||
|
||||
**Usage**
|
||||
|
||||
>>> fsize(112245)
|
||||
'109.6 K'
|
||||
|
||||
"""
|
||||
fsize_kb = fsize_b / 1024.0
|
||||
if fsize_kb < 1024:
|
||||
return "%.1f %s" % (fsize_kb, _("K"))
|
||||
fsize_mb = fsize_kb / 1024.0
|
||||
if fsize_mb < 1024:
|
||||
return "%.1f %s" % (fsize_mb, _("M"))
|
||||
fsize_gb = fsize_mb / 1024.0
|
||||
return "%.1f %s" % (fsize_gb, _("G"))
|
||||
return "%.1f GiB" % fsize_gb
|
||||
|
||||
def fpcnt(dec):
|
||||
"""
|
||||
Formats a string to display a percentage with two decimal places
|
||||
|
||||
:param dec: the ratio in the range [0.0, 1.0]
|
||||
:type dec: float
|
||||
:param dec: float, the ratio in the range [0.0, 1.0]
|
||||
:returns: a formatted string representing a percentage
|
||||
:rtype: string
|
||||
|
||||
@@ -338,8 +243,7 @@ def fspeed(bps):
|
||||
"""
|
||||
Formats a string to display a transfer speed utilizing :func:`fsize`
|
||||
|
||||
:param bps: bytes per second
|
||||
:type bps: int
|
||||
:param bps: int, bytes per second
|
||||
:returns: a formatted string representing transfer speed
|
||||
:rtype: string
|
||||
|
||||
@@ -349,23 +253,14 @@ def fspeed(bps):
|
||||
'42.1 KiB/s'
|
||||
|
||||
"""
|
||||
fspeed_kb = bps / 1024.0
|
||||
if fspeed_kb < 1024:
|
||||
return "%.1f %s" % (fspeed_kb, _("KiB/s"))
|
||||
fspeed_mb = fspeed_kb / 1024.0
|
||||
if fspeed_mb < 1024:
|
||||
return "%.1f %s" % (fspeed_mb, _("MiB/s"))
|
||||
fspeed_gb = fspeed_mb / 1024.0
|
||||
return "%.1f %s" % (fspeed_gb, _("GiB/s"))
|
||||
return '%s/s' % (fsize(bps))
|
||||
|
||||
def fpeer(num_peers, total_peers):
|
||||
"""
|
||||
Formats a string to show 'num_peers' ('total_peers')
|
||||
|
||||
:param num_peers: the number of connected peers
|
||||
:type num_peers: int
|
||||
:param total_peers: the total number of peers
|
||||
:type total_peers: int
|
||||
:param num_peers: int, the number of connected peers
|
||||
:param total_peers: int, the total number of peers
|
||||
:returns: a formatted string: num_peers (total_peers), if total_peers < 0, then it will not be shown
|
||||
:rtype: string
|
||||
|
||||
@@ -386,8 +281,7 @@ def ftime(seconds):
|
||||
"""
|
||||
Formats a string to show time in a human readable form
|
||||
|
||||
:param seconds: the number of seconds
|
||||
:type seconds: int
|
||||
:param seconds: int, the number of seconds
|
||||
:returns: a formatted time string, will return '' if seconds == 0
|
||||
:rtype: string
|
||||
|
||||
@@ -423,24 +317,22 @@ def ftime(seconds):
|
||||
|
||||
def fdate(seconds):
|
||||
"""
|
||||
Formats a date time string in the locale's date representation based on the systems timezone
|
||||
Formats a date string in the locale's date representation based on the systems timezone
|
||||
|
||||
:param seconds: time in seconds since the Epoch
|
||||
:type seconds: float
|
||||
:returns: a string in the locale's datetime representation or "" if seconds < 0
|
||||
:param seconds: float, time in seconds since the Epoch
|
||||
:returns: a string in the locale's date representation or "" if seconds < 0
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
if seconds < 0:
|
||||
return ""
|
||||
return time.strftime("%x %X", time.localtime(seconds))
|
||||
return time.strftime("%x", time.localtime(seconds))
|
||||
|
||||
def is_url(url):
|
||||
"""
|
||||
A simple test to check if the URL is valid
|
||||
A simple regex test to check if the URL is valid
|
||||
|
||||
:param url: the url to test
|
||||
:type url: string
|
||||
:param url: string, the url to test
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
|
||||
@@ -450,14 +342,14 @@ def is_url(url):
|
||||
True
|
||||
|
||||
"""
|
||||
return url.partition('://')[0] in ("http", "https", "ftp", "udp")
|
||||
import re
|
||||
return bool(re.search('^(https?|ftp|udp)://', url))
|
||||
|
||||
def is_magnet(uri):
|
||||
"""
|
||||
A check to determine if a uri is a valid bittorrent magnet uri
|
||||
|
||||
:param uri: the uri to check
|
||||
:type uri: string
|
||||
:param uri: string, the uri to check
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
|
||||
@@ -467,22 +359,40 @@ def is_magnet(uri):
|
||||
True
|
||||
|
||||
"""
|
||||
magnet_scheme = 'magnet:?'
|
||||
xt_param = 'xt=urn:btih:'
|
||||
if uri.startswith(magnet_scheme) and xt_param in uri:
|
||||
if uri[:20] == "magnet:?xt=urn:btih:":
|
||||
return True
|
||||
return False
|
||||
|
||||
def fetch_url(url):
|
||||
"""
|
||||
Downloads a torrent file from a given URL and checks the file's validity
|
||||
|
||||
:param url: string, the url of the .torrent file to fetch
|
||||
:returns: the filepath to the downloaded file
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
import urllib
|
||||
from deluge.log import LOG as log
|
||||
try:
|
||||
filename, headers = urllib.urlretrieve(url)
|
||||
except IOError:
|
||||
log.debug("Network error while trying to fetch torrent from %s", url)
|
||||
else:
|
||||
if filename.endswith(".torrent") or headers["content-type"] ==\
|
||||
"application/x-bittorrent":
|
||||
return filename
|
||||
else:
|
||||
log.debug("URL doesn't appear to be a valid torrent file: %s", url)
|
||||
return None
|
||||
|
||||
def create_magnet_uri(infohash, name=None, trackers=[]):
|
||||
"""
|
||||
Creates a magnet uri
|
||||
|
||||
:param infohash: the info-hash of the torrent
|
||||
:type infohash: string
|
||||
:param name: the name of the torrent (optional)
|
||||
:type name: string
|
||||
:param trackers: the trackers to announce to (optional)
|
||||
:type trackers: list of strings
|
||||
:param infohash: string, the info-hash of the torrent
|
||||
:param name: string, the name of the torrent (optional)
|
||||
:param trackers: list of strings, the trackers to announce to (optional)
|
||||
|
||||
:returns: a magnet uri string
|
||||
:rtype: string
|
||||
@@ -502,8 +412,7 @@ def get_path_size(path):
|
||||
"""
|
||||
Gets the size in bytes of 'path'
|
||||
|
||||
:param path: the path to check for size
|
||||
:type path: string
|
||||
:param path: string, the path to check for size
|
||||
:returns: the size in bytes of the path or -1 if the path does not exist
|
||||
:rtype: int
|
||||
|
||||
@@ -525,31 +434,26 @@ def free_space(path):
|
||||
"""
|
||||
Gets the free space available at 'path'
|
||||
|
||||
:param path: the path to check
|
||||
:type path: string
|
||||
:param path: string, the path to check
|
||||
:returns: the free space at path in bytes
|
||||
:rtype: int
|
||||
|
||||
:raises InvalidPathError: if the path is not valid
|
||||
|
||||
"""
|
||||
if not path or not os.path.exists(path):
|
||||
raise InvalidPathError("%s is not a valid path" % path)
|
||||
|
||||
if windows_check():
|
||||
from win32file import GetDiskFreeSpaceEx
|
||||
return GetDiskFreeSpaceEx(path)[0]
|
||||
import win32api
|
||||
drive = path.split(":")[0]
|
||||
free = win32api.GetDiskFreeSpaceEx(drive)[0]
|
||||
return free
|
||||
else:
|
||||
disk_data = os.statvfs(path.encode("utf8"))
|
||||
block_size = disk_data.f_frsize
|
||||
disk_data = os.statvfs(path)
|
||||
block_size = disk_data.f_bsize
|
||||
return disk_data.f_bavail * block_size
|
||||
|
||||
def is_ip(ip):
|
||||
"""
|
||||
A simple test to see if 'ip' is valid
|
||||
|
||||
:param ip: the ip to check
|
||||
:type ip: string
|
||||
:param ip: string, the ip to check
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
|
||||
@@ -562,186 +466,14 @@ def is_ip(ip):
|
||||
import socket
|
||||
#first we test ipv4
|
||||
try:
|
||||
if windows_check():
|
||||
if socket.inet_aton("%s" % (ip)):
|
||||
return True
|
||||
else:
|
||||
if socket.inet_pton(socket.AF_INET, "%s" % (ip)):
|
||||
return True
|
||||
if socket.inet_pton(socket.AF_INET, "%s" % (ip)):
|
||||
return True
|
||||
except socket.error:
|
||||
if not socket.has_ipv6:
|
||||
return False
|
||||
#now test ipv6
|
||||
try:
|
||||
if windows_check():
|
||||
log.warning("ipv6 check unavailable on windows")
|
||||
if socket.inet_pton(socket.AF_INET6, "%s" % (ip)):
|
||||
return True
|
||||
else:
|
||||
if socket.inet_pton(socket.AF_INET6, "%s" % (ip)):
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
def path_join(*parts):
|
||||
"""
|
||||
An implementation of os.path.join that always uses / for the separator
|
||||
to ensure that the correct paths are produced when working with internal
|
||||
paths on Windows.
|
||||
"""
|
||||
path = ''
|
||||
for part in parts:
|
||||
if not part:
|
||||
continue
|
||||
elif part[0] == '/':
|
||||
path = part
|
||||
elif not path:
|
||||
path = part
|
||||
else:
|
||||
path += '/' + part
|
||||
return path
|
||||
|
||||
XML_ESCAPES = (
|
||||
('&', '&'),
|
||||
('<', '<'),
|
||||
('>', '>'),
|
||||
('"', '"'),
|
||||
("'", ''')
|
||||
)
|
||||
|
||||
def xml_decode(string):
|
||||
"""
|
||||
Unescape a string that was previously encoded for use within xml.
|
||||
|
||||
:param string: The string to escape
|
||||
:type string: string
|
||||
:returns: The unescaped version of the string.
|
||||
:rtype: string
|
||||
"""
|
||||
for char, escape in XML_ESCAPES:
|
||||
string = string.replace(escape, char)
|
||||
return string
|
||||
|
||||
def xml_encode(string):
|
||||
"""
|
||||
Escape a string for use within an xml element or attribute.
|
||||
|
||||
:param string: The string to escape
|
||||
:type string: string
|
||||
:returns: An escaped version of the string.
|
||||
:rtype: string
|
||||
"""
|
||||
for char, escape in XML_ESCAPES:
|
||||
string = string.replace(char, escape)
|
||||
return string
|
||||
|
||||
def decode_string(s, encoding="utf8"):
|
||||
"""
|
||||
Decodes a string and return unicode. If it cannot decode using
|
||||
`:param:encoding` then it will try latin1, and if that fails,
|
||||
try to detect the string encoding. If that fails, decode with
|
||||
ignore.
|
||||
|
||||
:param s: string to decode
|
||||
:type s: string
|
||||
:keyword encoding: the encoding to use in the decoding
|
||||
:type encoding: string
|
||||
:returns: s converted to unicode
|
||||
:rtype: unicode
|
||||
|
||||
"""
|
||||
if not s:
|
||||
return u''
|
||||
elif isinstance(s, unicode):
|
||||
return s
|
||||
|
||||
encodings = [lambda: ("utf8", 'strict'),
|
||||
lambda: ("iso-8859-1", 'strict'),
|
||||
lambda: (chardet.detect(s)["encoding"], 'strict'),
|
||||
lambda: (encoding, 'ignore')]
|
||||
|
||||
if not encoding is "utf8":
|
||||
encodings.insert(0, lambda: (encoding, 'strict'))
|
||||
|
||||
for l in encodings:
|
||||
try:
|
||||
return s.decode(*l())
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return u''
|
||||
|
||||
def utf8_encoded(s, encoding="utf8"):
|
||||
"""
|
||||
Returns a utf8 encoded string of s
|
||||
|
||||
:param s: (unicode) string to (re-)encode
|
||||
:type s: basestring
|
||||
:keyword encoding: the encoding to use in the decoding
|
||||
:type encoding: string
|
||||
:returns: a utf8 encoded string of s
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
s = decode_string(s, encoding).encode("utf8")
|
||||
elif isinstance(s, unicode):
|
||||
s = s.encode("utf8")
|
||||
return s
|
||||
|
||||
class VersionSplit(object):
|
||||
"""
|
||||
Used for comparing version numbers.
|
||||
|
||||
:param ver: the version
|
||||
:type ver: string
|
||||
|
||||
"""
|
||||
def __init__(self, ver):
|
||||
ver = ver.lower()
|
||||
vs = ver.replace("_", "-").split("-")
|
||||
self.version = [int(x) for x in vs[0].split(".") if x.isdigit()]
|
||||
self.suffix = None
|
||||
self.dev = False
|
||||
if len(vs) > 1:
|
||||
if vs[1].startswith(("rc", "alpha", "beta")):
|
||||
self.suffix = vs[1]
|
||||
if vs[-1] == 'dev':
|
||||
self.dev = True
|
||||
|
||||
def __cmp__(self, ver):
|
||||
"""
|
||||
The comparison method.
|
||||
|
||||
:param ver: the version to compare with
|
||||
:type ver: VersionSplit
|
||||
|
||||
"""
|
||||
|
||||
# If there is no suffix we use z because we want final
|
||||
# to appear after alpha, beta, and rc alphabetically.
|
||||
v1 = [self.version, self.suffix or 'z', self.dev]
|
||||
v2 = [ver.version, ver.suffix or 'z', ver.dev]
|
||||
return cmp(v1, v2)
|
||||
|
||||
def win32_unicode_argv():
|
||||
""" Gets sys.argv as list of unicode objects on any platform."""
|
||||
if windows_check():
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on Windows, with the
|
||||
# underlying Windows API instead replacing multi-byte characters with '?'.
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
get_cmd_linew = cdll.kernel32.GetCommandLineW
|
||||
get_cmd_linew.argtypes = []
|
||||
get_cmd_linew.restype = LPCWSTR
|
||||
|
||||
cmdline_to_argvw = windll.shell32.CommandLineToArgvW
|
||||
cmdline_to_argvw.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
cmdline_to_argvw.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = get_cmd_linew()
|
||||
argc = c_int(0)
|
||||
argv = cmdline_to_argvw(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in xrange(start, argc.value)]
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# component.py
|
||||
#
|
||||
# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@@ -19,409 +19,203 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from twisted.internet.defer import maybeDeferred, succeed, DeferredList, fail
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
import gobject
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class ComponentAlreadyRegistered(Exception):
|
||||
pass
|
||||
COMPONENT_STATE = [
|
||||
"Stopped",
|
||||
"Started",
|
||||
"Paused"
|
||||
]
|
||||
|
||||
class Component(object):
|
||||
"""
|
||||
Component objects are singletons managed by the :class:`ComponentRegistry`.
|
||||
When a new Component object is instantiated, it will be automatically
|
||||
registered with the :class:`ComponentRegistry`.
|
||||
class Component:
|
||||
def __init__(self, name, interval=1000, depend=None):
|
||||
# Register with the ComponentRegistry
|
||||
register(name, self, depend)
|
||||
self._interval = interval
|
||||
self._timer = None
|
||||
self._state = COMPONENT_STATE.index("Stopped")
|
||||
|
||||
The ComponentRegistry has the ability to start, stop, pause and shutdown the
|
||||
components registered with it.
|
||||
|
||||
**Events:**
|
||||
|
||||
**start()** - This method is called when the client has connected to a
|
||||
Deluge core.
|
||||
|
||||
**stop()** - This method is called when the client has disconnected from a
|
||||
Deluge core.
|
||||
|
||||
**update()** - This method is called every 1 second by default while the
|
||||
Componented is in a *Started* state. The interval can be
|
||||
specified during instantiation. The update() timer can be
|
||||
paused by instructing the :class:`ComponentRegistry` to pause
|
||||
this Component.
|
||||
|
||||
**shutdown()** - This method is called when the client is exiting. If the
|
||||
Component is in a "Started" state when this is called, a
|
||||
call to stop() will be issued prior to shutdown().
|
||||
|
||||
**States:**
|
||||
|
||||
A Component can be in one of these 5 states.
|
||||
|
||||
**Started** - The Component has been started by the :class:`ComponentRegistry`
|
||||
and will have it's update timer started.
|
||||
|
||||
**Starting** - The Component has had it's start method called, but it hasn't
|
||||
fully started yet.
|
||||
|
||||
**Stopped** - The Component has either been stopped or has yet to be started.
|
||||
|
||||
**Stopping** - The Component has had it's stop method called, but it hasn't
|
||||
fully stopped yet.
|
||||
|
||||
**Paused** - The Component has had it's update timer stopped, but will
|
||||
still be considered in a Started state.
|
||||
|
||||
"""
|
||||
def __init__(self, name, interval=1, depend=None):
|
||||
self._component_name = name
|
||||
self._component_interval = interval
|
||||
self._component_depend = depend
|
||||
self._component_state = "Stopped"
|
||||
self._component_timer = None
|
||||
self._component_starting_deferred = None
|
||||
self._component_stopping_deferred = None
|
||||
_ComponentRegistry.register(self)
|
||||
|
||||
def __del__(self):
|
||||
if _ComponentRegistry:
|
||||
_ComponentRegistry.deregister(self._component_name)
|
||||
|
||||
def _component_start_timer(self):
|
||||
if hasattr(self, "update"):
|
||||
self._component_timer = LoopingCall(self.update)
|
||||
self._component_timer.start(self._component_interval)
|
||||
|
||||
def _component_start(self):
|
||||
def on_start(result):
|
||||
self._component_state = "Started"
|
||||
self._component_starting_deferred = None
|
||||
self._component_start_timer()
|
||||
return True
|
||||
|
||||
def on_start_fail(result):
|
||||
self._component_state = "Stopped"
|
||||
self._component_starting_deferred = None
|
||||
log.error(result)
|
||||
return result
|
||||
|
||||
if self._component_state == "Stopped":
|
||||
if hasattr(self, "start"):
|
||||
self._component_state = "Starting"
|
||||
d = maybeDeferred(self.start)
|
||||
d.addCallback(on_start)
|
||||
d.addErrback(on_start_fail)
|
||||
self._component_starting_deferred = d
|
||||
else:
|
||||
d = maybeDeferred(on_start, None)
|
||||
elif self._component_state == "Starting":
|
||||
return self._component_starting_deferred
|
||||
elif self._component_state == "Started":
|
||||
d = succeed(True)
|
||||
else:
|
||||
d = fail("Cannot start a component not in a Stopped state!")
|
||||
|
||||
return d
|
||||
|
||||
def _component_stop(self):
|
||||
def on_stop(result):
|
||||
self._component_state = "Stopped"
|
||||
if self._component_timer and self._component_timer.running:
|
||||
self._component_timer.stop()
|
||||
return True
|
||||
|
||||
def on_stop_fail(result):
|
||||
self._component_state = "Started"
|
||||
self._component_stopping_deferred = None
|
||||
log.error(result)
|
||||
return result
|
||||
|
||||
if self._component_state != "Stopped" and self._component_state != "Stopping":
|
||||
if hasattr(self, "stop"):
|
||||
self._component_state = "Stopping"
|
||||
d = maybeDeferred(self.stop)
|
||||
d.addCallback(on_stop)
|
||||
d.addErrback(on_stop_fail)
|
||||
self._component_stopping_deferred = d
|
||||
else:
|
||||
d = maybeDeferred(on_stop, None)
|
||||
|
||||
if self._component_state == "Stopping":
|
||||
return self._component_stopping_deferred
|
||||
|
||||
return succeed(None)
|
||||
|
||||
def _component_pause(self):
|
||||
def on_pause(result):
|
||||
self._component_state = "Paused"
|
||||
|
||||
if self._component_state == "Started":
|
||||
if self._component_timer and self._component_timer.running:
|
||||
d = maybeDeferred(self._component_timer.stop)
|
||||
d.addCallback(on_pause)
|
||||
else:
|
||||
d = succeed(None)
|
||||
elif self._component_state == "Paused":
|
||||
d = succeed(None)
|
||||
else:
|
||||
d = fail("Cannot pause a component in a non-Started state!")
|
||||
|
||||
return d
|
||||
|
||||
def _component_resume(self):
|
||||
def on_resume(result):
|
||||
self._component_state = "Started"
|
||||
|
||||
if self._component_state == "Paused":
|
||||
d = maybeDeferred(self._component_start_timer)
|
||||
d.addCallback(on_resume)
|
||||
else:
|
||||
d = fail("Component cannot be resumed from a non-Paused state!")
|
||||
|
||||
return d
|
||||
|
||||
def _component_shutdown(self):
|
||||
def on_stop(result):
|
||||
if hasattr(self, "shutdown"):
|
||||
return maybeDeferred(self.shutdown)
|
||||
return succeed(None)
|
||||
|
||||
d = self._component_stop()
|
||||
d.addCallback(on_stop)
|
||||
return d
|
||||
def get_state(self):
|
||||
return self._state
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def _start(self):
|
||||
self._state = COMPONENT_STATE.index("Started")
|
||||
if self._update():
|
||||
self._timer = gobject.timeout_add(self._interval, self._update)
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
def _stop(self):
|
||||
self._state = COMPONENT_STATE.index("Stopped")
|
||||
try:
|
||||
gobject.source_remove(self._timer)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _pause(self):
|
||||
self._state = COMPONENT_STATE.index("Paused")
|
||||
try:
|
||||
gobject.source_remove(self._timer)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _resume(self):
|
||||
self._start()
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
class ComponentRegistry(object):
|
||||
"""
|
||||
The ComponentRegistry holds a list of currently registered
|
||||
:class:`Component` objects. It is used to manage the Components by
|
||||
starting, stopping, pausing and shutting them down.
|
||||
"""
|
||||
def _update(self):
|
||||
try:
|
||||
self.update()
|
||||
except AttributeError:
|
||||
# This will stop the timer since the component doesn't have an
|
||||
# update method.
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ComponentRegistry:
|
||||
def __init__(self):
|
||||
self.components = {}
|
||||
self.depend = {}
|
||||
|
||||
def register(self, obj):
|
||||
"""
|
||||
Registers a component object with the registry. This is done
|
||||
automatically when a Component object is instantiated.
|
||||
def register(self, name, obj, depend):
|
||||
"""Registers a component.. depend must be list or None"""
|
||||
log.debug("Registered %s with ComponentRegistry..", name)
|
||||
self.components[name] = obj
|
||||
if depend != None:
|
||||
self.depend[name] = depend
|
||||
|
||||
:param obj: the Component object
|
||||
:type obj: object
|
||||
def get(self, name):
|
||||
"""Returns a reference to the component 'name'"""
|
||||
return self.components[name]
|
||||
|
||||
:raises ComponentAlreadyRegistered: if a component with the same name is already registered.
|
||||
def start(self):
|
||||
"""Starts all components"""
|
||||
for component in self.components.keys():
|
||||
self.start_component(component)
|
||||
|
||||
"""
|
||||
name = obj._component_name
|
||||
if name in self.components:
|
||||
raise ComponentAlreadyRegistered(
|
||||
"Component already registered with name %s" % name)
|
||||
def start_component(self, name):
|
||||
"""Starts a component"""
|
||||
# Check to see if this component has any dependencies
|
||||
if self.depend.has_key(name):
|
||||
for depend in self.depend[name]:
|
||||
self.start_component(depend)
|
||||
# Only start if the component is stopped.
|
||||
if self.components[name].get_state() == \
|
||||
COMPONENT_STATE.index("Stopped"):
|
||||
log.debug("Starting component %s..", name)
|
||||
self.components[name].start()
|
||||
self.components[name]._start()
|
||||
|
||||
self.components[obj._component_name] = obj
|
||||
def stop(self):
|
||||
"""Stops all components"""
|
||||
for component in self.components.keys():
|
||||
self.stop_component(component)
|
||||
|
||||
def deregister(self, name):
|
||||
"""
|
||||
Deregisters a component from the registry. A stop will be
|
||||
issued to the component prior to deregistering it.
|
||||
def stop_component(self, component):
|
||||
if self.components[component].get_state() != \
|
||||
COMPONENT_STATE.index("Stopped"):
|
||||
log.debug("Stopping component %s..", component)
|
||||
self.components[component].stop()
|
||||
self.components[component]._stop()
|
||||
|
||||
:param name: the name of the component
|
||||
:type name: string
|
||||
def pause(self):
|
||||
"""Pauses all components. Stops calling update()"""
|
||||
for component in self.components.keys():
|
||||
self.pause_component(component)
|
||||
|
||||
"""
|
||||
def pause_component(self, component):
|
||||
if self.components[component].get_state() not in \
|
||||
[COMPONENT_STATE.index("Paused"), COMPONENT_STATE.index("Stopped")]:
|
||||
log.debug("Pausing component %s..", component)
|
||||
self.components[component]._pause()
|
||||
|
||||
if name in self.components:
|
||||
log.debug("Deregistering Component: %s", name)
|
||||
d = self.stop([name])
|
||||
def on_stop(result, name):
|
||||
del self.components[name]
|
||||
return d.addCallback(on_stop, name)
|
||||
else:
|
||||
return succeed(None)
|
||||
def resume(self):
|
||||
"""Resumes all components. Starts calling update()"""
|
||||
for component in self.components.keys():
|
||||
self.resume_component(component)
|
||||
|
||||
def start(self, names=[]):
|
||||
"""
|
||||
Starts Components that are currently in a Stopped state and their
|
||||
dependencies. If *names* is specified, will only start those
|
||||
Components and their dependencies and if not it will start all
|
||||
registered components.
|
||||
|
||||
:param names: a list of Components to start
|
||||
:type names: list
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully started
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
# Start all the components if names is empty
|
||||
if not names:
|
||||
names = self.components.keys()
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
def on_depends_started(result, name):
|
||||
return self.components[name]._component_start()
|
||||
|
||||
deferreds = []
|
||||
|
||||
for name in names:
|
||||
if self.components[name]._component_depend:
|
||||
# This component has depends, so we need to start them first.
|
||||
d = self.start(self.components[name]._component_depend)
|
||||
d.addCallback(on_depends_started, name)
|
||||
deferreds.append(d)
|
||||
else:
|
||||
deferreds.append(self.components[name]._component_start())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
|
||||
def stop(self, names=[]):
|
||||
"""
|
||||
Stops Components that are currently not in a Stopped state. If
|
||||
*names* is specified, then it will only stop those Components,
|
||||
and if not it will stop all the registered Components.
|
||||
|
||||
:param names: a list of Components to start
|
||||
:type names: list
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully stopped
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
if not names:
|
||||
names = self.components.keys()
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
deferreds = []
|
||||
|
||||
for name in names:
|
||||
if name in self.components:
|
||||
deferreds.append(self.components[name]._component_stop())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
|
||||
def pause(self, names=[]):
|
||||
"""
|
||||
Pauses Components that are currently in a Started state. If
|
||||
*names* is specified, then it will only pause those Components,
|
||||
and if not it will pause all the registered Components.
|
||||
|
||||
:param names: a list of Components to pause
|
||||
:type names: list
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully paused
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
if not names:
|
||||
names = self.components.keys()
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
deferreds = []
|
||||
|
||||
for name in names:
|
||||
if self.components[name]._component_state == "Started":
|
||||
deferreds.append(self.components[name]._component_pause())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
|
||||
def resume(self, names=[]):
|
||||
"""
|
||||
Resumes Components that are currently in a Paused state. If
|
||||
*names* is specified, then it will only resume those Components,
|
||||
and if not it will resume all the registered Components.
|
||||
|
||||
:param names: a list of Components to resume
|
||||
:type names: list
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully resumed
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
if not names:
|
||||
names = self.components.keys()
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
deferreds = []
|
||||
|
||||
for name in names:
|
||||
if self.components[name]._component_state == "Paused":
|
||||
deferreds.append(self.components[name]._component_resume())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdowns all Components regardless of state. This will call
|
||||
:meth:`stop` on call the components prior to shutting down. This should
|
||||
be called when the program is exiting to ensure all Components have a
|
||||
chance to properly shutdown.
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully resumed
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
deferreds = []
|
||||
|
||||
for component in self.components.values():
|
||||
deferreds.append(component._component_shutdown())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
def resume_component(self, component):
|
||||
if self.components[component].get_state() == COMPONENT_STATE.index("Paused"):
|
||||
log.debug("Resuming component %s..", component)
|
||||
self.components[component]._resume()
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Updates all Components that are in a Started state.
|
||||
"""Updates all components"""
|
||||
for component in self.components.keys():
|
||||
# Only update the component if it's started
|
||||
if self.components[component].get_state() == \
|
||||
COMPONENT_STATE.index("Started"):
|
||||
self.components[component].update()
|
||||
|
||||
return True
|
||||
|
||||
def shutdown(self):
|
||||
"""Shuts down all components. This should be called when the program
|
||||
exits so that components can do any necessary clean-up."""
|
||||
# Stop all components first
|
||||
self.stop()
|
||||
for component in self.components.keys():
|
||||
log.debug("Shutting down component %s..", component)
|
||||
try:
|
||||
self.components[component].shutdown()
|
||||
except Exception, e:
|
||||
log.debug("Unable to call shutdown(): %s", e)
|
||||
|
||||
"""
|
||||
for component in self.components.items():
|
||||
component.update()
|
||||
|
||||
_ComponentRegistry = ComponentRegistry()
|
||||
|
||||
deregister = _ComponentRegistry.deregister
|
||||
start = _ComponentRegistry.start
|
||||
stop = _ComponentRegistry.stop
|
||||
pause = _ComponentRegistry.pause
|
||||
resume = _ComponentRegistry.resume
|
||||
update = _ComponentRegistry.update
|
||||
shutdown = _ComponentRegistry.shutdown
|
||||
def register(name, obj, depend=None):
|
||||
"""Registers a component with the registry"""
|
||||
_ComponentRegistry.register(name, obj, depend)
|
||||
|
||||
def get(name):
|
||||
"""
|
||||
Return a reference to a component.
|
||||
def start(component=None):
|
||||
"""Starts all components"""
|
||||
if component == None:
|
||||
_ComponentRegistry.start()
|
||||
else:
|
||||
_ComponentRegistry.start_component(component)
|
||||
|
||||
:param name: the Component name to get
|
||||
:type name: string
|
||||
def stop(component=None):
|
||||
"""Stops all or specified components"""
|
||||
if component == None:
|
||||
_ComponentRegistry.stop()
|
||||
else:
|
||||
_ComponentRegistry.stop_component(component)
|
||||
|
||||
:returns: the Component object
|
||||
:rtype: object
|
||||
def pause(component=None):
|
||||
"""Pauses all or specificed components"""
|
||||
if component == None:
|
||||
_ComponentRegistry.pause()
|
||||
else:
|
||||
_ComponentRegistry.pause_component(component)
|
||||
|
||||
:raises KeyError: if the Component does not exist
|
||||
def resume(component=None):
|
||||
"""Resumes all or specificed components"""
|
||||
if component == None:
|
||||
_ComponentRegistry.resume()
|
||||
else:
|
||||
_ComponentRegistry.resume_component(component)
|
||||
|
||||
"""
|
||||
return _ComponentRegistry.components[name]
|
||||
def update():
|
||||
"""Updates all components"""
|
||||
_ComponentRegistry.update()
|
||||
|
||||
def shutdown():
|
||||
"""Shutdowns all components"""
|
||||
_ComponentRegistry.shutdown()
|
||||
|
||||
def get(component):
|
||||
"""Return a reference to the component"""
|
||||
return _ComponentRegistry.get(component)
|
||||
|
288
deluge/config.py
@@ -19,65 +19,21 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
"""
|
||||
Deluge Config Module
|
||||
|
||||
This module is used for loading and saving of configuration files.. or anything
|
||||
really.
|
||||
|
||||
The format of the config file is two json encoded dicts:
|
||||
|
||||
<version dict>
|
||||
<content dict>
|
||||
|
||||
The version dict contains two keys: file and format. The format version is
|
||||
controlled by the Config class. It should only be changed when anything below
|
||||
it is changed directly by the Config class. An example of this would be if we
|
||||
changed the serializer for the content to something different.
|
||||
|
||||
The config file version is changed by the 'owner' of the config file. This is
|
||||
to signify that there is a change in the naming of some config keys or something
|
||||
similar along those lines.
|
||||
|
||||
The content is simply the dict to be saved and will be serialized before being
|
||||
written.
|
||||
|
||||
Converting
|
||||
|
||||
Since the format of the config could change, there needs to be a way to have
|
||||
the Config object convert to newer formats. To do this, you will need to
|
||||
register conversion functions for various versions of the config file. Note that
|
||||
this can only be done for the 'config file version' and not for the 'format'
|
||||
version as this will be done internally.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import cPickle as pickle
|
||||
import shutil
|
||||
import os
|
||||
|
||||
import gobject
|
||||
import deluge.common
|
||||
from deluge.log import LOG as log
|
||||
|
||||
json = deluge.common.json
|
||||
|
||||
def prop(func):
|
||||
"""Function decorator for defining property attributes
|
||||
|
||||
@@ -92,42 +48,6 @@ def prop(func):
|
||||
"""
|
||||
return property(doc=func.__doc__, **func())
|
||||
|
||||
def find_json_objects(s):
|
||||
"""
|
||||
Find json objects in a string.
|
||||
|
||||
:param s: the string to find json objects in
|
||||
:type s: string
|
||||
|
||||
:returns: a list of tuples containing start and end locations of json objects in the string `s`
|
||||
:rtype: [(start, end), ...]
|
||||
|
||||
"""
|
||||
objects = []
|
||||
opens = 0
|
||||
start = s.find("{")
|
||||
offset = start
|
||||
|
||||
if start < 0:
|
||||
return []
|
||||
|
||||
quoted = False
|
||||
for index, c in enumerate(s[offset:]):
|
||||
if c == '"':
|
||||
quoted = not quoted
|
||||
elif quoted:
|
||||
continue
|
||||
elif c == "{":
|
||||
opens += 1
|
||||
elif c == "}":
|
||||
opens -= 1
|
||||
if opens == 0:
|
||||
objects.append((start, index+offset+1))
|
||||
start = index + offset + 1
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""
|
||||
This class is used to access/create/modify config files
|
||||
@@ -139,22 +59,15 @@ class Config(object):
|
||||
"""
|
||||
def __init__(self, filename, defaults=None, config_dir=None):
|
||||
self.__config = {}
|
||||
self.__previous_config = {}
|
||||
self.__set_functions = {}
|
||||
self.__change_callbacks = []
|
||||
|
||||
# These hold the version numbers and they will be set when loaded
|
||||
self.__version = {
|
||||
"format": 1,
|
||||
"file": 1
|
||||
}
|
||||
|
||||
# This will get set with a reactor.callLater whenever a config option
|
||||
self.__change_callback = None
|
||||
# This will get set with a gobject.timeout_add whenever a config option
|
||||
# is set.
|
||||
self._save_timer = None
|
||||
self.__save_timer = None
|
||||
|
||||
if defaults:
|
||||
for key, value in defaults.iteritems():
|
||||
self.set_item(key, value)
|
||||
self.__config = defaults
|
||||
|
||||
# Load the config from file in the config_dir
|
||||
if config_dir:
|
||||
@@ -164,9 +77,6 @@ class Config(object):
|
||||
|
||||
self.load()
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.__config
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""
|
||||
See
|
||||
@@ -178,14 +88,12 @@ class Config(object):
|
||||
def set_item(self, key, value):
|
||||
"""
|
||||
Sets item 'key' to 'value' in the config dictionary, but does not allow
|
||||
changing the item's type unless it is None. If the types do not match,
|
||||
it will attempt to convert it to the set type before raising a ValueError.
|
||||
changing the item's type unless it is None
|
||||
|
||||
:param key: string, item to change to change
|
||||
:param value: the value to change item to, must be same type as what is currently in the config
|
||||
|
||||
:raises ValueError: raised when the type of value is not the same as\
|
||||
what is currently in the config and it could not convert the value
|
||||
:raises ValueError: raised when the type of value is not the same as what is currently in the config
|
||||
|
||||
**Usage**
|
||||
|
||||
@@ -195,10 +103,6 @@ what is currently in the config and it could not convert the value
|
||||
5
|
||||
|
||||
"""
|
||||
if isinstance(value, basestring):
|
||||
value = deluge.common.utf8_encoded(value)
|
||||
|
||||
|
||||
if not self.__config.has_key(key):
|
||||
self.__config[key] = value
|
||||
log.debug("Setting '%s' to %s of %s", key, value, type(value))
|
||||
@@ -212,35 +116,29 @@ what is currently in the config and it could not convert the value
|
||||
|
||||
if value is not None and oldtype != type(None) and oldtype != newtype:
|
||||
try:
|
||||
if oldtype == unicode:
|
||||
value = oldtype(value, "utf8")
|
||||
else:
|
||||
value = oldtype(value)
|
||||
value = oldtype(value)
|
||||
except ValueError:
|
||||
log.warning("Type '%s' invalid for '%s'", newtype, key)
|
||||
raise
|
||||
|
||||
log.debug("Setting '%s' to %s of %s", key, value, type(value))
|
||||
|
||||
# Make a copy of the current config prior to changing it
|
||||
self.__previous_config.update(self.__config)
|
||||
self.__config[key] = value
|
||||
# Run the set_function for this key if any
|
||||
from twisted.internet import reactor
|
||||
try:
|
||||
for func in self.__set_functions[key]:
|
||||
reactor.callLater(0, func, key, value)
|
||||
gobject.idle_add(self.__set_functions[key], key, value)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
def do_change_callbacks(key, value):
|
||||
for func in self.__change_callbacks:
|
||||
func(key, value)
|
||||
reactor.callLater(0, do_change_callbacks, key, value)
|
||||
gobject.idle_add(self.__change_callback, key, value)
|
||||
except:
|
||||
pass
|
||||
|
||||
# We set the save_timer for 5 seconds if not already set
|
||||
if not self._save_timer or not self._save_timer.active():
|
||||
self._save_timer = reactor.callLater(5, self.save)
|
||||
if not self.__save_timer:
|
||||
self.__save_timer = gobject.timeout_add(5000, self.save)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
@@ -265,13 +163,7 @@ what is currently in the config and it could not convert the value
|
||||
5
|
||||
|
||||
"""
|
||||
if isinstance(self.__config[key], str):
|
||||
try:
|
||||
return self.__config[key].decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
return self.__config[key]
|
||||
else:
|
||||
return self.__config[key]
|
||||
return self.__config[key]
|
||||
|
||||
def register_change_callback(self, callback):
|
||||
"""
|
||||
@@ -288,7 +180,7 @@ what is currently in the config and it could not convert the value
|
||||
>>> config.register_change_callback(cb)
|
||||
|
||||
"""
|
||||
self.__change_callbacks.append(callback)
|
||||
self.__change_callback = callback
|
||||
|
||||
def register_set_function(self, key, function, apply_now=True):
|
||||
"""
|
||||
@@ -309,14 +201,10 @@ what is currently in the config and it could not convert the value
|
||||
|
||||
"""
|
||||
log.debug("Registering function for %s key..", key)
|
||||
if key not in self.__set_functions:
|
||||
self.__set_functions[key] = []
|
||||
|
||||
self.__set_functions[key].append(function)
|
||||
|
||||
self.__set_functions[key] = function
|
||||
# Run the function now if apply_now is set
|
||||
if apply_now:
|
||||
function(key, self.__config[key])
|
||||
self.__set_functions[key](key, self.__config[key])
|
||||
return
|
||||
|
||||
def apply_all(self):
|
||||
@@ -336,20 +224,7 @@ what is currently in the config and it could not convert the value
|
||||
"""
|
||||
log.debug("Calling all set functions..")
|
||||
for key, value in self.__set_functions.iteritems():
|
||||
for func in value:
|
||||
func(key, self.__config[key])
|
||||
|
||||
def apply_set_functions(self, key):
|
||||
"""
|
||||
Calls set functions for `:param:key`.
|
||||
|
||||
:param key: str, the config key
|
||||
|
||||
"""
|
||||
log.debug("Calling set functions for key %s..", key)
|
||||
if key in self.__set_functions:
|
||||
for func in self.__set_functions[key]:
|
||||
func(key, self.__config[key])
|
||||
value(key, self.__config[key])
|
||||
|
||||
def load(self, filename=None):
|
||||
"""
|
||||
@@ -361,50 +236,18 @@ what is currently in the config and it could not convert the value
|
||||
"""
|
||||
if not filename:
|
||||
filename = self.__config_file
|
||||
|
||||
try:
|
||||
with open(filename, "rb") as _file:
|
||||
data = _file.read()
|
||||
except IOError, e:
|
||||
log.warning("Unable to open config file %s: %s", filename, e)
|
||||
return
|
||||
self.__config.update(pickle.load(open(filename, "rb")))
|
||||
except Exception, e:
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
|
||||
objects = find_json_objects(data)
|
||||
|
||||
if not len(objects):
|
||||
# No json objects found, try depickling it
|
||||
try:
|
||||
self.__config.update(pickle.loads(data))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
elif len(objects) == 1:
|
||||
start, end = objects[0]
|
||||
try:
|
||||
self.__config.update(json.loads(data[start:end]))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
elif len(objects) == 2:
|
||||
try:
|
||||
start, end = objects[0]
|
||||
self.__version.update(json.loads(data[start:end]))
|
||||
start, end = objects[1]
|
||||
self.__config.update(json.loads(data[start:end]))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
|
||||
log.debug("Config %s version: %s.%s loaded: %s", filename,
|
||||
self.__version["format"], self.__version["file"], self.__config)
|
||||
log.debug("Config %s loaded: %s", filename, self.__config)
|
||||
|
||||
def save(self, filename=None):
|
||||
"""
|
||||
Save configuration to disk
|
||||
|
||||
:param filename: if None, uses filename set in object initiliazation
|
||||
:rtype bool:
|
||||
:return: whether or not the save succeeded.
|
||||
|
||||
"""
|
||||
if not filename:
|
||||
@@ -412,40 +255,21 @@ what is currently in the config and it could not convert the value
|
||||
# Check to see if the current config differs from the one on disk
|
||||
# We will only write a new config file if there is a difference
|
||||
try:
|
||||
with open(filename, "rb") as _file:
|
||||
data = _file.read()
|
||||
objects = find_json_objects(data)
|
||||
start, end = objects[0]
|
||||
version = json.loads(data[start:end])
|
||||
start, end = objects[1]
|
||||
loaded_data = json.loads(data[start:end])
|
||||
if self.__config == loaded_data and self.__version == version:
|
||||
if self.__config == pickle.load(open(filename, "rb")):
|
||||
# The config has not changed so lets just return
|
||||
if self._save_timer and self._save_timer.active():
|
||||
self._save_timer.cancel()
|
||||
return True
|
||||
except (IOError, IndexError), e:
|
||||
log.warning("Unable to open config file: %s because: %s", filename, e)
|
||||
self.__save_timer = None
|
||||
return
|
||||
except Exception, e:
|
||||
log.warning("Unable to open config file: %s", filename)
|
||||
|
||||
self.__save_timer = None
|
||||
|
||||
# Save the new config and make sure it's written to disk
|
||||
try:
|
||||
log.debug("Saving new config file %s", filename + ".new")
|
||||
f = open(filename + ".new", "wb")
|
||||
json.dump(self.__version, f, indent=2)
|
||||
json.dump(self.__config, f, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
f.close()
|
||||
except IOError, e:
|
||||
log.error("Error writing new config file: %s", e)
|
||||
return False
|
||||
|
||||
# Make a backup of the old config
|
||||
try:
|
||||
log.debug("Backing up old config file to %s~", filename)
|
||||
shutil.move(filename, filename + "~")
|
||||
pickle.dump(self.__config, open(filename + ".new", "wb"))
|
||||
except Exception, e:
|
||||
log.warning("Unable to backup old config...")
|
||||
log.error("Error writing new config file: %s", e)
|
||||
return
|
||||
|
||||
# The new config file has been written successfully, so let's move it over
|
||||
# the existing one.
|
||||
@@ -454,50 +278,8 @@ what is currently in the config and it could not convert the value
|
||||
shutil.move(filename + ".new", filename)
|
||||
except Exception, e:
|
||||
log.error("Error moving new config file: %s", e)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
finally:
|
||||
if self._save_timer and self._save_timer.active():
|
||||
self._save_timer.cancel()
|
||||
|
||||
def run_converter(self, input_range, output_version, func):
|
||||
"""
|
||||
Runs a function that will convert file versions in the `:param:input_range`
|
||||
to the `:param:output_version`.
|
||||
|
||||
:param input_range: tuple, (int, int) the range of input versions this
|
||||
function will accept
|
||||
:param output_version: int, the version this function will return
|
||||
:param func: func, the function that will do the conversion, it will take
|
||||
the config dict as an argument and return the augmented dict
|
||||
|
||||
:raises ValueError: if the output_version is less than the input_range
|
||||
|
||||
"""
|
||||
if output_version in input_range or output_version <= max(input_range):
|
||||
raise ValueError("output_version needs to be greater than input_range")
|
||||
|
||||
if self.__version["file"] not in input_range:
|
||||
log.debug("File version %s is not in input_range %s, ignoring converter function..",
|
||||
self.__version["file"], input_range)
|
||||
return
|
||||
|
||||
try:
|
||||
self.__config = func(self.__config)
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.error("There was an exception try to convert config file %s %s to %s",
|
||||
self.__config_file, self.__version["file"], output_version)
|
||||
raise e
|
||||
else:
|
||||
self.__version["file"] = output_version
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def config_file(self):
|
||||
return self.__config_file
|
||||
|
||||
@prop
|
||||
def config():
|
||||
"""The config dictionary"""
|
||||
|
@@ -19,21 +19,13 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
import gobject
|
||||
import os
|
||||
import os.path
|
||||
|
||||
import deluge.common
|
||||
from deluge.log import LOG as log
|
||||
@@ -43,51 +35,27 @@ class _ConfigManager:
|
||||
def __init__(self):
|
||||
log.debug("ConfigManager started..")
|
||||
self.config_files = {}
|
||||
self.__config_directory = None
|
||||
|
||||
@property
|
||||
def config_directory(self):
|
||||
if self.__config_directory is None:
|
||||
self.__config_directory = deluge.common.get_default_config_dir()
|
||||
return self.__config_directory
|
||||
self.config_directory = deluge.common.get_default_config_dir()
|
||||
# Set a 5 minute timer to call save()
|
||||
gobject.timeout_add(300000, self.save)
|
||||
|
||||
def __del__(self):
|
||||
log.debug("ConfigManager stopping..")
|
||||
del self.config_files
|
||||
|
||||
def set_config_dir(self, directory):
|
||||
"""
|
||||
Sets the config directory.
|
||||
|
||||
:param directory: str, the directory where the config info should be
|
||||
|
||||
:returns bool: True if successfully changed directory, False if not
|
||||
"""
|
||||
|
||||
if not directory:
|
||||
return False
|
||||
|
||||
"""Sets the config directory"""
|
||||
if directory == None:
|
||||
return
|
||||
log.info("Setting config directory to: %s", directory)
|
||||
if not os.path.exists(directory):
|
||||
# Try to create the config folder if it doesn't exist
|
||||
try:
|
||||
os.makedirs(directory)
|
||||
except Exception, e:
|
||||
log.error("Unable to make config directory: %s", e)
|
||||
return False
|
||||
elif not os.path.isdir(directory):
|
||||
log.error("Config directory needs to be a directory!")
|
||||
return False
|
||||
log.warning("Unable to make config directory: %s", e)
|
||||
|
||||
self.__config_directory = directory
|
||||
|
||||
# Reset the config_files so we don't get config from old config folder
|
||||
# XXX: Probably should have it go through the config_files dict and try
|
||||
# to reload based on the new config directory
|
||||
self.save()
|
||||
self.config_files = {}
|
||||
|
||||
return True
|
||||
self.config_directory = directory
|
||||
|
||||
def get_config_dir(self):
|
||||
return self.config_directory
|
||||
@@ -101,8 +69,8 @@ class _ConfigManager:
|
||||
|
||||
def save(self):
|
||||
"""Saves all the configs to disk."""
|
||||
for value in self.config_files.values():
|
||||
value.save()
|
||||
for key in self.config_files.keys():
|
||||
self.config_files[key].save()
|
||||
# We need to return True to keep the timer active
|
||||
return True
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# alertmanager.py
|
||||
#
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@@ -19,75 +19,54 @@
|
||||
# along with deluge. 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.
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
The AlertManager handles all the libtorrent alerts.
|
||||
"""The AlertManager handles all the libtorrent alerts."""
|
||||
|
||||
This should typically only be used by the Core. Plugins should utilize the
|
||||
`:mod:EventManager` for similar functionality.
|
||||
|
||||
"""
|
||||
|
||||
from twisted.internet import reactor
|
||||
import gobject
|
||||
|
||||
import deluge.component as component
|
||||
from deluge._libtorrent import lt
|
||||
try:
|
||||
import deluge.libtorrent as lt
|
||||
except ImportError:
|
||||
import libtorrent as lt
|
||||
if not (lt.version_major == 0 and lt.version_minor == 14):
|
||||
raise ImportError("This version of Deluge requires libtorrent 0.14!")
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class AlertManager(component.Component):
|
||||
def __init__(self):
|
||||
def __init__(self, session):
|
||||
log.debug("AlertManager initialized..")
|
||||
component.Component.__init__(self, "AlertManager", interval=0.3)
|
||||
self.session = component.get("Core").session
|
||||
|
||||
component.Component.__init__(self, "AlertManager", interval=50)
|
||||
self.session = session
|
||||
self.session.set_alert_mask(
|
||||
lt.alert.category_t.error_notification |
|
||||
lt.alert.category_t.port_mapping_notification |
|
||||
lt.alert.category_t.storage_notification |
|
||||
lt.alert.category_t.tracker_notification |
|
||||
lt.alert.category_t.status_notification |
|
||||
lt.alert.category_t.ip_block_notification |
|
||||
lt.alert.category_t.performance_warning)
|
||||
lt.alert.category_t.ip_block_notification)
|
||||
|
||||
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
|
||||
self.handlers = {}
|
||||
|
||||
self.delayed_calls = []
|
||||
|
||||
def update(self):
|
||||
self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()]
|
||||
self.handle_alerts()
|
||||
|
||||
def stop(self):
|
||||
for dc in self.delayed_calls:
|
||||
if dc.active():
|
||||
dc.cancel()
|
||||
self.delayed_calls = []
|
||||
def shutdown(self):
|
||||
del self.session
|
||||
del self.handlers
|
||||
|
||||
def register_handler(self, alert_type, handler):
|
||||
"""Registers a function that will be called when 'alert_type' is pop'd
|
||||
in handle_alerts. The handler function should look like:
|
||||
handler(alert)
|
||||
Where 'alert' is the actual alert object from libtorrent
|
||||
"""
|
||||
Registers a function that will be called when 'alert_type' is pop'd
|
||||
in handle_alerts. The handler function should look like: handler(alert)
|
||||
Where 'alert' is the actual alert object from libtorrent.
|
||||
|
||||
:param alert_type: str, this is string representation of the alert name
|
||||
:param handler: func(alert), the function to be called when the alert is raised
|
||||
"""
|
||||
if alert_type not in self.handlers:
|
||||
if alert_type not in self.handlers.keys():
|
||||
# There is no entry for this alert type yet, so lets make it with an
|
||||
# empty list.
|
||||
self.handlers[alert_type] = []
|
||||
@@ -97,37 +76,32 @@ class AlertManager(component.Component):
|
||||
log.debug("Registered handler for alert %s", alert_type)
|
||||
|
||||
def deregister_handler(self, handler):
|
||||
"""
|
||||
De-registers the `:param:handler` function from all alert types.
|
||||
|
||||
:param handler: func, the handler function to deregister
|
||||
"""
|
||||
"""De-registers the 'handler' function from all alert types."""
|
||||
# Iterate through all handlers and remove 'handler' where found
|
||||
for (key, value) in self.handlers.items():
|
||||
for (key, value) in self.handlers:
|
||||
if handler in value:
|
||||
# Handler is in this alert type list
|
||||
value.remove(handler)
|
||||
|
||||
def handle_alerts(self, wait=False):
|
||||
"""
|
||||
Pops all libtorrent alerts in the session queue and handles them
|
||||
appropriately.
|
||||
|
||||
:param wait: bool, if True then the handler functions will be run right
|
||||
away and waited to return before processing the next alert
|
||||
"""
|
||||
"""Pops all libtorrent alerts in the session queue and handles them
|
||||
appropriately."""
|
||||
alert = self.session.pop_alert()
|
||||
# Loop through all alerts in the queue
|
||||
while alert is not None:
|
||||
alert_type = type(alert).__name__
|
||||
# Loop through all alerts in the queue
|
||||
# Do some magic to get the alert type as a string
|
||||
alert_type = str(type(alert)).split("'")[1].split(".")[-1]
|
||||
# Display the alert message
|
||||
log.debug("%s: %s", alert_type, alert.message())
|
||||
# Call any handlers for this alert type
|
||||
if alert_type in self.handlers:
|
||||
if alert_type in self.handlers.keys():
|
||||
for handler in self.handlers[alert_type]:
|
||||
if not wait:
|
||||
self.delayed_calls.append(reactor.callLater(0, handler, alert))
|
||||
gobject.idle_add(handler, alert)
|
||||
else:
|
||||
handler(alert)
|
||||
|
||||
alert = self.session.pop_alert()
|
||||
|
||||
# Return True so that the timer continues
|
||||
return True
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# authmanager.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@@ -19,52 +19,26 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
import stat
|
||||
|
||||
import deluge.component as component
|
||||
import deluge.configmanager as configmanager
|
||||
import deluge.error
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
AUTH_LEVEL_NONE = 0
|
||||
AUTH_LEVEL_READONLY = 1
|
||||
AUTH_LEVEL_NORMAL = 5
|
||||
AUTH_LEVEL_ADMIN = 10
|
||||
|
||||
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
|
||||
|
||||
class BadLoginError(deluge.error.DelugeError):
|
||||
pass
|
||||
|
||||
class AuthManager(component.Component):
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "AuthManager")
|
||||
self.__auth = {}
|
||||
self.auth = {}
|
||||
|
||||
def start(self):
|
||||
self.__load_auth_file()
|
||||
|
||||
def stop(self):
|
||||
self.__auth = {}
|
||||
self.auth = {}
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
@@ -75,76 +49,43 @@ class AuthManager(component.Component):
|
||||
|
||||
:param username: str, username
|
||||
:param password: str, password
|
||||
:returns: int, the auth level for this user
|
||||
:rtype: int
|
||||
|
||||
:raises BadLoginError: if the username does not exist or password does not match
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
||||
if username not in self.__auth:
|
||||
if username not in self.auth:
|
||||
# Let's try to re-load the file.. Maybe it's been updated
|
||||
self.__load_auth_file()
|
||||
if username not in self.__auth:
|
||||
raise BadLoginError("Username does not exist")
|
||||
if username not in self.auth:
|
||||
return False
|
||||
|
||||
if self.__auth[username][0] == password:
|
||||
# Return the users auth level
|
||||
return int(self.__auth[username][1])
|
||||
else:
|
||||
raise BadLoginError("Password does not match")
|
||||
if self.auth[username] == password:
|
||||
return True
|
||||
|
||||
def __create_localclient_account(self):
|
||||
"""
|
||||
Returns the string.
|
||||
"""
|
||||
# We create a 'localclient' account with a random password
|
||||
try:
|
||||
from hashlib import sha1 as sha_hash
|
||||
except ImportError:
|
||||
from sha import new as sha_hash
|
||||
return "localclient:" + sha_hash(str(random.random())).hexdigest() + ":" + str(AUTH_LEVEL_ADMIN) + "\n"
|
||||
return False
|
||||
|
||||
def __load_auth_file(self):
|
||||
auth_file = configmanager.get_config_dir("auth")
|
||||
# Check for auth file and create if necessary
|
||||
if not os.path.exists(auth_file):
|
||||
localclient = self.__create_localclient_account()
|
||||
fd = open(auth_file, "w")
|
||||
fd.write(localclient)
|
||||
fd.flush()
|
||||
os.fsync(fd.fileno())
|
||||
fd.close()
|
||||
# We create a 'localclient' account with a random password
|
||||
try:
|
||||
from hashlib import sha1 as sha_hash
|
||||
except ImportError:
|
||||
from sha import new as sha_hash
|
||||
open(auth_file, "w").write("localclient:" + sha_hash(str(random.random())).hexdigest() + "\n")
|
||||
# Change the permissions on the file so only this user can read/write it
|
||||
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)
|
||||
f = [localclient]
|
||||
else:
|
||||
# Load the auth file into a dictionary: {username: password, ...}
|
||||
with open(auth_file, "r") as _file:
|
||||
f = _file.readlines()
|
||||
|
||||
# Load the auth file into a dictionary: {username: password, ...}
|
||||
f = open(auth_file, "r")
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("#") or not line:
|
||||
# This line is a comment or empty
|
||||
if line.startswith("#"):
|
||||
# This is a comment line
|
||||
continue
|
||||
try:
|
||||
lsplit = line.split(":")
|
||||
except Exception, e:
|
||||
log.error("Your auth file is malformed: %s", e)
|
||||
username, password = line.split(":")
|
||||
except ValueError:
|
||||
continue
|
||||
if len(lsplit) == 2:
|
||||
username, password = lsplit
|
||||
log.warning("Your auth entry for %s contains no auth level, using AUTH_LEVEL_DEFAULT(%s)..", username, AUTH_LEVEL_DEFAULT)
|
||||
level = AUTH_LEVEL_DEFAULT
|
||||
elif len(lsplit) == 3:
|
||||
username, password, level = lsplit
|
||||
else:
|
||||
log.error("Your auth file is malformed: Incorrect number of fields!")
|
||||
continue
|
||||
|
||||
self.__auth[username.strip()] = (password.strip(), level)
|
||||
|
||||
if "localclient" not in self.__auth:
|
||||
with open(auth_file, "a") as _file:
|
||||
_file.write(self.__create_localclient_account())
|
||||
self.auth[username.strip()] = password.strip()
|
||||
|
@@ -19,24 +19,18 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
try:
|
||||
import deluge.libtorrent as lt
|
||||
except ImportError:
|
||||
import libtorrent as lt
|
||||
if not (lt.version_major == 0 and lt.version_minor == 14):
|
||||
raise ImportError("This version of Deluge requires libtorrent 0.14!")
|
||||
|
||||
import deluge.component as component
|
||||
from deluge.configmanager import ConfigManager
|
||||
@@ -46,7 +40,7 @@ MAX_NUM_ATTEMPTS = 10
|
||||
|
||||
class AutoAdd(component.Component):
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5)
|
||||
component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5000)
|
||||
# Get the core config
|
||||
self.config = ConfigManager("core.conf")
|
||||
|
||||
@@ -68,18 +62,14 @@ class AutoAdd(component.Component):
|
||||
return
|
||||
|
||||
# Check the auto add folder for new torrents to add
|
||||
if not os.path.isdir(self.config["autoadd_location"]):
|
||||
if not os.path.exists(self.config["autoadd_location"]):
|
||||
log.warning("Invalid AutoAdd folder: %s", self.config["autoadd_location"])
|
||||
component.pause("AutoAdd")
|
||||
return
|
||||
|
||||
for filename in os.listdir(self.config["autoadd_location"]):
|
||||
try:
|
||||
if filename.split(".")[-1] == "torrent":
|
||||
filepath = os.path.join(self.config["autoadd_location"], filename)
|
||||
except UnicodeDecodeError, e:
|
||||
log.error("Unable to auto add torrent due to improper filename encoding: %s", e)
|
||||
continue
|
||||
if os.path.isfile(filepath) and filename.endswith(".torrent"):
|
||||
try:
|
||||
filedump = self.load_torrent(filepath)
|
||||
except (RuntimeError, Exception), e:
|
||||
|
1056
deluge/core/core.py
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# daemon.py
|
||||
#
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@@ -19,196 +19,27 @@
|
||||
# along with deluge. 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.
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import gettext
|
||||
import locale
|
||||
import pkg_resources
|
||||
from twisted.internet import reactor
|
||||
import twisted.internet.error
|
||||
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
import deluge.common
|
||||
from deluge.core.rpcserver import RPCServer, export
|
||||
from deluge.log import LOG as log
|
||||
import deluge.error
|
||||
|
||||
class Daemon(object):
|
||||
def __init__(self, options=None, args=None, classic=False):
|
||||
# Check for another running instance of the daemon
|
||||
if os.path.isfile(deluge.configmanager.get_config_dir("deluged.pid")):
|
||||
# Get the PID and the port of the supposedly running daemon
|
||||
try:
|
||||
with open(deluge.configmanager.get_config_dir("deluged.pid")) as _file:
|
||||
(pid, port) = _file.read().strip().split(";")
|
||||
pid = int(pid)
|
||||
port = int(port)
|
||||
except ValueError:
|
||||
pid = None
|
||||
port = None
|
||||
|
||||
|
||||
def process_running(pid):
|
||||
if deluge.common.windows_check():
|
||||
import win32process
|
||||
return pid in win32process.EnumProcesses()
|
||||
else:
|
||||
# We can just use os.kill on UNIX to test if the process is running
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
if pid is not None and process_running(pid):
|
||||
# Ok, so a process is running with this PID, let's make doubly-sure
|
||||
# it's a deluged process by trying to open a socket to it's port.
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.connect(("127.0.0.1", port))
|
||||
except socket.error:
|
||||
# Can't connect, so it must not be a deluged process..
|
||||
pass
|
||||
else:
|
||||
# This is a deluged!
|
||||
s.close()
|
||||
raise deluge.error.DaemonRunningError("There is a deluge daemon running with this config directory!")
|
||||
|
||||
# Initialize gettext
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
if hasattr(locale, "bindtextdomain"):
|
||||
locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
||||
if hasattr(locale, "textdomain"):
|
||||
locale.textdomain("deluge")
|
||||
gettext.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
||||
gettext.textdomain("deluge")
|
||||
gettext.install("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
||||
except Exception, e:
|
||||
log.error("Unable to initialize gettext/locale: %s", e)
|
||||
import __builtin__
|
||||
__builtin__.__dict__["_"] = lambda x: x
|
||||
|
||||
# Twisted catches signals to terminate, so just have it call the shutdown
|
||||
# method.
|
||||
reactor.addSystemEventTrigger("before", "shutdown", self._shutdown)
|
||||
|
||||
# Catch some Windows specific signals
|
||||
if deluge.common.windows_check():
|
||||
from win32api import SetConsoleCtrlHandler
|
||||
from win32con import CTRL_CLOSE_EVENT
|
||||
from win32con import CTRL_SHUTDOWN_EVENT
|
||||
def win_handler(ctrl_type):
|
||||
log.debug("ctrl_type: %s", ctrl_type)
|
||||
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
|
||||
self._shutdown()
|
||||
return 1
|
||||
SetConsoleCtrlHandler(win_handler)
|
||||
|
||||
class Daemon:
|
||||
def __init__(self, options, args):
|
||||
version = deluge.common.get_version()
|
||||
if deluge.common.get_revision() != "":
|
||||
version = version + "r" + deluge.common.get_revision()
|
||||
|
||||
log.info("Deluge daemon %s", version)
|
||||
log.debug("options: %s", options)
|
||||
log.debug("args: %s", args)
|
||||
# Set the config directory
|
||||
if options and options.config:
|
||||
deluge.configmanager.set_config_dir(options.config)
|
||||
|
||||
if options and options.listen_interface:
|
||||
listen_interface = options.listen_interface
|
||||
else:
|
||||
listen_interface = ""
|
||||
|
||||
if options and options.read_only_config_keys:
|
||||
read_only_config_keys = options.read_only_config_keys.split(",")
|
||||
else:
|
||||
read_only_config_keys = []
|
||||
deluge.configmanager.set_config_dir(options.config)
|
||||
|
||||
from deluge.core.core import Core
|
||||
# Start the core as a thread and join it until it's done
|
||||
self.core = Core(listen_interface=listen_interface,
|
||||
read_only_config_keys=read_only_config_keys)
|
||||
self.core = Core(options.port).run()
|
||||
|
||||
port = self.core.config["daemon_port"]
|
||||
if options and options.port:
|
||||
port = options.port
|
||||
if options and options.ui_interface:
|
||||
interface = options.ui_interface
|
||||
else:
|
||||
interface = ""
|
||||
|
||||
self.rpcserver = RPCServer(
|
||||
port=port,
|
||||
allow_remote=self.core.config["allow_remote"],
|
||||
listen=not classic,
|
||||
interface=interface
|
||||
)
|
||||
|
||||
# Register the daemon and the core RPCs
|
||||
self.rpcserver.register_object(self.core)
|
||||
self.rpcserver.register_object(self)
|
||||
|
||||
|
||||
# Make sure we start the PreferencesManager first
|
||||
component.start("PreferencesManager")
|
||||
|
||||
if not classic:
|
||||
# Write out a pid file all the time, we use this to see if a deluged is running
|
||||
# We also include the running port number to do an additional test
|
||||
with open(deluge.configmanager.get_config_dir("deluged.pid"), "wb") as _file:
|
||||
_file.write("%s;%s\n" % (os.getpid(), port))
|
||||
|
||||
component.start()
|
||||
try:
|
||||
reactor.run()
|
||||
finally:
|
||||
self._shutdown()
|
||||
|
||||
@export()
|
||||
def shutdown(self, *args, **kwargs):
|
||||
reactor.callLater(0, reactor.stop)
|
||||
|
||||
def _shutdown(self, *args, **kwargs):
|
||||
if os.path.exists(deluge.configmanager.get_config_dir("deluged.pid")):
|
||||
try:
|
||||
os.remove(deluge.configmanager.get_config_dir("deluged.pid"))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.error("Error removing deluged.pid!")
|
||||
|
||||
log.info("Waiting for components to shutdown..")
|
||||
d = component.shutdown()
|
||||
return d
|
||||
|
||||
@export()
|
||||
def info(self):
|
||||
"""
|
||||
Returns some info from the daemon.
|
||||
|
||||
:returns: str, the version number
|
||||
"""
|
||||
return deluge.common.get_version()
|
||||
|
||||
@export()
|
||||
def get_method_list(self):
|
||||
"""
|
||||
Returns a list of the exported methods.
|
||||
"""
|
||||
return self.rpcserver.get_method_list()
|
||||
|
@@ -1,84 +0,0 @@
|
||||
#
|
||||
# eventmanager.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge 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 deluge. 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 deluge.component as component
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class EventManager(component.Component):
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "EventManager")
|
||||
self.handlers = {}
|
||||
|
||||
def emit(self, event):
|
||||
"""
|
||||
Emits the event to interested clients.
|
||||
|
||||
:param event: DelugeEvent
|
||||
"""
|
||||
# Emit the event to the interested clients
|
||||
component.get("RPCServer").emit_event(event)
|
||||
# Call any handlers for the event
|
||||
if event.name in self.handlers:
|
||||
for handler in self.handlers[event.name]:
|
||||
#log.debug("Running handler %s for event %s with args: %s", event.name, handler, event.args)
|
||||
try:
|
||||
handler(*event.args)
|
||||
except Exception, e:
|
||||
log.error("Event handler %s failed in %s with exception %s", event.name, handler, e)
|
||||
|
||||
def register_event_handler(self, event, handler):
|
||||
"""
|
||||
Registers a function to be called when a `:param:event` is emitted.
|
||||
|
||||
:param event: str, the event name
|
||||
:param handler: function, to be called when `:param:event` is emitted
|
||||
|
||||
"""
|
||||
if event not in self.handlers:
|
||||
self.handlers[event] = []
|
||||
|
||||
if handler not in self.handlers[event]:
|
||||
self.handlers[event].append(handler)
|
||||
|
||||
def deregister_event_handler(self, event, handler):
|
||||
"""
|
||||
Deregisters an event handler function.
|
||||
|
||||
:param event: str, the event name
|
||||
:param handler: function, currently registered to handle `:param:event`
|
||||
|
||||
"""
|
||||
if event in self.handlers and handler in self.handlers[event]:
|
||||
self.handlers[event].remove(handler)
|
@@ -19,18 +19,7 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
@@ -77,24 +66,6 @@ def filter_one_keyword(torrent_ids, keyword):
|
||||
yield torrent_id
|
||||
break
|
||||
|
||||
def tracker_error_filter(torrent_ids, values):
|
||||
filtered_torrent_ids = []
|
||||
tm = component.get("TorrentManager")
|
||||
|
||||
# If this is a tracker_host, then we need to filter on it
|
||||
if values[0] != "Error":
|
||||
for torrent_id in torrent_ids:
|
||||
if values[0] == tm[torrent_id].get_status(["tracker_host"])["tracker_host"]:
|
||||
filtered_torrent_ids.append(torrent_id)
|
||||
return filtered_torrent_ids
|
||||
|
||||
# Check all the torrent's tracker_status for 'Error:' and only return torrent_ids
|
||||
# that have this substring in their tracker_status
|
||||
for torrent_id in torrent_ids:
|
||||
if "Error:" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
|
||||
filtered_torrent_ids.append(torrent_id)
|
||||
|
||||
return filtered_torrent_ids
|
||||
|
||||
class FilterManager(component.Component):
|
||||
"""FilterManager
|
||||
@@ -104,17 +75,13 @@ class FilterManager(component.Component):
|
||||
component.Component.__init__(self, "FilterManager")
|
||||
log.debug("FilterManager init..")
|
||||
self.core = core
|
||||
self.torrents = core.torrentmanager
|
||||
self.torrents = core.torrents
|
||||
self.registered_filters = {}
|
||||
self.register_filter("keyword", filter_keywords)
|
||||
self.tree_fields = {}
|
||||
|
||||
self.register_tree_field("state", self._init_state_tree)
|
||||
def _init_tracker_tree():
|
||||
return {"Error": 0}
|
||||
self.register_tree_field("tracker_host", _init_tracker_tree)
|
||||
|
||||
self.register_filter("tracker_host", tracker_error_filter)
|
||||
self.register_tree_field("tracker_host")
|
||||
|
||||
def filter_torrent_ids(self, filter_dict):
|
||||
"""
|
||||
@@ -126,11 +93,12 @@ class FilterManager(component.Component):
|
||||
|
||||
#sanitize input: filter-value must be a list of strings
|
||||
for key, value in filter_dict.items():
|
||||
if isinstance(value, basestring):
|
||||
filter_dict[key] = [value]
|
||||
if isinstance(value, str):
|
||||
filter_dict[key] = [value]
|
||||
|
||||
if "id" in filter_dict: #optimized filter for id:
|
||||
torrent_ids = list(filter_dict["id"])
|
||||
|
||||
if "id"in filter_dict: #optimized filter for id:
|
||||
torrent_ids = filter_dict["id"]
|
||||
del filter_dict["id"]
|
||||
else:
|
||||
torrent_ids = self.torrents.get_torrent_list()
|
||||
@@ -139,10 +107,6 @@ class FilterManager(component.Component):
|
||||
return torrent_ids
|
||||
|
||||
#special purpose: state=Active.
|
||||
if "state" in filter_dict:
|
||||
# We need to make sure this is a list for the logic below
|
||||
filter_dict["state"] = list(filter_dict["state"])
|
||||
|
||||
if "state" in filter_dict and "Active" in filter_dict["state"]:
|
||||
filter_dict["state"].remove("Active")
|
||||
if not filter_dict["state"]:
|
||||
@@ -165,13 +129,11 @@ class FilterManager(component.Component):
|
||||
|
||||
#leftover filter arguments:
|
||||
#default filter on status fields.
|
||||
status_func = self.core.get_torrent_status #premature optimalisation..
|
||||
status_func = self.core.export_get_torrent_status #premature optimalisation..
|
||||
for torrent_id in list(torrent_ids):
|
||||
status = status_func(torrent_id, filter_dict.keys()) #status={key:value}
|
||||
for field, values in filter_dict.iteritems():
|
||||
if field in status and status[field] in values:
|
||||
continue
|
||||
elif torrent_id in torrent_ids:
|
||||
if (not status[field] in values) and torrent_id in torrent_ids:
|
||||
torrent_ids.remove(torrent_id)
|
||||
|
||||
return torrent_ids
|
||||
@@ -182,7 +144,7 @@ class FilterManager(component.Component):
|
||||
for use in sidebar.
|
||||
"""
|
||||
torrent_ids = self.torrents.get_torrent_list()
|
||||
status_func = self.core.get_torrent_status #premature optimalisation..
|
||||
status_func = self.core.export_get_torrent_status #premature optimalisation..
|
||||
tree_keys = list(self.tree_fields.keys())
|
||||
if hide_cat:
|
||||
for cat in hide_cat:
|
||||
@@ -197,10 +159,6 @@ class FilterManager(component.Component):
|
||||
value = status[field]
|
||||
items[field][value] = items[field].get(value, 0) + 1
|
||||
|
||||
if "tracker_host" in items:
|
||||
items["tracker_host"]["All"] = len(torrent_ids)
|
||||
items["tracker_host"]["Error"] = len(tracker_error_filter(torrent_ids, ("Error",)))
|
||||
|
||||
if "state" in tree_keys and not show_zero_hits:
|
||||
self._hide_state_items(items["state"])
|
||||
|
||||
@@ -235,11 +193,10 @@ class FilterManager(component.Component):
|
||||
self.tree_fields[field] = init_func
|
||||
|
||||
def deregister_tree_field(self, field):
|
||||
if field in self.tree_fields:
|
||||
del self.tree_fields[field]
|
||||
del self.tree_fields[field]
|
||||
|
||||
def filter_state_active(self, torrent_ids):
|
||||
get_status = self.core.get_torrent_status
|
||||
get_status = self.core.export_get_torrent_status
|
||||
for torrent_id in list(torrent_ids):
|
||||
status = get_status(torrent_id, ["download_payload_rate", "upload_payload_rate"])
|
||||
if status["download_payload_rate"] or status["upload_payload_rate"]:
|
||||
@@ -266,3 +223,6 @@ class FilterManager(component.Component):
|
||||
iy = 99
|
||||
|
||||
return ix - iy
|
||||
|
||||
|
||||
|
||||
|
@@ -19,18 +19,7 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
@@ -40,9 +29,14 @@ import pickle
|
||||
import cPickle
|
||||
import shutil
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
try:
|
||||
import deluge.libtorrent as lt
|
||||
except ImportError:
|
||||
import libtorrent as lt
|
||||
if not (lt.version_major == 0 and lt.version_minor == 14):
|
||||
raise ImportError("This version of Deluge requires libtorrent 0.14!")
|
||||
|
||||
from deluge.configmanager import ConfigManager, get_config_dir
|
||||
from deluge.configmanager import ConfigManager
|
||||
import deluge.core.torrentmanager
|
||||
from deluge.log import LOG as log
|
||||
|
||||
@@ -69,8 +63,8 @@ class PickleUpgrader(pickle.Unpickler):
|
||||
class OldStateUpgrader:
|
||||
def __init__(self):
|
||||
self.config = ConfigManager("core.conf")
|
||||
self.state05_location = os.path.join(get_config_dir(), "persistent.state")
|
||||
self.state10_location = os.path.join(get_config_dir(), "state", "torrents.state")
|
||||
self.state05_location = os.path.join(self.config["config_location"], "persistent.state")
|
||||
self.state10_location = os.path.join(self.config["state_location"], "torrents.state")
|
||||
if os.path.exists(self.state05_location) and not os.path.exists(self.state10_location):
|
||||
# If the 0.5 state file exists and the 1.0 doesn't, then let's upgrade it
|
||||
self.upgrade05()
|
||||
@@ -89,7 +83,7 @@ class OldStateUpgrader:
|
||||
|
||||
new_state = deluge.core.torrentmanager.TorrentManagerState()
|
||||
for ti, uid in state.torrents.items():
|
||||
torrent_path = os.path.join(get_config_dir(), "torrentfiles", ti.filename)
|
||||
torrent_path = os.path.join(self.config["config_location"], "torrentfiles", ti.filename)
|
||||
try:
|
||||
torrent_info = None
|
||||
log.debug("Attempting to create torrent_info from %s", torrent_path)
|
||||
@@ -97,11 +91,11 @@ class OldStateUpgrader:
|
||||
torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
|
||||
_file.close()
|
||||
except (IOError, RuntimeError), e:
|
||||
log.warning("Unable to open %s: %s", torrent_path, e)
|
||||
log.warning("Unable to open %s: %s", filepath, e)
|
||||
|
||||
# Copy the torrent file to the new location
|
||||
import shutil
|
||||
shutil.copyfile(torrent_path, os.path.join(get_config_dir(), "state", str(torrent_info.info_hash()) + ".torrent"))
|
||||
shutil.copyfile(torrent_path, os.path.join(self.config["state_location"], str(torrent_info.info_hash()) + ".torrent"))
|
||||
|
||||
# Set the file prioritiy property if not already there
|
||||
if not hasattr(ti, "priorities"):
|
||||
@@ -127,7 +121,7 @@ class OldStateUpgrader:
|
||||
try:
|
||||
log.debug("Saving torrent state file.")
|
||||
state_file = open(
|
||||
os.path.join(get_config_dir(), "state", "torrents.state"), "wb")
|
||||
os.path.join(self.config["state_location"], "torrents.state"), "wb")
|
||||
cPickle.dump(new_state, state_file)
|
||||
state_file.close()
|
||||
except IOError, e:
|
||||
|
@@ -19,27 +19,14 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
"""PluginManager for Core"""
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.task import LoopingCall
|
||||
import gobject
|
||||
|
||||
from deluge.event import PluginEnabledEvent, PluginDisabledEvent
|
||||
import deluge.pluginmanagerbase
|
||||
import deluge.component as component
|
||||
from deluge.log import LOG as log
|
||||
@@ -50,7 +37,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
|
||||
functions to access parts of the core."""
|
||||
|
||||
def __init__(self, core):
|
||||
component.Component.__init__(self, "CorePluginManager")
|
||||
component.Component.__init__(self, "PluginManager")
|
||||
self.core = core
|
||||
# Set up the hooks dictionary
|
||||
self.hooks = {
|
||||
"post_torrent_add": [],
|
||||
"post_torrent_remove": [],
|
||||
"post_session_load": []
|
||||
}
|
||||
|
||||
self.status_fields = {}
|
||||
|
||||
@@ -62,44 +56,29 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
|
||||
# Enable plugins that are enabled in the config
|
||||
self.enable_plugins()
|
||||
|
||||
# Set update timer to call update() in plugins every second
|
||||
self.update_timer = gobject.timeout_add(1000, self.update_plugins)
|
||||
|
||||
def stop(self):
|
||||
# Disable all enabled plugins
|
||||
self.disable_plugins()
|
||||
# Stop the update timer
|
||||
gobject.source_remove(self.update_timer)
|
||||
|
||||
def shutdown(self):
|
||||
self.stop()
|
||||
|
||||
def update_plugins(self):
|
||||
for plugin in self.plugins.keys():
|
||||
if hasattr(self.plugins[plugin], "update"):
|
||||
try:
|
||||
self.plugins[plugin].update()
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
|
||||
def enable_plugin(self, name):
|
||||
if name not in self.plugins:
|
||||
super(PluginManager, self).enable_plugin(name)
|
||||
if name in self.plugins:
|
||||
component.get("EventManager").emit(PluginEnabledEvent(name))
|
||||
|
||||
def disable_plugin(self, name):
|
||||
if name in self.plugins:
|
||||
super(PluginManager, self).disable_plugin(name)
|
||||
if name not in self.plugins:
|
||||
component.get("EventManager").emit(PluginDisabledEvent(name))
|
||||
|
||||
def get_status(self, torrent_id, fields):
|
||||
"""Return the value of status fields for the selected torrent_id."""
|
||||
status = {}
|
||||
if len(fields) == 0:
|
||||
fields = self.status_fields.keys()
|
||||
for field in fields:
|
||||
try:
|
||||
status[field] = self.status_fields[field](torrent_id)
|
||||
except KeyError:
|
||||
self.plugins[plugin].update()
|
||||
except AttributeError:
|
||||
# We don't care if this doesn't work
|
||||
pass
|
||||
return status
|
||||
|
||||
def get_core(self):
|
||||
"""Returns a reference to the core"""
|
||||
return self.core
|
||||
|
||||
def register_status_field(self, field, function):
|
||||
"""Register a new status field. This can be used in the same way the
|
||||
@@ -114,3 +93,62 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
|
||||
del self.status_fields[field]
|
||||
except:
|
||||
log.warning("Unable to deregister status field %s", field)
|
||||
|
||||
def get_status(self, torrent_id, fields):
|
||||
"""Return the value of status fields for the selected torrent_id."""
|
||||
status = {}
|
||||
for field in fields:
|
||||
try:
|
||||
status[field] = self.status_fields[field](torrent_id)
|
||||
except KeyError:
|
||||
log.warning("Status field %s is not registered with the\
|
||||
PluginManager.", field)
|
||||
return status
|
||||
|
||||
def register_hook(self, hook, function):
|
||||
"""Register a hook function with the plugin manager"""
|
||||
try:
|
||||
self.hooks[hook].append(function)
|
||||
except KeyError:
|
||||
log.warning("Plugin attempting to register invalid hook.")
|
||||
|
||||
def deregister_hook(self, hook, function):
|
||||
"""Deregisters a hook function"""
|
||||
try:
|
||||
self.hooks[hook].remove(function)
|
||||
except:
|
||||
log.warning("Unable to deregister hook %s", hook)
|
||||
|
||||
def run_post_torrent_add(self, torrent_id):
|
||||
"""This hook is run after a torrent has been added to the session."""
|
||||
log.debug("run_post_torrent_add")
|
||||
for function in self.hooks["post_torrent_add"]:
|
||||
function(torrent_id)
|
||||
|
||||
def run_post_torrent_remove(self, torrent_id):
|
||||
"""This hook is run after a torrent has been removed from the session.
|
||||
"""
|
||||
log.debug("run_post_torrent_remove")
|
||||
for function in self.hooks["post_torrent_remove"]:
|
||||
function(torrent_id)
|
||||
|
||||
def run_post_session_load(self):
|
||||
"""This hook is run after all the torrents have been loaded into the
|
||||
session from the saved state. It is called prior to resuming the
|
||||
torrents and they all will have a 'Paused' state."""
|
||||
log.debug("run_post_session_load")
|
||||
for function in self.hooks["post_session_load"]:
|
||||
function()
|
||||
|
||||
def get_torrent_list(self):
|
||||
"""Returns a list of torrent_id's in the current session."""
|
||||
return component.get("TorrentManager").get_torrent_list()
|
||||
|
||||
def block_ip_range(self, range):
|
||||
"""Blocks the ip range in the core"""
|
||||
return self.core.export_block_ip_range(range)
|
||||
|
||||
def reset_ip_filter(self):
|
||||
"""Resets the ip filter"""
|
||||
return self.core.export_reset_ip_filter()
|
||||
|
||||
|
@@ -19,43 +19,28 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import os.path
|
||||
import threading
|
||||
import pkg_resources
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.task import LoopingCall
|
||||
import gobject
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
try:
|
||||
import deluge.libtorrent as lt
|
||||
except ImportError:
|
||||
import libtorrent as lt
|
||||
if not (lt.version_major == 0 and lt.version_minor == 14):
|
||||
raise ImportError("This version of Deluge requires libtorrent 0.14!")
|
||||
|
||||
from deluge.event import *
|
||||
import deluge.configmanager
|
||||
import deluge.common
|
||||
import deluge.component as component
|
||||
from deluge.log import LOG as log
|
||||
|
||||
try:
|
||||
import GeoIP
|
||||
except ImportError:
|
||||
GeoIP = None
|
||||
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"config_location": deluge.configmanager.get_config_dir(),
|
||||
"send_info": False,
|
||||
"info_sent": 0.0,
|
||||
"daemon_port": 58846,
|
||||
@@ -63,11 +48,10 @@ DEFAULT_PREFS = {
|
||||
"compact_allocation": False,
|
||||
"download_location": deluge.common.get_default_download_dir(),
|
||||
"listen_ports": [6881, 6891],
|
||||
"listen_interface": "",
|
||||
"copy_torrent_file": False,
|
||||
"del_copy_torrent_file": False,
|
||||
"torrentfiles_location": deluge.common.get_default_download_dir(),
|
||||
"plugins_location": os.path.join(deluge.configmanager.get_config_dir(), "plugins"),
|
||||
"state_location": os.path.join(deluge.configmanager.get_config_dir(), "state"),
|
||||
"prioritize_first_last_pieces": False,
|
||||
"random_port": True,
|
||||
"dht": True,
|
||||
@@ -144,15 +128,10 @@ DEFAULT_PREFS = {
|
||||
"outgoing_ports": [0, 0],
|
||||
"random_outgoing_ports": True,
|
||||
"peer_tos": "0x00",
|
||||
"rate_limit_ip_overhead": True,
|
||||
"geoip_db_location": "/usr/share/GeoIP/GeoIP.dat",
|
||||
"cache_size": 512,
|
||||
"cache_expiry": 60
|
||||
"rate_limit_ip_overhead": True
|
||||
}
|
||||
|
||||
class PreferencesManager(component.Component):
|
||||
LT_SINGLE_PROXY = deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("0.16.0.0")
|
||||
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "PreferencesManager")
|
||||
|
||||
@@ -161,14 +140,16 @@ class PreferencesManager(component.Component):
|
||||
def start(self):
|
||||
self.core = component.get("Core")
|
||||
self.session = component.get("Core").session
|
||||
self.settings = component.get("Core").settings
|
||||
self.signals = component.get("SignalManager")
|
||||
|
||||
# Register set functions in the Config
|
||||
self.config.register_set_function("torrentfiles_location",
|
||||
self._on_set_torrentfiles_location)
|
||||
self.config.register_set_function("state_location",
|
||||
self._on_set_state_location)
|
||||
self.config.register_set_function("listen_ports",
|
||||
self._on_set_listen_ports)
|
||||
self.config.register_set_function("listen_interface",
|
||||
self._on_set_listen_interface)
|
||||
self.config.register_set_function("random_port",
|
||||
self._on_set_random_port)
|
||||
self.config.register_set_function("outgoing_ports",
|
||||
@@ -227,27 +208,12 @@ class PreferencesManager(component.Component):
|
||||
self._on_new_release_check)
|
||||
self.config.register_set_function("rate_limit_ip_overhead",
|
||||
self._on_rate_limit_ip_overhead)
|
||||
self.config.register_set_function("geoip_db_location",
|
||||
self._on_geoip_db_location)
|
||||
self.config.register_set_function("cache_size",
|
||||
self._on_cache_size)
|
||||
self.config.register_set_function("cache_expiry",
|
||||
self._on_cache_expiry)
|
||||
|
||||
self.config.register_change_callback(self._on_config_value_change)
|
||||
|
||||
def stop(self):
|
||||
if self.new_release_timer and self.new_release_timer.running:
|
||||
self.new_release_timer.stop()
|
||||
|
||||
# Config set functions
|
||||
def session_set_setting(self, key, value):
|
||||
settings = self.session.settings()
|
||||
setattr(settings, key, value)
|
||||
self.session.set_settings(settings)
|
||||
|
||||
def _on_config_value_change(self, key, value):
|
||||
component.get("EventManager").emit(ConfigValueChangedEvent(key, value))
|
||||
self.signals.emit("config_value_changed", key, value)
|
||||
|
||||
def _on_set_torrentfiles_location(self, key, value):
|
||||
if self.config["copy_torrent_file"]:
|
||||
@@ -256,15 +222,18 @@ class PreferencesManager(component.Component):
|
||||
except Exception, e:
|
||||
log.debug("Unable to make directory: %s", e)
|
||||
|
||||
def _on_set_state_location(self, key, value):
|
||||
if not os.access(value, os.F_OK):
|
||||
try:
|
||||
os.makedirs(value)
|
||||
except Exception, e:
|
||||
log.debug("Unable to make directory: %s", e)
|
||||
|
||||
def _on_set_listen_ports(self, key, value):
|
||||
# Only set the listen ports if random_port is not true
|
||||
if self.config["random_port"] is not True:
|
||||
log.debug("listen port range set to %s-%s", value[0], value[1])
|
||||
self.session.listen_on(value[0], value[1], str(self.config["listen_interface"]).strip())
|
||||
|
||||
def _on_set_listen_interface(self, key, value):
|
||||
# Call the random_port callback since it'll do what we need
|
||||
self._on_set_random_port("random_port", self.config["random_port"])
|
||||
self.session.listen_on(value[0], value[1])
|
||||
|
||||
def _on_set_random_port(self, key, value):
|
||||
log.debug("random port value set to %s", value)
|
||||
@@ -282,12 +251,13 @@ class PreferencesManager(component.Component):
|
||||
# Set the listen ports
|
||||
log.debug("listen port range set to %s-%s", listen_ports[0],
|
||||
listen_ports[1])
|
||||
self.session.listen_on(listen_ports[0], listen_ports[1], str(self.config["listen_interface"]).strip())
|
||||
self.session.listen_on(listen_ports[0], listen_ports[1])
|
||||
|
||||
def _on_set_outgoing_ports(self, key, value):
|
||||
if not self.config["random_outgoing_ports"]:
|
||||
log.debug("outgoing port range set to %s-%s", value[0], value[1])
|
||||
self.session_set_setting("outgoing_ports", (value[0], value[1]))
|
||||
self.settings.outgoing_ports = value[0], value[1]
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_random_outgoing_ports(self, key, value):
|
||||
if value:
|
||||
@@ -296,19 +266,20 @@ class PreferencesManager(component.Component):
|
||||
def _on_set_peer_tos(self, key, value):
|
||||
log.debug("setting peer_tos to: %s", value)
|
||||
try:
|
||||
self.session_set_setting("peer_tos", chr(int(value, 16)))
|
||||
self.settings.peer_tos = chr(int(value, 16))
|
||||
except ValueError, e:
|
||||
log.debug("Invalid tos byte: %s", e)
|
||||
return
|
||||
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_dht(self, key, value):
|
||||
log.debug("dht value set to %s", value)
|
||||
state_file = deluge.configmanager.get_config_dir("dht.state")
|
||||
state_file = deluge.common.get_default_config_dir("dht.state")
|
||||
if value:
|
||||
state = None
|
||||
try:
|
||||
with open(state_file, "rb") as _file:
|
||||
state = lt.bdecode(_file.read())
|
||||
state = lt.bdecode(open(state_file, "rb").read())
|
||||
except Exception, e:
|
||||
log.warning("Unable to read DHT state file: %s", e)
|
||||
|
||||
@@ -319,8 +290,6 @@ class PreferencesManager(component.Component):
|
||||
self.session.start_dht(None)
|
||||
self.session.add_dht_router("router.bittorrent.com", 6881)
|
||||
self.session.add_dht_router("router.utorrent.com", 6881)
|
||||
self.session.add_dht_router("dht.transmissionbt.com", 6881)
|
||||
self.session.add_dht_router("dht.aelitis.com", 6881)
|
||||
self.session.add_dht_router("router.bitcomet.com", 6881)
|
||||
else:
|
||||
self.core.save_dht_state()
|
||||
@@ -349,19 +318,16 @@ class PreferencesManager(component.Component):
|
||||
|
||||
def _on_set_utpex(self, key, value):
|
||||
log.debug("utpex value set to %s", value)
|
||||
# In libtorrent versions below 0.16.7.0 disable extension bindings due to GIL issue.
|
||||
# https://code.google.com/p/libtorrent/issues/detail?id=369
|
||||
if value and deluge.common.VersionSplit(lt.version) >= deluge.common.VersionSplit("0.16.7.0"):
|
||||
self.session.add_extension("ut_pex")
|
||||
if value:
|
||||
self.session.add_extension(lt.create_ut_pex_plugin)
|
||||
|
||||
def _on_set_encryption(self, key, value):
|
||||
log.debug("encryption value %s set to %s..", key, value)
|
||||
pe_enc_level = {0: lt.enc_level.plaintext, 1: lt.enc_level.rc4, 2: lt.enc_level.both}
|
||||
pe_settings = lt.pe_settings()
|
||||
pe_settings.out_enc_policy = \
|
||||
lt.enc_policy(self.config["enc_out_policy"])
|
||||
pe_settings.in_enc_policy = lt.enc_policy(self.config["enc_in_policy"])
|
||||
pe_settings.allowed_enc_level = lt.enc_level(pe_enc_level[self.config["enc_level"]])
|
||||
pe_settings.allowed_enc_level = lt.enc_level(self.config["enc_level"])
|
||||
pe_settings.prefer_rc4 = self.config["enc_prefer_rc4"]
|
||||
self.session.set_pe_settings(pe_settings)
|
||||
set = self.session.get_pe_settings()
|
||||
@@ -403,39 +369,51 @@ class PreferencesManager(component.Component):
|
||||
self.session.set_max_half_open_connections(value)
|
||||
|
||||
def _on_set_max_connections_per_second(self, key, value):
|
||||
self.session_set_setting("connection_speed", value)
|
||||
self.settings.connection_speed = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_ignore_limits_on_local_network(self, key, value):
|
||||
self.session_set_setting("ignore_limits_on_local_network", value)
|
||||
self.settings.ignore_limits_on_local_network = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_share_ratio_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.session_set_setting("share_ratio_limit", value)
|
||||
self.settings.share_ratio_limit = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_seed_time_ratio_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.session_set_setting("seed_time_ratio_limit", value)
|
||||
self.settings.seed_time_ratio_limit = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_seed_time_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
# This value is stored in minutes in deluge, but libtorrent wants seconds
|
||||
self.session_set_setting("seed_time_limit", int(value * 60))
|
||||
self.settings.seed_time_limit = int(value * 60)
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_max_active_downloading(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.session_set_setting("active_downloads", value)
|
||||
log.debug("active_downloads: %s", self.settings.active_downloads)
|
||||
self.settings.active_downloads = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_max_active_seeding(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.session_set_setting("active_seeds", value)
|
||||
log.debug("active_seeds: %s", self.settings.active_seeds)
|
||||
self.settings.active_seeds = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_max_active_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.session_set_setting("active_limit", value)
|
||||
log.debug("active_limit: %s", self.settings.active_limit)
|
||||
self.settings.active_limit = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_dont_count_slow_torrents(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.session_set_setting("dont_count_slow_torrents", value)
|
||||
self.settings.dont_count_slow_torrents = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_send_info(self, key, value):
|
||||
log.debug("Sending anonymous stats..")
|
||||
@@ -458,7 +436,7 @@ class PreferencesManager(component.Component):
|
||||
platform.machine() + "&python=" + platform.python_version() \
|
||||
+ "&deluge=" + deluge.common.get_version() \
|
||||
+ "&os=" + platform.system() \
|
||||
+ "&plugins=" + quote_plus(":".join(self.config["enabled_plugins"]))
|
||||
+ "&plugins=" + quote_plus(self.config["enabled_plugins"])
|
||||
urlopen(url)
|
||||
except IOError, e:
|
||||
log.debug("Network error while trying to send info: %s", e)
|
||||
@@ -471,70 +449,28 @@ class PreferencesManager(component.Component):
|
||||
if value:
|
||||
log.debug("Checking for new release..")
|
||||
threading.Thread(target=self.core.get_new_release).start()
|
||||
if self.new_release_timer and self.new_release_timer.running:
|
||||
self.new_release_timer.stop()
|
||||
if self.new_release_timer:
|
||||
gobject.source_remove(self.new_release_timer)
|
||||
# Set a timer to check for a new release every 3 days
|
||||
self.new_release_timer = LoopingCall(
|
||||
self._on_new_release_check, "new_release_check", True)
|
||||
self.new_release_timer.start(72 * 60 * 60, False)
|
||||
self.new_release_timer = gobject.timeout_add(
|
||||
72 * 60 * 60 * 1000, self._on_new_release_check, "new_release_check", True)
|
||||
else:
|
||||
if self.new_release_timer and self.new_release_timer.running:
|
||||
self.new_release_timer.stop()
|
||||
if self.new_release_timer:
|
||||
gobject.source.remove(self.new_release_timer)
|
||||
|
||||
def _on_set_proxies(self, key, value):
|
||||
# Test for single proxy with lt >= 0.16
|
||||
if self.LT_SINGLE_PROXY:
|
||||
for proxy_type in value:
|
||||
if proxy_type == "peer":
|
||||
continue
|
||||
if self.config["proxies"][proxy_type] != value["peer"]:
|
||||
log.warning("This version of libtorrent only supports a single proxy setting "
|
||||
"based upon 'peer' which will apply to all other other types.")
|
||||
self.config["proxies"][proxy_type] = value["peer"]
|
||||
|
||||
proxy_settings = lt.proxy_settings()
|
||||
proxy_settings.type = lt.proxy_type(value["peer"]["type"])
|
||||
proxy_settings.username = str(value["peer"]["username"])
|
||||
proxy_settings.password = str(value["peer"]["password"])
|
||||
proxy_settings.hostname = str(value["peer"]["hostname"])
|
||||
proxy_settings.port = value["peer"]["port"]
|
||||
log.debug("Setting proxy settings: %s", value["peer"])
|
||||
self.session.set_proxy(proxy_settings)
|
||||
else:
|
||||
for k, v in value.items():
|
||||
for k, v in value.items():
|
||||
if v["type"]:
|
||||
proxy_settings = lt.proxy_settings()
|
||||
proxy_settings.type = lt.proxy_type(v["type"])
|
||||
proxy_settings.username = str(v["username"])
|
||||
proxy_settings.password = str(v["password"])
|
||||
proxy_settings.hostname = str(v["hostname"])
|
||||
proxy_settings.username = v["username"]
|
||||
proxy_settings.password = v["password"]
|
||||
proxy_settings.hostname = v["hostname"]
|
||||
proxy_settings.port = v["port"]
|
||||
log.debug("Setting %s proxy settings: %s", k, v)
|
||||
log.debug("setting %s proxy settings", k)
|
||||
getattr(self.session, "set_%s_proxy" % k)(proxy_settings)
|
||||
|
||||
def _on_rate_limit_ip_overhead(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
self.session_set_setting("rate_limit_ip_overhead", value)
|
||||
|
||||
def _on_geoip_db_location(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
# Load the GeoIP DB for country look-ups if available
|
||||
if os.path.exists(value):
|
||||
try:
|
||||
self.core.geoip_instance = GeoIP.open(value, GeoIP.GEOIP_STANDARD)
|
||||
except AttributeError:
|
||||
try:
|
||||
self.session.load_country_db(value)
|
||||
except RuntimeError, ex:
|
||||
log.error("Unable to load geoip database: %s", ex)
|
||||
except AttributeError:
|
||||
log.warning("GeoIP Unavailable")
|
||||
else:
|
||||
log.warning("Unable to find GeoIP database file!")
|
||||
|
||||
def _on_cache_size(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
self.session_set_setting("cache_size", value)
|
||||
|
||||
def _on_cache_expiry(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
self.session_set_setting("cache_expiry", value)
|
||||
self.settings.rate_limit_ip_overhead = value
|
||||
self.session.set_settings(self.settings)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# rpcserver.py
|
||||
#
|
||||
# Copyright (C) 2008,2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@@ -19,510 +19,96 @@
|
||||
# along with deluge. 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.
|
||||
#
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
"""RPCServer Module"""
|
||||
|
||||
from __future__ import with_statement
|
||||
import gobject
|
||||
|
||||
import sys
|
||||
import zlib
|
||||
import os
|
||||
import stat
|
||||
import traceback
|
||||
|
||||
from twisted.internet.protocol import Factory, Protocol
|
||||
from twisted.internet import ssl, reactor, defer
|
||||
|
||||
from OpenSSL import crypto, SSL
|
||||
from types import FunctionType
|
||||
|
||||
try:
|
||||
import rencode
|
||||
except ImportError:
|
||||
import deluge.rencode as rencode
|
||||
from deluge.SimpleXMLRPCServer import SimpleXMLRPCServer
|
||||
from deluge.SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
|
||||
from SocketServer import ThreadingMixIn
|
||||
from base64 import decodestring, encodestring
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.authmanager import AUTH_LEVEL_NONE, AUTH_LEVEL_DEFAULT
|
||||
|
||||
RPC_RESPONSE = 1
|
||||
RPC_ERROR = 2
|
||||
RPC_EVENT = 3
|
||||
|
||||
def export(auth_level=AUTH_LEVEL_DEFAULT):
|
||||
"""
|
||||
Decorator function to register an object's method as an RPC. The object
|
||||
will need to be registered with an :class:`RPCServer` to be effective.
|
||||
|
||||
:param func: the function to export
|
||||
:type func: function
|
||||
:param auth_level: the auth level required to call this method
|
||||
:type auth_level: int
|
||||
|
||||
"""
|
||||
def wrap(func, *args, **kwargs):
|
||||
func._rpcserver_export = True
|
||||
func._rpcserver_auth_level = auth_level
|
||||
doc = func.__doc__
|
||||
func.__doc__ = "**RPC Exported Function** (*Auth Level: %s*)\n\n" % auth_level
|
||||
if doc:
|
||||
func.__doc__ += doc
|
||||
|
||||
return func
|
||||
|
||||
if type(auth_level) is FunctionType:
|
||||
func = auth_level
|
||||
auth_level = AUTH_LEVEL_DEFAULT
|
||||
return wrap(func)
|
||||
else:
|
||||
return wrap
|
||||
|
||||
|
||||
def format_request(call):
|
||||
"""
|
||||
Format the RPCRequest message for debug printing
|
||||
|
||||
:param call: the request
|
||||
:type call: a RPCRequest
|
||||
|
||||
:returns: a formatted string for printing
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
try:
|
||||
s = call[1] + "("
|
||||
if call[2]:
|
||||
s += ", ".join([str(x) for x in call[2]])
|
||||
if call[3]:
|
||||
if call[2]:
|
||||
s += ", "
|
||||
s += ", ".join([key + "=" + str(value) for key, value in call[3].items()])
|
||||
s += ")"
|
||||
except UnicodeEncodeError:
|
||||
return "UnicodeEncodeError, call: %s" % call
|
||||
else:
|
||||
return s
|
||||
|
||||
class DelugeError(Exception):
|
||||
pass
|
||||
|
||||
class NotAuthorizedError(DelugeError):
|
||||
pass
|
||||
|
||||
class ServerContextFactory(object):
|
||||
def getContext(self):
|
||||
"""
|
||||
Create an SSL context.
|
||||
|
||||
This loads the servers cert/private key SSL files for use with the
|
||||
SSL transport.
|
||||
"""
|
||||
ssl_dir = deluge.configmanager.get_config_dir("ssl")
|
||||
ctx = SSL.Context(SSL.SSLv23_METHOD)
|
||||
ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
|
||||
ctx.use_certificate_file(os.path.join(ssl_dir, "daemon.cert"))
|
||||
ctx.use_privatekey_file(os.path.join(ssl_dir, "daemon.pkey"))
|
||||
return ctx
|
||||
|
||||
class DelugeRPCProtocol(Protocol):
|
||||
__buffer = None
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
This method is called whenever data is received from a client. The
|
||||
only message that a client sends to the server is a RPC Request message.
|
||||
If the RPC Request message is valid, then the method is called in
|
||||
:meth:`dispatch`.
|
||||
|
||||
:param data: the data from the client. It should be a zlib compressed
|
||||
rencoded string.
|
||||
:type data: str
|
||||
|
||||
"""
|
||||
if self.__buffer:
|
||||
# We have some data from the last dataReceived() so lets prepend it
|
||||
data = self.__buffer + data
|
||||
self.__buffer = None
|
||||
|
||||
while data:
|
||||
dobj = zlib.decompressobj()
|
||||
try:
|
||||
request = rencode.loads(dobj.decompress(data))
|
||||
except Exception, e:
|
||||
#log.debug("Received possible invalid message (%r): %s", data, e)
|
||||
# This could be cut-off data, so we'll save this in the buffer
|
||||
# and try to prepend it on the next dataReceived()
|
||||
self.__buffer = data
|
||||
return
|
||||
else:
|
||||
data = dobj.unused_data
|
||||
|
||||
if type(request) is not tuple:
|
||||
log.debug("Received invalid message: type is not tuple")
|
||||
return
|
||||
|
||||
if len(request) < 1:
|
||||
log.debug("Received invalid message: there are no items")
|
||||
return
|
||||
|
||||
for call in request:
|
||||
if len(call) != 4:
|
||||
log.debug("Received invalid rpc request: number of items in request is %s", len(call))
|
||||
continue
|
||||
#log.debug("RPCRequest: %s", format_request(call))
|
||||
reactor.callLater(0, self.dispatch, *call)
|
||||
|
||||
def sendData(self, data):
|
||||
"""
|
||||
Sends the data to the client.
|
||||
|
||||
:param data: the object that is to be sent to the client. This should
|
||||
be one of the RPC message types.
|
||||
:type data: object
|
||||
|
||||
"""
|
||||
self.transport.write(zlib.compress(rencode.dumps(data)))
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
This method is called when a new client connects.
|
||||
"""
|
||||
peer = self.transport.getPeer()
|
||||
log.info("Deluge Client connection made from: %s:%s", peer.host, peer.port)
|
||||
# Set the initial auth level of this session to AUTH_LEVEL_NONE and empty username.
|
||||
self.factory.authorized_sessions[self.transport.sessionno] = (AUTH_LEVEL_NONE, "")
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
This method is called when the client is disconnected.
|
||||
|
||||
:param reason: the reason the client disconnected.
|
||||
:type reason: str
|
||||
|
||||
"""
|
||||
|
||||
# We need to remove this session from various dicts
|
||||
del self.factory.authorized_sessions[self.transport.sessionno]
|
||||
if self.transport.sessionno in self.factory.session_protocols:
|
||||
del self.factory.session_protocols[self.transport.sessionno]
|
||||
if self.transport.sessionno in self.factory.interested_events:
|
||||
del self.factory.interested_events[self.transport.sessionno]
|
||||
|
||||
log.info("Deluge client disconnected: %s", reason.value)
|
||||
|
||||
def dispatch(self, request_id, method, args, kwargs):
|
||||
"""
|
||||
This method is run when a RPC Request is made. It will run the local method
|
||||
and will send either a RPC Response or RPC Error back to the client.
|
||||
|
||||
:param request_id: the request_id from the client (sent in the RPC Request)
|
||||
:type request_id: int
|
||||
:param method: the local method to call. It must be registered with
|
||||
the :class:`RPCServer`.
|
||||
:type method: str
|
||||
:param args: the arguments to pass to `method`
|
||||
:type args: list
|
||||
:param kwargs: the keyword-arguments to pass to `method`
|
||||
:type kwargs: dict
|
||||
|
||||
"""
|
||||
def sendError():
|
||||
"""
|
||||
Sends an error response with the contents of the exception that was raised.
|
||||
"""
|
||||
exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
|
||||
|
||||
self.sendData((
|
||||
RPC_ERROR,
|
||||
request_id,
|
||||
(exceptionType.__name__,
|
||||
exceptionValue.args[0] if len(exceptionValue.args) == 1 else "",
|
||||
"".join(traceback.format_tb(exceptionTraceback)))
|
||||
))
|
||||
|
||||
if method == "daemon.login":
|
||||
# This is a special case and used in the initial connection process
|
||||
# We need to authenticate the user here
|
||||
try:
|
||||
ret = component.get("AuthManager").authorize(*args, **kwargs)
|
||||
if ret:
|
||||
self.factory.authorized_sessions[self.transport.sessionno] = (ret, args[0])
|
||||
self.factory.session_protocols[self.transport.sessionno] = self
|
||||
except Exception, e:
|
||||
sendError()
|
||||
log.exception(e)
|
||||
else:
|
||||
self.sendData((RPC_RESPONSE, request_id, (ret)))
|
||||
if not ret:
|
||||
self.transport.loseConnection()
|
||||
finally:
|
||||
return
|
||||
elif method == "daemon.set_event_interest" and self.transport.sessionno in self.factory.authorized_sessions:
|
||||
# This special case is to allow clients to set which events they are
|
||||
# interested in receiving.
|
||||
# We are expecting a sequence from the client.
|
||||
try:
|
||||
if self.transport.sessionno not in self.factory.interested_events:
|
||||
self.factory.interested_events[self.transport.sessionno] = []
|
||||
self.factory.interested_events[self.transport.sessionno].extend(args[0])
|
||||
except Exception, e:
|
||||
sendError()
|
||||
else:
|
||||
self.sendData((RPC_RESPONSE, request_id, (True)))
|
||||
finally:
|
||||
return
|
||||
|
||||
if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions:
|
||||
try:
|
||||
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
|
||||
auth_level = self.factory.authorized_sessions[self.transport.sessionno][0]
|
||||
if auth_level < method_auth_requirement:
|
||||
# This session is not allowed to call this method
|
||||
log.debug("Session %s is trying to call a method it is not authorized to call!", self.transport.sessionno)
|
||||
raise NotAuthorizedError("Auth level too low: %s < %s" % (auth_level, method_auth_requirement))
|
||||
# Set the session_id in the factory so that methods can know
|
||||
# which session is calling it.
|
||||
self.factory.session_id = self.transport.sessionno
|
||||
ret = self.factory.methods[method](*args, **kwargs)
|
||||
except Exception, e:
|
||||
sendError()
|
||||
# Don't bother printing out DelugeErrors, because they are just for the client
|
||||
if not isinstance(e, DelugeError):
|
||||
log.exception("Exception calling RPC request: %s", e)
|
||||
else:
|
||||
# Check if the return value is a deferred, since we'll need to
|
||||
# wait for it to fire before sending the RPC_RESPONSE
|
||||
if isinstance(ret, defer.Deferred):
|
||||
def on_success(result):
|
||||
self.sendData((RPC_RESPONSE, request_id, result))
|
||||
return result
|
||||
|
||||
def on_fail(failure):
|
||||
try:
|
||||
failure.raiseException()
|
||||
except Exception, e:
|
||||
sendError()
|
||||
return failure
|
||||
|
||||
ret.addCallbacks(on_success, on_fail)
|
||||
else:
|
||||
self.sendData((RPC_RESPONSE, request_id, ret))
|
||||
def export(func):
|
||||
func._rpcserver_export = True
|
||||
return func
|
||||
|
||||
class RPCServer(component.Component):
|
||||
"""
|
||||
This class is used to handle rpc requests from the client. Objects are
|
||||
registered with this class and their methods are exported using the export
|
||||
decorator.
|
||||
|
||||
:param port: the port the RPCServer will listen on
|
||||
:type port: int
|
||||
:param interface: the interface to listen on, this may override the `allow_remote` setting
|
||||
:type interface: str
|
||||
:param allow_remote: set True if the server should allow remote connections
|
||||
:type allow_remote: bool
|
||||
:param listen: if False, will not start listening.. This is only useful in Classic Mode
|
||||
:type listen: bool
|
||||
"""
|
||||
|
||||
def __init__(self, port=58846, interface="", allow_remote=False, listen=True):
|
||||
def __init__(self, port):
|
||||
component.Component.__init__(self, "RPCServer")
|
||||
|
||||
self.factory = Factory()
|
||||
self.factory.protocol = DelugeRPCProtocol
|
||||
self.factory.session_id = -1
|
||||
# Get config
|
||||
self.config = deluge.configmanager.ConfigManager("core.conf")
|
||||
|
||||
# Holds the registered methods
|
||||
self.factory.methods = {}
|
||||
# Holds the session_ids and auth levels
|
||||
self.factory.authorized_sessions = {}
|
||||
# Holds the protocol objects with the session_id as key
|
||||
self.factory.session_protocols = {}
|
||||
# Holds the interested event list for the sessions
|
||||
self.factory.interested_events = {}
|
||||
if port == None:
|
||||
port = self.config["daemon_port"]
|
||||
|
||||
if not listen:
|
||||
return
|
||||
|
||||
if allow_remote:
|
||||
if self.config["allow_remote"]:
|
||||
hostname = ""
|
||||
else:
|
||||
hostname = "localhost"
|
||||
|
||||
if interface:
|
||||
hostname = interface
|
||||
|
||||
log.info("Starting DelugeRPC server %s:%s", hostname, port)
|
||||
|
||||
# Check for SSL keys and generate some if needed
|
||||
check_ssl_keys()
|
||||
|
||||
# Setup the xmlrpc server
|
||||
try:
|
||||
reactor.listenSSL(port, self.factory, ServerContextFactory(), interface=hostname)
|
||||
log.info("Starting XMLRPC server %s:%s", hostname, port)
|
||||
self.server = XMLRPCServer((hostname, port),
|
||||
requestHandler=BasicAuthXMLRPCRequestHandler,
|
||||
logRequests=False,
|
||||
allow_none=True)
|
||||
except Exception, e:
|
||||
log.info("Daemon already running or port not available..")
|
||||
log.error(e)
|
||||
sys.exit(0)
|
||||
|
||||
def register_object(self, obj, name=None):
|
||||
"""
|
||||
Registers an object to export it's rpc methods. These methods should
|
||||
be exported with the export decorator prior to registering the object.
|
||||
self.server.register_multicall_functions()
|
||||
self.server.register_introspection_functions()
|
||||
|
||||
:param obj: the object that we want to export
|
||||
:type obj: object
|
||||
:param name: the name to use, if None, it will be the class name of the object
|
||||
:type name: str
|
||||
"""
|
||||
self.server.socket.setblocking(False)
|
||||
|
||||
gobject.io_add_watch(self.server.socket.fileno(), gobject.IO_IN | gobject.IO_OUT | gobject.IO_PRI | gobject.IO_ERR | gobject.IO_HUP, self._on_socket_activity)
|
||||
|
||||
def _on_socket_activity(self, source, condition):
|
||||
"""This gets called when there is activity on the socket, ie, data to read
|
||||
or to write."""
|
||||
self.server.handle_request()
|
||||
return True
|
||||
|
||||
def register_object(self, obj, name=None):
|
||||
if not name:
|
||||
name = obj.__class__.__name__.lower()
|
||||
name = obj.__class__.__name__
|
||||
|
||||
for d in dir(obj):
|
||||
if d[0] == "_":
|
||||
continue
|
||||
if getattr(getattr(obj, d), '_rpcserver_export', False):
|
||||
log.debug("Registering method: %s", name + "." + d)
|
||||
self.factory.methods[name + "." + d] = getattr(obj, d)
|
||||
self.server.register_function(getattr(obj, d), name + "." + d)
|
||||
|
||||
def get_object_method(self, name):
|
||||
class XMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
|
||||
def get_request(self):
|
||||
"""Get the request and client address from the socket.
|
||||
We override this so that we can get the ip address of the client.
|
||||
"""
|
||||
Returns a registered method.
|
||||
request, client_address = self.socket.accept()
|
||||
self.client_address = client_address[0]
|
||||
return (request, client_address)
|
||||
|
||||
class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
def do_POST(self):
|
||||
if "authorization" in self.headers:
|
||||
auth = self.headers['authorization']
|
||||
auth = auth.replace("Basic ","")
|
||||
decoded_auth = decodestring(auth)
|
||||
# Check authentication here
|
||||
if component.get("AuthManager").authorize(*decoded_auth.split(":")):
|
||||
# User authorized, call the real do_POST now
|
||||
return SimpleXMLRPCRequestHandler.do_POST(self)
|
||||
|
||||
:param name: the name of the method, usually in the form of 'object.method'
|
||||
:type name: str
|
||||
|
||||
:returns: method
|
||||
|
||||
:raises KeyError: if `name` is not registered
|
||||
|
||||
"""
|
||||
return self.factory.methods[name]
|
||||
|
||||
def get_method_list(self):
|
||||
"""
|
||||
Returns a list of the exported methods.
|
||||
|
||||
:returns: the exported methods
|
||||
:rtype: list
|
||||
"""
|
||||
return self.factory.methods.keys()
|
||||
|
||||
def get_session_id(self):
|
||||
"""
|
||||
Returns the session id of the current RPC.
|
||||
|
||||
:returns: the session id, this will be -1 if no connections have been made
|
||||
:rtype: int
|
||||
|
||||
"""
|
||||
return self.factory.session_id
|
||||
|
||||
def get_session_user(self):
|
||||
"""
|
||||
Returns the username calling the current RPC.
|
||||
|
||||
:returns: the username of the user calling the current RPC
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
session_id = self.get_session_id()
|
||||
if session_id > -1 and session_id in self.factory.authorized_sessions:
|
||||
return self.factory.authorized_sessions[session_id][1]
|
||||
else:
|
||||
# No connections made yet
|
||||
return ""
|
||||
|
||||
def is_session_valid(self, session_id):
|
||||
"""
|
||||
Checks if the session is still valid, eg, if the client is still connected.
|
||||
|
||||
:param session_id: the session id
|
||||
:type session_id: int
|
||||
|
||||
:returns: True if the session is valid
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
return session_id in self.factory.authorized_sessions
|
||||
|
||||
def emit_event(self, event):
|
||||
"""
|
||||
Emits the event to interested clients.
|
||||
|
||||
:param event: the event to emit
|
||||
:type event: :class:`deluge.event.DelugeEvent`
|
||||
"""
|
||||
log.debug("intevents: %s", self.factory.interested_events)
|
||||
# Find sessions interested in this event
|
||||
for session_id, interest in self.factory.interested_events.items():
|
||||
if event.name in interest:
|
||||
log.debug("Emit Event: %s %s", event.name, event.args)
|
||||
# This session is interested so send a RPC_EVENT
|
||||
self.factory.session_protocols[session_id].sendData(
|
||||
(RPC_EVENT, event.name, event.args)
|
||||
)
|
||||
|
||||
def check_ssl_keys():
|
||||
"""
|
||||
Check for SSL cert/key and create them if necessary
|
||||
"""
|
||||
ssl_dir = deluge.configmanager.get_config_dir("ssl")
|
||||
if not os.path.exists(ssl_dir):
|
||||
# The ssl folder doesn't exist so we need to create it
|
||||
os.makedirs(ssl_dir)
|
||||
generate_ssl_keys()
|
||||
else:
|
||||
for f in ("daemon.pkey", "daemon.cert"):
|
||||
if not os.path.exists(os.path.join(ssl_dir, f)):
|
||||
generate_ssl_keys()
|
||||
break
|
||||
|
||||
def generate_ssl_keys():
|
||||
"""
|
||||
This method generates a new SSL key/cert.
|
||||
"""
|
||||
digest = "sha256"
|
||||
# Generate key pair
|
||||
pkey = crypto.PKey()
|
||||
pkey.generate_key(crypto.TYPE_RSA, 2048)
|
||||
|
||||
# Generate cert request
|
||||
req = crypto.X509Req()
|
||||
subj = req.get_subject()
|
||||
setattr(subj, "CN", "Deluge Daemon")
|
||||
req.set_pubkey(pkey)
|
||||
req.sign(pkey, digest)
|
||||
|
||||
# Generate certificate
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(0)
|
||||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years
|
||||
cert.set_issuer(req.get_subject())
|
||||
cert.set_subject(req.get_subject())
|
||||
cert.set_pubkey(req.get_pubkey())
|
||||
cert.sign(pkey, digest)
|
||||
|
||||
# Write out files
|
||||
ssl_dir = deluge.configmanager.get_config_dir("ssl")
|
||||
with open(os.path.join(ssl_dir, "daemon.pkey"), "w") as _file:
|
||||
_file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
with open(os.path.join(ssl_dir, "daemon.cert"), "w") as _file:
|
||||
_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
# Make the files only readable by this user
|
||||
for f in ("daemon.pkey", "daemon.cert"):
|
||||
os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE)
|
||||
# if cannot authenticate, end the connection
|
||||
self.send_response(401)
|
||||
self.end_headers()
|
||||
|
108
deluge/core/signalmanager.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#
|
||||
# signalmanager.py
|
||||
#
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge 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 deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
|
||||
import deluge.xmlrpclib as xmlrpclib
|
||||
import socket
|
||||
import struct
|
||||
|
||||
import gobject
|
||||
|
||||
import deluge.component as component
|
||||
from deluge.log import LOG as log
|
||||
|
||||
class Transport(xmlrpclib.Transport):
|
||||
def make_connection(self, host):
|
||||
# create a HTTP connection object from a host descriptor
|
||||
import httplib
|
||||
host, extra_headers, x509 = self.get_host_info(host)
|
||||
h = httplib.HTTP(host)
|
||||
h._conn.connect()
|
||||
h._conn.sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
|
||||
struct.pack('ii', 1, 0))
|
||||
return h
|
||||
|
||||
class SignalManager(component.Component):
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "SignalManager")
|
||||
self.clients = {}
|
||||
self.handlers = {}
|
||||
|
||||
def shutdown(self):
|
||||
self.clients = {}
|
||||
self.handlers = {}
|
||||
|
||||
def register_handler(self, signal, handler):
|
||||
"""Registers a handler for signals"""
|
||||
if signal not in self.handler.keys():
|
||||
self.handler[signal] = []
|
||||
|
||||
self.handler[signal].append(handler)
|
||||
log.debug("Registered signal handler for %s", signal)
|
||||
|
||||
def deregister_handler(self, handler):
|
||||
"""De-registers the 'handler' function from all signal types."""
|
||||
# Iterate through all handlers and remove 'handler' where found
|
||||
for (key, value) in self.handlers:
|
||||
if handler in value:
|
||||
value.remove(handler)
|
||||
|
||||
def deregister_client(self, address):
|
||||
"""Deregisters a client"""
|
||||
log.debug("Deregistering %s as a signal reciever..", address)
|
||||
for client in self.clients.keys():
|
||||
if client.split("//")[1].split(":")[0] == address:
|
||||
del self.clients[client]
|
||||
break
|
||||
|
||||
def register_client(self, address, port):
|
||||
"""Registers a client to emit signals to."""
|
||||
uri = "http://" + str(address) + ":" + str(port)
|
||||
log.debug("Registering %s as a signal reciever..", uri)
|
||||
self.clients[uri] = xmlrpclib.ServerProxy(uri, transport=Transport())
|
||||
#self.clients[uri].socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
|
||||
# struct.pack('ii', 1, 0))
|
||||
|
||||
def emit(self, signal, *data):
|
||||
# Run the handlers
|
||||
if signal in self.handlers.keys():
|
||||
for handler in self.handlers[signal]:
|
||||
handler(*data)
|
||||
|
||||
for uri in self.clients:
|
||||
gobject.idle_add(self._emit, uri, signal, 1, *data)
|
||||
|
||||
def _emit(self, uri, signal, count, *data):
|
||||
if uri not in self.clients:
|
||||
return
|
||||
client = self.clients[uri]
|
||||
try:
|
||||
client.emit_signal(signal, *data)
|
||||
except (socket.error, Exception), e:
|
||||
log.warning("Unable to emit signal to client %s: %s (%d)", client, e, count)
|
||||
if count < 30:
|
||||
gobject.timeout_add(1000, self._emit, uri, signal, count + 1, *data)
|
||||
else:
|
||||
log.info("Removing %s because it couldn't be reached..", uri)
|
||||
del self.clients[uri]
|
39
deluge/data/GEOIP_LICENSE.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
There are two licenses, one for the C library software, and one for
|
||||
the database.
|
||||
|
||||
SOFTWARE LICENSE (C library)
|
||||
|
||||
The GeoIP C Library is licensed under the LGPL. For details see
|
||||
the COPYING file.
|
||||
|
||||
OPEN DATA LICENSE (GeoLite Country and GeoLite City databases)
|
||||
|
||||
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
|
||||
|
||||
All advertising materials and documentation mentioning features or use of
|
||||
this database must display the following acknowledgment:
|
||||
"This product includes GeoLite data created by MaxMind, available from
|
||||
http://maxmind.com/"
|
||||
|
||||
Redistribution and use with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
1. Redistributions must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
2. All advertising materials and documentation mentioning features or use of
|
||||
this database must display the following acknowledgement:
|
||||
"This product includes GeoLite data created by MaxMind, available from
|
||||
http://maxmind.com/"
|
||||
3. "MaxMind" may not be used to endorse or promote products derived from this
|
||||
database without specific prior written permission.
|
||||
|
||||
THIS DATABASE IS PROVIDED BY MAXMIND, INC ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL MAXMIND BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
DATABASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
BIN
deluge/data/GeoIP.dat
Normal file
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
2
deluge/data/pixmaps/flags/FLAGS_LICENCE
Normal file
@@ -0,0 +1,2 @@
|
||||
From: http://famfamfam.com/lab/icons/flags/
|
||||
"These flag icons are available for free use for any purpose with no requirement for attribution."
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 275 B After Width: | Height: | Size: 408 B |
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 600 B |
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 428 B |
Before Width: | Height: | Size: 582 B After Width: | Height: | Size: 836 B |
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 506 B |
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 647 B |
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 403 B |
Before Width: | Height: | Size: 554 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 524 B |
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 663 B |
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 449 B |
Before Width: | Height: | Size: 333 B After Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 548 B After Width: | Height: | Size: 675 B |
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 486 B |
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 639 B |
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 458 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 526 B |
Before Width: | Height: | Size: 460 B After Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 443 B |
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 514 B |
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 600 B |
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 480 B After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 528 B |
Before Width: | Height: | Size: 443 B After Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 521 B |
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 300 B After Width: | Height: | Size: 453 B |
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 450 B |
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 525 B |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 472 B |