flexget.plugins.filter.torrent_alive
Covered: 41 lines
Missed: 79 lines
Skipped 29 lines
Percent: 34 %
  1
import logging
  2
import threading
  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
 20
    def run(self):
 21
        try:
 22
            self.tracker_seeds = get_tracker_seeds(self.tracker, self.info_hash)
 23
        except URLError, e:
 24
            log.debug('Error scraping %s: %s' % (self.tracker, e))
 25
            self.tracker_seeds = 0
 26
        else:
 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):
 31
    """
 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
 36
    """
 37
    seeds = 0
 38
    for background in threads:
 39
        log.debug('Coming up next: %s' % background.tracker)
 40
        background.join()
 41
        seeds = max(seeds, background.tracker_seeds)
 42
        log.debug('Current hightest number of seeds found: %s' % seeds)
 43
    return seeds
 46
def get_scrape_url(tracker_url, info_hash):
 47
    if 'announce' in tracker_url:
 48
        result = tracker_url.replace('announce', 'scrape')
 49
    else:
 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'))
 56
    return result
 59
def get_tracker_seeds(url, info_hash):
 60
    url = get_scrape_url(url, info_hash)
 61
    if not url:
 62
        log.debug('if not url is true returning 0')
 63
        return 0
 64
    log.debug('Checking for seeds from %s' % url)
 65
    try:
 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)
 69
        return 0
 70
    if not data:
 71
        log.debug('the moose is loose')
 72
        return 0
 73
    log.debug('get_tracker_seeds is returning: %s' % data.values()[0]['complete'])
 74
    return data.values()[0]['complete']
 77
class TorrentAlive(object):
 79
    def validator(self):
 80
        from flexget import validator
 81
        root = validator.factory()
 82
        root.accept('boolean')
 83
        root.accept('integer')
 84
        return root
 86
    @priority(150)
 87
    def on_feed_filter(self, feed, config):
 88
        if not config:
 89
            return
 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']))
 95
    @priority(250)
 96
    def on_feed_output(self, feed, config):
 97
        if not config:
 98
            return
100
        min_seeds = int(config)
102
        for entry in feed.accepted:
104
            if entry.get('torrent_seeds'):
105
                log.debug('Not checking trackers for seeds, as torrent_seeds is already filled.')
106
                continue
107
            log.debug('Checking for seeds for %s:' % entry['title'])
108
            torrent = entry.get('torrent')
109
            if torrent:
110
                seeds = 0
111
                info_hash = torrent.get_info_hash()
112
                announce_list = torrent.content.get('announce-list')
113
                if announce_list:
115
                    threadlist = []
116
                    for tier in announce_list:
117
                        for tracker in tier:
118
                            background = TorrentAliveThread(tracker, info_hash)
119
                            try:
120
                                background.start()
121
                                threadlist.append(background)
122
                            except threading.ThreadError:
124
                                log.debug('Reached max threads, finishing current threads.')
125
                                seeds = max(seeds, max_seeds_from_threads(threadlist))
126
                                background.start()
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)
132
                else:
134
                    tracker = torrent.content['announce']
135
                    try:
136
                        seeds = get_tracker_seeds(tracker, info_hash)
137
                    except URLError, e:
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')
144
                    feed.rerun()
145
                else:
146
                    log.debug('Found %i seeds from trackers' % seeds)
148
register_plugin(TorrentAlive, 'torrent_alive', api_ver=2)