3
from urllib import quote
4
from urllib2 import URLError
5
from flexget.utils.tools import urlopener
6
from flexget.utils.bittorrent import bdecode
7
from flexget.plugin import register_plugin, priority
9
log = logging.getLogger('torrent_alive')
12
class TorrentAliveThread(threading.Thread):
14
def __init__(self, tracker, info_hash):
15
threading.Thread.__init__(self)
16
self.tracker = tracker
17
self.info_hash = info_hash
18
self.tracker_seeds = 0
22
self.tracker_seeds = get_tracker_seeds(self.tracker, self.info_hash)
24
log.debug('Error scraping %s: %s' % (self.tracker, e))
25
self.tracker_seeds = 0
27
log.debug('%s seeds found from %s' % (self.tracker_seeds, get_scrape_url(self.tracker, self.info_hash)))
30
def max_seeds_from_threads(threads):
32
Joins the threads and returns the maximum seeds found from any of them.
34
:param threads: A list of started `TorrentAliveThread`s
35
:return: Maximum seeds found from any of the threads
38
for background in threads:
39
log.debug('Coming up next: %s' % background.tracker)
41
seeds = max(seeds, background.tracker_seeds)
42
log.debug('Current hightest number of seeds found: %s' % seeds)
46
def get_scrape_url(tracker_url, info_hash):
47
if 'announce' in tracker_url:
48
result = tracker_url.replace('announce', 'scrape')
50
log.debug('`announce` not contained in tracker url, guessing scrape address.')
51
result = tracker_url + '/scrape'
52
if result.startswith('udp:'):
53
result = result.replace('udp:', 'http:')
54
result += '&' if '?' in result else '?'
55
result += 'info_hash=%s' % quote(info_hash.decode('hex'))
59
def get_tracker_seeds(url, info_hash):
60
url = get_scrape_url(url, info_hash)
62
log.debug('if not url is true returning 0')
64
log.debug('Checking for seeds from %s' % url)
66
data = bdecode(urlopener(url, log, retries=1, timeout=10).read()).get('files')
67
except SyntaxError, e:
68
log.warning('Error decoding tracker response: %s' % e)
71
log.debug('the moose is loose')
73
log.debug('get_tracker_seeds is returning: %s' % data.values()[0]['complete'])
74
return data.values()[0]['complete']
77
class TorrentAlive(object):
80
from flexget import validator
81
root = validator.factory()
82
root.accept('boolean')
83
root.accept('integer')
87
def on_feed_filter(self, feed, config):
90
for entry in feed.entries:
91
if 'torrent_seeds' in entry and entry['torrent_seeds'] < config:
92
feed.reject(entry, reason='Had < %d required seeds. (%s)' % (config, entry['torrent_seeds']))
94
# Run on output phase so that we let torrent plugin output modified torrent file first
96
def on_feed_output(self, feed, config):
100
min_seeds = int(config)
102
for entry in feed.accepted:
103
# TODO: shouldn't this still check min_seeds ?
104
if entry.get('torrent_seeds'):
105
log.debug('Not checking trackers for seeds, as torrent_seeds is already filled.')
107
log.debug('Checking for seeds for %s:' % entry['title'])
108
torrent = entry.get('torrent')
111
info_hash = torrent.get_info_hash()
112
announce_list = torrent.content.get('announce-list')
114
# Multitracker torrent
116
for tier in announce_list:
118
background = TorrentAliveThread(tracker, info_hash)
121
threadlist.append(background)
122
except threading.ThreadError:
123
# If we can't start a new thread, wait for current ones to complete and continue
124
log.debug('Reached max threads, finishing current threads.')
125
seeds = max(seeds, max_seeds_from_threads(threadlist))
127
threadlist = [background]
128
log.debug('Started thread to scrape %s with info hash %s' % (tracker, info_hash))
130
seeds = max(seeds, max_seeds_from_threads(threadlist))
131
log.debug('Highest number of seeds found: %s' % seeds)
134
tracker = torrent.content['announce']
136
seeds = get_tracker_seeds(tracker, info_hash)
138
log.debug('Error scraping %s: %s' % (tracker, e))
141
if seeds < min_seeds:
142
feed.reject(entry, reason='Tracker(s) had < %s required seeds. (%s)' % (min_seeds, seeds),
143
remember_time='1 hour')
146
log.debug('Found %i seeds from trackers' % seeds)
148
register_plugin(TorrentAlive, 'torrent_alive', api_ver=2)