Begin rewrite of blocklist (core) using twisted.
This commit is contained in:
parent
d317d5c857
commit
8e69a82881
|
@ -2,6 +2,7 @@
|
||||||
# core.py
|
# core.py
|
||||||
#
|
#
|
||||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||||
|
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||||
#
|
#
|
||||||
# Deluge is free software.
|
# Deluge is free software.
|
||||||
#
|
#
|
||||||
|
@ -33,20 +34,20 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
import urllib
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor, threads
|
||||||
|
from twisted.web import error
|
||||||
|
|
||||||
from deluge.log import LOG as log
|
from deluge.log import LOG as log
|
||||||
from deluge.plugins.pluginbase import CorePluginBase
|
from deluge.plugins.pluginbase import CorePluginBase
|
||||||
import deluge.component as component
|
import deluge.component as component
|
||||||
import deluge.configmanager
|
import deluge.configmanager
|
||||||
from deluge.core.rpcserver import export
|
from deluge.core.rpcserver import export
|
||||||
|
from deluge.httpdownloader import download_file
|
||||||
|
|
||||||
from peerguardian import PGReader, PGException
|
from peerguardian import PGReader, PGException
|
||||||
from text import TextReader, GZMuleReader, PGZip, PGTextReaderGzip
|
from text import TextReader, GZMuleReader, PGZip, PGTextReaderGzip
|
||||||
|
@ -55,13 +56,11 @@ DEFAULT_PREFS = {
|
||||||
"url": "http://deluge-torrent.org/blocklist/nipfilter.dat.gz",
|
"url": "http://deluge-torrent.org/blocklist/nipfilter.dat.gz",
|
||||||
"load_on_start": False,
|
"load_on_start": False,
|
||||||
"check_after_days": 4,
|
"check_after_days": 4,
|
||||||
"listtype": "gzmule",
|
"list_type": "gzmule",
|
||||||
|
"last_update": "",
|
||||||
|
"list_size": 0,
|
||||||
"timeout": 180,
|
"timeout": 180,
|
||||||
"try_times": 3,
|
"try_times": 3,
|
||||||
"file_type": "",
|
|
||||||
"file_url": "",
|
|
||||||
"file_date": "",
|
|
||||||
"file_size": 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FORMATS = {
|
FORMATS = {
|
||||||
|
@ -76,8 +75,6 @@ class Core(CorePluginBase):
|
||||||
def enable(self):
|
def enable(self):
|
||||||
log.debug('Blocklist: Plugin enabled..')
|
log.debug('Blocklist: Plugin enabled..')
|
||||||
|
|
||||||
self.is_downloading = False
|
|
||||||
self.is_importing = False
|
|
||||||
self.has_imported = False
|
self.has_imported = False
|
||||||
self.up_to_date = False
|
self.up_to_date = False
|
||||||
self.num_blocked = 0
|
self.num_blocked = 0
|
||||||
|
@ -87,11 +84,14 @@ class Core(CorePluginBase):
|
||||||
|
|
||||||
self.config = deluge.configmanager.ConfigManager("blocklist.conf", DEFAULT_PREFS)
|
self.config = deluge.configmanager.ConfigManager("blocklist.conf", DEFAULT_PREFS)
|
||||||
if self.config["load_on_start"]:
|
if self.config["load_on_start"]:
|
||||||
self.import_list()
|
# TODO: Check if been more than check_after_days
|
||||||
|
self.use_cache = True
|
||||||
|
d = self.import_list()
|
||||||
|
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||||
|
|
||||||
# This function is called every 'check_after_days' days, to download
|
# This function is called every 'check_after_days' days, to download
|
||||||
# and import a new list if needed.
|
# and import a new list if needed.
|
||||||
self.update_timer = LoopingCall(self.download_blocklist)
|
self.update_timer = LoopingCall(self.check_import)
|
||||||
self.update_timer.start(self.config["check_after_days"] * 24 * 60 * 60)
|
self.update_timer.start(self.config["check_after_days"] * 24 * 60 * 60)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
|
@ -105,15 +105,22 @@ class Core(CorePluginBase):
|
||||||
|
|
||||||
## Exported RPC methods ###
|
## Exported RPC methods ###
|
||||||
@export()
|
@export()
|
||||||
def download_list(self, _import=False):
|
def check_import(self, force=False):
|
||||||
"""Download the blocklist specified in the config as url"""
|
"""Imports latest blocklist specified by blocklist url.
|
||||||
self.download_blocklist(_import)
|
Only downloads/imports if necessary or forced."""
|
||||||
|
|
||||||
@export()
|
# Reset variables
|
||||||
def import_list(self, force=False):
|
self.force_download = force
|
||||||
"""Import the blocklist from the blocklist.cache, if load is True, then
|
self.use_cache = False
|
||||||
it will download the blocklist file if needed."""
|
self.failed_attempts = 0
|
||||||
reactor.callInThread(self.import_blocklist, force=force)
|
|
||||||
|
# Start callback chain
|
||||||
|
d = self.download_list()
|
||||||
|
d.addCallbacks(self.on_download_complete, self.on_download_error)
|
||||||
|
d.addCallback(self.import_list)
|
||||||
|
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||||
|
|
||||||
|
return d
|
||||||
|
|
||||||
@export()
|
@export()
|
||||||
def get_config(self):
|
def get_config(self):
|
||||||
|
@ -137,97 +144,27 @@ class Core(CorePluginBase):
|
||||||
else:
|
else:
|
||||||
status["state"] = "Idle"
|
status["state"] = "Idle"
|
||||||
|
|
||||||
status["up_to_date"] = self.up_to_date
|
|
||||||
status["num_blocked"] = self.num_blocked
|
status["num_blocked"] = self.num_blocked
|
||||||
status["file_progress"] = self.file_progress
|
status["file_progress"] = self.file_progress
|
||||||
status["file_type"] = self.config["file_type"]
|
status["file_type"] = self.config["list_type"]
|
||||||
status["file_url"] = self.config["file_url"]
|
status["file_url"] = self.config["url"]
|
||||||
status["file_size"] = self.config["file_size"]
|
status["file_size"] = self.config["list_size"]
|
||||||
status["file_date"] = self.config["file_date"]
|
status["file_date"] = self.config["last_update"]
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
####
|
####
|
||||||
|
|
||||||
|
def update_info(self, blocklist):
|
||||||
|
"""Updates blocklist info"""
|
||||||
|
self.config["last_update"] = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||||
|
self.config["list_size"] = os.path.getsize(blocklist)
|
||||||
|
|
||||||
def on_download_blocklist(self, load):
|
def download_list(self, url=None):
|
||||||
self.is_downloading = False
|
|
||||||
if load:
|
|
||||||
self.import_list()
|
|
||||||
|
|
||||||
def import_blocklist(self, force=False):
|
|
||||||
"""Imports the downloaded blocklist into the session"""
|
|
||||||
if self.is_downloading:
|
|
||||||
return
|
|
||||||
|
|
||||||
if force or self.need_new_blocklist():
|
|
||||||
self.download_blocklist(True)
|
|
||||||
return
|
|
||||||
|
|
||||||
# If we have a newly downloaded file, lets try that before the .cache
|
|
||||||
if os.path.exists(deluge.configmanager.get_config_dir("blocklist.download")):
|
|
||||||
bl_file = deluge.configmanager.get_config_dir("blocklist.download")
|
|
||||||
using_download = True
|
|
||||||
elif self.has_imported:
|
|
||||||
# Blocklist is up to date so doesn't need to be imported
|
|
||||||
log.debug("Latest blocklist is already imported")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
bl_file = deluge.configmanager.get_config_dir("blocklist.cache")
|
|
||||||
using_download = False
|
|
||||||
|
|
||||||
self.is_importing = True
|
|
||||||
log.debug("Reset IP Filter..")
|
|
||||||
component.get("Core").reset_ip_filter()
|
|
||||||
|
|
||||||
self.num_blocked = 0
|
|
||||||
|
|
||||||
# Open the file for reading
|
|
||||||
try:
|
|
||||||
read_list = FORMATS[self.config["listtype"]][1](bl_file)
|
|
||||||
except Exception, e:
|
|
||||||
log.debug("Unable to read blocklist file: %s", e)
|
|
||||||
self.is_importing = False
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
log.debug("Blocklist import starting..")
|
|
||||||
ips = read_list.next()
|
|
||||||
while ips:
|
|
||||||
self.core.block_ip_range(ips)
|
|
||||||
self.num_blocked += 1
|
|
||||||
ips = read_list.next()
|
|
||||||
read_list.close()
|
|
||||||
except Exception, e:
|
|
||||||
log.debug("Exception during import: %s", e)
|
|
||||||
else:
|
|
||||||
log.debug("Blocklist import complete!")
|
|
||||||
# The import was successful so lets move this to blocklist.cache
|
|
||||||
if using_download:
|
|
||||||
log.debug("Moving blocklist.download to blocklist.cache")
|
|
||||||
shutil.move(bl_file, deluge.configmanager.get_config_dir("blocklist.cache"))
|
|
||||||
# Set information about the file
|
|
||||||
self.config["file_type"] = self.config["listtype"]
|
|
||||||
self.config["file_url"] = self.config["url"]
|
|
||||||
|
|
||||||
self.is_importing = False
|
|
||||||
self.has_imported = True
|
|
||||||
|
|
||||||
def download_blocklist(self, load=False):
|
|
||||||
"""Runs download_blocklist_thread() in a thread and calls on_download_blocklist
|
|
||||||
when finished. If load is True, then we will import the blocklist
|
|
||||||
upon download completion."""
|
|
||||||
if self.is_importing:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.is_downloading = True
|
|
||||||
reactor.callInThread(self.download_blocklist_thread, self.on_download_blocklist, load)
|
|
||||||
|
|
||||||
def download_blocklist_thread(self, callback, load):
|
|
||||||
"""Downloads the blocklist specified by 'url' in the config"""
|
"""Downloads the blocklist specified by 'url' in the config"""
|
||||||
def on_retrieve_data(count, block_size, total_blocks):
|
def on_retrieve_data(data, current_length, total_length):
|
||||||
if total_blocks:
|
if total_length:
|
||||||
fp = float(count * block_size) / total_blocks
|
fp = float(current_length) / total_length
|
||||||
if fp > 1.0:
|
if fp > 1.0:
|
||||||
fp = 1.0
|
fp = 1.0
|
||||||
else:
|
else:
|
||||||
|
@ -238,78 +175,100 @@ class Core(CorePluginBase):
|
||||||
import socket
|
import socket
|
||||||
socket.setdefaulttimeout(self.config["timeout"])
|
socket.setdefaulttimeout(self.config["timeout"])
|
||||||
|
|
||||||
for i in xrange(self.config["try_times"]):
|
headers = {}
|
||||||
log.debug("Attempting to download blocklist %s", self.config["url"])
|
if not url:
|
||||||
try:
|
url = self.config["url"]
|
||||||
(filename, headers) = urllib.urlretrieve(
|
|
||||||
self.config["url"],
|
blocklist = deluge.configmanager.get_config_dir("blocklist.cache")
|
||||||
deluge.configmanager.get_config_dir("blocklist.download"),
|
if os.path.exists(blocklist) and not self.force_download:
|
||||||
on_retrieve_data)
|
last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime(blocklist))
|
||||||
except Exception, e:
|
headers['If-Modified-Since'] = last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||||
log.debug("Error downloading blocklist: %s", e)
|
|
||||||
os.remove(deluge.configmanager.get_config_dir("blocklist.download"))
|
log.debug("Attempting to download blocklist %s", url)
|
||||||
continue
|
self.is_downloading = True
|
||||||
|
return download_file(url, deluge.configmanager.get_config_dir("blocklist.download"), headers)
|
||||||
|
|
||||||
|
def on_download_complete(self, result):
|
||||||
|
"""Runs any download clean up functions"""
|
||||||
|
log.debug("Blocklist download complete!")
|
||||||
|
self.is_downloading = False
|
||||||
|
return threads.deferToThread(self.update_info,
|
||||||
|
deluge.configmanager.ConfigManager("blocklist.download"))
|
||||||
|
|
||||||
|
def on_download_error(self, f):
|
||||||
|
"""Recovers from download error"""
|
||||||
|
self.is_downloading = False
|
||||||
|
error_msg = f.getErrorMessage()
|
||||||
|
d = None
|
||||||
|
if f.check(error.PageRedirect):
|
||||||
|
# Handle redirect errors
|
||||||
|
location = error_msg.split(" to ")[1]
|
||||||
|
if "Moved Permanently" in error:
|
||||||
|
log.debug("Setting blocklist url to %s" % location)
|
||||||
|
self.config["url"] = location
|
||||||
|
f.trap(f.type)
|
||||||
|
d = self.download_list(url=location)
|
||||||
|
d.addCallbacks(self.on_download_complete, self.on_download_error)
|
||||||
else:
|
else:
|
||||||
log.debug("Blocklist successfully downloaded..")
|
if "Not Modified" in error_msg:
|
||||||
self.config["file_date"] = datetime.datetime.strptime(headers["last-modified"],"%a, %d %b %Y %H:%M:%S GMT").ctime()
|
log.debug("Blocklist is up-to-date!")
|
||||||
self.config["file_size"] = long(headers["content-length"])
|
d = threads.deferToThread(update_info,
|
||||||
reactor.callLater(0, callback, load)
|
deluge.configmanager.ConfigManager("blocklist.cache"))
|
||||||
return
|
self.use_cache = True
|
||||||
|
f.trap(f.type)
|
||||||
|
elif self.failed_attempts < self.config["try_times"]:
|
||||||
|
log.warning("Blocklist download failed!")
|
||||||
|
self.failed_attempts += 1
|
||||||
|
f.trap(f.type)
|
||||||
|
return d
|
||||||
|
|
||||||
def need_new_blocklist(self):
|
def import_list(self, force=False):
|
||||||
"""Returns True if a new blocklist file should be downloaded"""
|
"""Imports the downloaded blocklist into the session"""
|
||||||
|
if self.use_cache and self.has_imported:
|
||||||
# Assume blocklist is not up to date
|
log.debug("Latest blocklist is already imported")
|
||||||
self.up_to_date = False
|
|
||||||
|
|
||||||
# Check to see if we've just downloaded a new blocklist
|
|
||||||
if os.path.exists(deluge.configmanager.get_config_dir("blocklist.download")):
|
|
||||||
log.debug("New blocklist waiting to be imported")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if os.path.exists(deluge.configmanager.get_config_dir("blocklist.cache")):
|
|
||||||
# Check current block lists time stamp and decide if it needs to be replaced
|
|
||||||
list_stats = os.stat(deluge.configmanager.get_config_dir("blocklist.cache"))
|
|
||||||
list_size = long(list_stats.st_size)
|
|
||||||
list_checked = datetime.datetime.fromtimestamp(list_stats.st_mtime)
|
|
||||||
try:
|
|
||||||
list_time = datetime.datetime.strptime(self.config["file_date"], "%a %b %d %H:%M:%S %Y")
|
|
||||||
except:
|
|
||||||
list_time = list_checked
|
|
||||||
current_time = datetime.datetime.today()
|
|
||||||
else:
|
|
||||||
log.debug("Blocklist doesn't exist")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If local blocklist file exists but nothing is in it
|
self.is_importing = True
|
||||||
if list_size == 0:
|
log.debug("Reset IP Filter..")
|
||||||
log.debug("Empty blocklist")
|
# Does this return a deferred?
|
||||||
return True
|
self.core.reset_ip_filter()
|
||||||
|
|
||||||
# If blocklist has just started up, check for updates if over x days
|
self.num_blocked = 0
|
||||||
if not self.has_imported and current_time < (list_checked + datetime.timedelta(days=self.config["check_after_days"])):
|
|
||||||
log.debug("Blocklist doesn't need checking yet")
|
|
||||||
return False
|
|
||||||
|
|
||||||
import socket
|
# TODO: Make non-blocking (use deferToThread)
|
||||||
socket.setdefaulttimeout(self.config["timeout"])
|
|
||||||
|
|
||||||
try:
|
# Open the file for reading
|
||||||
# Get remote blocklist time stamp and size
|
read_list = FORMATS[self.config["listtype"]][1](bl_file)
|
||||||
remote_stats = urllib.urlopen(self.config["url"]).info()
|
log.debug("Blocklist import starting..")
|
||||||
remote_size = long(remote_stats["content-length"])
|
ips = read_list.next()
|
||||||
remote_time = datetime.datetime.strptime(remote_stats["last-modified"],"%a, %d %b %Y %H:%M:%S GMT")
|
while ips:
|
||||||
except Exception, e:
|
self.core.block_ip_range(ips)
|
||||||
log.debug("Unable to get blocklist stats: %s", e)
|
self.num_blocked += 1
|
||||||
return False
|
ips = read_list.next()
|
||||||
|
read_list.close()
|
||||||
|
|
||||||
# Check if remote blocklist is newer (in date or size)
|
def on_import_complete(self, result):
|
||||||
if list_time < remote_time or list_size < remote_size:
|
"""Runs any import clean up functions"""
|
||||||
log.debug("Newer blocklist exists (%s & %d vs %s & %d)", remote_time, remote_size, list_time, list_size)
|
d = None
|
||||||
return True
|
self.is_importing = False
|
||||||
|
self.has_imported = True
|
||||||
|
log.debug("Blocklist import complete!")
|
||||||
|
# Move downloaded blocklist to cache
|
||||||
|
if not self.use_cache:
|
||||||
|
d = threads.deferToThread(shutil.move,
|
||||||
|
deluge.configmanager.ConfigManager("blocklist.download"),
|
||||||
|
deluge.configmanager.ConfigManager("blocklist.cache"))
|
||||||
|
return d
|
||||||
|
|
||||||
# Update last modified time of blocklist
|
def on_import_error(self, f):
|
||||||
os.utime(deluge.configmanager.get_config_dir("blocklist.cache"), None)
|
"""Recovers from import error"""
|
||||||
self.up_to_date = True
|
d = None
|
||||||
log.debug("Blocklist is up to date")
|
self.is_importing = False
|
||||||
return False
|
blocklist = deluge.configmanager.get_config_dir("blocklist.cache")
|
||||||
|
# If we have a backup and we haven't already used it
|
||||||
|
if os.path.exists(blocklist) and not self.use_cache:
|
||||||
|
e = f.trap(error.Error, IOError, TextException, PGException)
|
||||||
|
log.warning("Error reading blocklist: ", e)
|
||||||
|
d = self.import_list()
|
||||||
|
d.addCallbacks(on_import_complete, on_import_error)
|
||||||
|
return d
|
||||||
|
|
Loading…
Reference in New Issue