summaryrefslogtreecommitdiffstats
path: root/module/network
diff options
context:
space:
mode:
Diffstat (limited to 'module/network')
-rw-r--r--module/network/Browser.py9
-rw-r--r--module/network/Bucket.py12
-rw-r--r--module/network/CookieJar.py20
-rw-r--r--module/network/HTTPChunk.py25
-rw-r--r--module/network/HTTPDownload.py31
-rw-r--r--module/network/HTTPRequest.py38
-rw-r--r--module/network/RequestFactory.py31
-rw-r--r--module/network/XDCCRequest.py2
8 files changed, 96 insertions, 72 deletions
diff --git a/module/network/Browser.py b/module/network/Browser.py
index d68a23687..9cf6c2f30 100644
--- a/module/network/Browser.py
+++ b/module/network/Browser.py
@@ -16,7 +16,7 @@ class Browser(object):
self.options = options #holds pycurl options
self.bucket = bucket
- self.cj = None # needs to be setted later
+ self.cj = None # needs to be set later
self._size = 0
self.renewHTTPRequest()
@@ -55,6 +55,13 @@ class Browser(object):
return 0
@property
+ def name(self):
+ if self.dl:
+ return self.dl.name
+ else:
+ return ""
+
+ @property
def arrived(self):
if self.dl:
return self.dl.arrived
diff --git a/module/network/Bucket.py b/module/network/Bucket.py
index 69da277ae..db67faa4a 100644
--- a/module/network/Bucket.py
+++ b/module/network/Bucket.py
@@ -20,15 +20,18 @@
from time import time
from threading import Lock
+# 10kb minimum rate
+MIN_RATE = 10240
+
class Bucket:
def __init__(self):
- self.rate = 0
+ self.rate = 0 # bytes per second, maximum targeted throughput
self.tokens = 0
self.timestamp = time()
self.lock = Lock()
def __nonzero__(self):
- return False if self.rate < 10240 else True
+ return False if self.rate < MIN_RATE else True
def setRate(self, rate):
self.lock.acquire()
@@ -36,8 +39,8 @@ class Bucket:
self.lock.release()
def consumed(self, amount):
- """ return time the process have to sleep, after consumed specified amount """
- if self.rate < 10240: return 0 #min. 10kb, may become unresponsive otherwise
+ """ return the time the process has to sleep, after it consumed a specified amount """
+ if self.rate < MIN_RATE: return 0 #May become unresponsive otherwise
self.lock.acquire()
self.calc_tokens()
@@ -47,7 +50,6 @@ class Bucket:
time = -self.tokens/float(self.rate)
else:
time = 0
-
self.lock.release()
return time
diff --git a/module/network/CookieJar.py b/module/network/CookieJar.py
index c05812334..a020d6f9e 100644
--- a/module/network/CookieJar.py
+++ b/module/network/CookieJar.py
@@ -20,10 +20,12 @@
from time import time
class CookieJar():
- def __init__(self, pluginname, account=None):
+ def __init__(self, pluginname):
self.cookies = {}
- self.plugin = pluginname
- self.account = account
+ self.pluginname = pluginname
+
+ def __repr__(self):
+ return ("<CookieJar plugin=%s>\n\t" % self.pluginname) + "\n\t".join(self.cookies.values())
def addCookies(self, clist):
for c in clist:
@@ -33,18 +35,18 @@ class CookieJar():
def getCookies(self):
return self.cookies.values()
- def parseCookie(self, name):
+ def getCookie(self, name):
if name in self.cookies:
return self.cookies[name].split("\t")[6]
else:
return None
- def getCookie(self, name):
- return self.parseCookie(name)
+ def setCookie(self, domain, name, value, path="/", exp=None):
+ if not exp: exp = time() + 3600 * 24 * 180
- def setCookie(self, domain, name, value, path="/", exp=time()+3600*24*180):
- s = ".%s TRUE %s FALSE %s %s %s" % (domain, path, exp, name, value)
+ # dot makes it valid on all subdomains
+ s = ".%s TRUE %s FALSE %s %s %s" % (domain.strip("."), path, exp, name, value)
self.cookies[name] = s
def clear(self):
- self.cookies = {}
+ self.cookies = {} \ No newline at end of file
diff --git a/module/network/HTTPChunk.py b/module/network/HTTPChunk.py
index b637aef32..d17177ee7 100644
--- a/module/network/HTTPChunk.py
+++ b/module/network/HTTPChunk.py
@@ -20,10 +20,13 @@ from os import remove, stat, fsync
from os.path import exists
from time import sleep
from re import search
-from module.utils import fs_encode
+
import codecs
import pycurl
+from module.utils import remove_chars
+from module.utils.fs import fs_encode
+
from HTTPRequest import HTTPRequest
class WrongFormat(Exception):
@@ -204,7 +207,7 @@ class HTTPChunk(HTTPRequest):
def writeHeader(self, buf):
self.header += buf
- #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers
+ #@TODO forward headers?, this is possibly unneeded, when we just parse valid 200 headers
# as first chunk, we will parse the headers
if not self.range and self.header.endswith("\r\n\r\n"):
self.parseHeader()
@@ -233,8 +236,8 @@ class HTTPChunk(HTTPRequest):
sleep(self.p.bucket.consumed(size))
else:
# Avoid small buffers, increasing sleep time slowly if buffer size gets smaller
- # otherwise reduce sleep time percentual (values are based on tests)
- # So in general cpu time is saved without reducing bandwith too much
+ # otherwise reduce sleep time percentile (values are based on tests)
+ # So in general cpu time is saved without reducing bandwidth too much
if size < self.lastSize:
self.sleep += 0.002
@@ -250,17 +253,19 @@ class HTTPChunk(HTTPRequest):
def parseHeader(self):
- """parse data from recieved header"""
+ """parse data from received header"""
for orgline in self.decodeResponse(self.header).splitlines():
line = orgline.strip().lower()
if line.startswith("accept-ranges") and "bytes" in line:
self.p.chunkSupport = True
- if line.startswith("content-disposition") and "filename=" in line:
- name = orgline.partition("filename=")[2]
- name = name.replace('"', "").replace("'", "").replace(";", "").strip()
- self.p.nameDisposition = name
- self.log.debug("Content-Disposition: %s" % name)
+ if "content-disposition" in line:
+
+ m = search("filename(?P<type>=|\*=(?P<enc>.+)'')(?P<name>.*)", line)
+ if m:
+ name = remove_chars(m.groupdict()['name'], "\"';").strip()
+ self.p._name = name
+ self.log.debug("Content-Disposition: %s" % name)
if not self.resume and line.startswith("content-length"):
self.p.size = int(line.split(":")[1])
diff --git a/module/network/HTTPDownload.py b/module/network/HTTPDownload.py
index fe8075539..c6d2e1547 100644
--- a/module/network/HTTPDownload.py
+++ b/module/network/HTTPDownload.py
@@ -17,9 +17,9 @@
@author: RaNaN
"""
-from os import remove, fsync
+from os import remove
from os.path import dirname
-from time import sleep, time
+from time import time
from shutil import move
from logging import getLogger
@@ -28,11 +28,13 @@ import pycurl
from HTTPChunk import ChunkInfo, HTTPChunk
from HTTPRequest import BadHeader
-from module.plugins.Plugin import Abort
-from module.utils import save_join, fs_encode
+from module.plugins.Base import Abort
+from module.utils.fs import save_join, fs_encode
+
+# TODO: save content-disposition for resuming
class HTTPDownload():
- """ loads a url http + ftp """
+ """ loads an url, http + ftp supported """
def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
options={}, progressNotify=None, disposition=False):
@@ -49,7 +51,7 @@ class HTTPDownload():
self.abort = False
self.size = 0
- self.nameDisposition = None #will be parsed from content disposition
+ self._name = ""# will be parsed from content disposition
self.chunks = []
@@ -87,6 +89,10 @@ class HTTPDownload():
if not self.size: return 0
return (self.arrived * 100) / self.size
+ @property
+ def name(self):
+ return self._name if self.disposition else ""
+
def _copyChunks(self):
init = fs_encode(self.info.getChunkName(0)) #initial chunk name
@@ -113,8 +119,8 @@ class HTTPDownload():
remove(fname) #remove chunk
fo.close()
- if self.nameDisposition and self.disposition:
- self.filename = save_join(dirname(self.filename), self.nameDisposition)
+ if self.name:
+ self.filename = save_join(dirname(self.filename), self.name)
move(init, fs_encode(self.filename))
self.info.remove() #remove info file
@@ -144,8 +150,7 @@ class HTTPDownload():
finally:
self.close()
- if self.nameDisposition and self.disposition: return self.nameDisposition
- return None
+ return self.name
def _download(self, chunks, resume):
if not resume:
@@ -169,7 +174,7 @@ class HTTPDownload():
while 1:
#need to create chunks
- if not chunksCreated and self.chunkSupport and self.size: #will be setted later by first chunk
+ if not chunksCreated and self.chunkSupport and self.size: #will be set later by first chunk
if not resume:
self.info.setSize(self.size)
@@ -188,7 +193,7 @@ class HTTPDownload():
self.chunks.append(c)
self.m.add_handle(handle)
else:
- #close immediatly
+ #close immediately
self.log.debug("Invalid curl handle -> closed")
c.close()
@@ -286,7 +291,7 @@ class HTTPDownload():
if self.abort:
raise Abort()
- #sleep(0.003) #supress busy waiting - limits dl speed to (1 / x) * buffersize
+ #sleep(0.003) #suppress busy waiting - limits dl speed to (1 / x) * buffersize
self.m.select(1)
for chunk in self.chunks:
diff --git a/module/network/HTTPRequest.py b/module/network/HTTPRequest.py
index 4747d937f..4b45e10a2 100644
--- a/module/network/HTTPRequest.py
+++ b/module/network/HTTPRequest.py
@@ -25,21 +25,21 @@ from httplib import responses
from logging import getLogger
from cStringIO import StringIO
-from module.plugins.Plugin import Abort
+from module.plugins.Base import Abort
def myquote(url):
- return quote(url.encode('utf_8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]")
+ return quote(url.encode('utf8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]")
def myurlencode(data):
data = dict(data)
- return urlencode(dict((x.encode('utf_8') if isinstance(x, unicode) else x, \
- y.encode('utf_8') if isinstance(y, unicode) else y ) for x, y in data.iteritems()))
+ return urlencode(dict((x.encode('utf8') if isinstance(x, unicode) else x, \
+ y.encode('utf8') if isinstance(y, unicode) else y ) for x, y in data.iteritems()))
bad_headers = range(400, 404) + range(405, 418) + range(500, 506)
class BadHeader(Exception):
def __init__(self, code, content=""):
- Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)]))
+ Exception.__init__(self, "Bad server response: %s %s" % (code, responses.get(int(code), "Unknown Header")))
self.code = code
self.content = content
@@ -62,6 +62,7 @@ class HTTPRequest():
self.initHandle()
self.setInterface(options)
+ self.setOptions(options)
self.c.setopt(pycurl.WRITEFUNCTION, self.write)
self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
@@ -79,7 +80,8 @@ class HTTPRequest():
if hasattr(pycurl, "AUTOREFERER"):
self.c.setopt(pycurl.AUTOREFERER, 1)
self.c.setopt(pycurl.SSL_VERIFYPEER, 0)
- self.c.setopt(pycurl.LOW_SPEED_TIME, 30)
+ # Interval for low speed, detects connection loss, but can abort dl if hoster stalls the download
+ self.c.setopt(pycurl.LOW_SPEED_TIME, 45)
self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
#self.c.setopt(pycurl.VERBOSE, 1)
@@ -127,6 +129,11 @@ class HTTPRequest():
if "timeout" in options:
self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"])
+ def setOptions(self, options):
+ """ Sets same options as available in pycurl """
+ for k, v in options.iteritems():
+ if hasattr(pycurl, k):
+ self.c.setopt(getattr(pycurl, k), v)
def addCookies(self):
""" put cookies from curl handle to cj """
@@ -193,11 +200,20 @@ class HTTPRequest():
if just_header:
self.c.setopt(pycurl.FOLLOWLOCATION, 0)
self.c.setopt(pycurl.NOBODY, 1)
- self.c.perform()
- rep = self.header
- self.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.c.setopt(pycurl.NOBODY, 0)
+ # overwrite HEAD request, we want a common request type
+ if post:
+ self.c.setopt(pycurl.CUSTOMREQUEST, "POST")
+ else:
+ self.c.setopt(pycurl.CUSTOMREQUEST, "GET")
+
+ try:
+ self.c.perform()
+ rep = self.header
+ finally:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.c.setopt(pycurl.NOBODY, 0)
+ self.c.unsetopt(pycurl.CUSTOMREQUEST)
else:
self.c.perform()
@@ -262,7 +278,7 @@ class HTTPRequest():
#TODO: html_unescape as default
except LookupError:
- self.log.debug("No Decoder foung for %s" % encoding)
+ self.log.debug("No Decoder found for %s" % encoding)
except Exception:
self.log.debug("Error when decoding string from %s." % encoding)
diff --git a/module/network/RequestFactory.py b/module/network/RequestFactory.py
index 5b1528281..932184678 100644
--- a/module/network/RequestFactory.py
+++ b/module/network/RequestFactory.py
@@ -24,38 +24,29 @@ from Bucket import Bucket
from HTTPRequest import HTTPRequest
from CookieJar import CookieJar
-from XDCCRequest import XDCCRequest
-
class RequestFactory():
def __init__(self, core):
self.lock = Lock()
self.core = core
self.bucket = Bucket()
self.updateBucket()
- self.cookiejars = {}
+ @property
def iface(self):
return self.core.config["download"]["interface"]
- def getRequest(self, pluginName, account=None, type="HTTP"):
- self.lock.acquire()
-
- if type == "XDCC":
- return XDCCRequest(proxies=self.getProxies())
-
+ def getRequest(self, pluginName, cj=None):
req = Browser(self.bucket, self.getOptions())
- if account:
- cj = self.getCookieJar(pluginName, account)
+ if cj:
req.setCookieJar(cj)
else:
req.setCookieJar(CookieJar(pluginName))
- self.lock.release()
return req
def getHTTPRequest(self, **kwargs):
- """ returns a http request, dont forget to close it ! """
+ """ returns a http request, don't forget to close it ! """
options = self.getOptions()
options.update(kwargs) # submit kwargs as additional options
return HTTPRequest(CookieJar(None), options)
@@ -67,16 +58,12 @@ class RequestFactory():
rep = h.load(*args, **kwargs)
finally:
h.close()
-
- return rep
- def getCookieJar(self, pluginName, account=None):
- if (pluginName, account) in self.cookiejars:
- return self.cookiejars[(pluginName, account)]
+ return rep
- cj = CookieJar(pluginName, account)
- self.cookiejars[(pluginName, account)] = cj
- return cj
+ def openCookieJar(self, pluginname):
+ """Create new CookieJar"""
+ return CookieJar(pluginname)
def getProxies(self):
""" returns a proxy list for the request classes """
@@ -106,7 +93,7 @@ class RequestFactory():
def getOptions(self):
"""returns options needed for pycurl"""
- return {"interface": self.iface(),
+ return {"interface": self.iface,
"proxies": self.getProxies(),
"ipv6": self.core.config["download"]["ipv6"]}
diff --git a/module/network/XDCCRequest.py b/module/network/XDCCRequest.py
index f03798c17..7a1a98cb5 100644
--- a/module/network/XDCCRequest.py
+++ b/module/network/XDCCRequest.py
@@ -119,7 +119,7 @@ class XDCCRequest():
fh.write(data)
- # acknowledge data by sending number of recceived bytes
+ # acknowledge data by sending number of received bytes
dccsock.send(struct.pack('!I', self.recv))
dccsock.close()