Added initial rtorrent downloader, currently testing, possibly has some bugs.
This commit is contained in:
Executable
Executable
+281
@@ -0,0 +1,281 @@
|
||||
# Copyright (C) 2011 by clueless <clueless.nospam ! mail.com>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Version: 20111107
|
||||
#
|
||||
# Changelog
|
||||
# ---------
|
||||
# 2011-11-07 - Added support for Python2 (tested on 2.6)
|
||||
# 2011-10-03 - Fixed: moved check for end of list at the top of the while loop
|
||||
# in _decode_list (in case the list is empty) (Chris Lucas)
|
||||
# - Converted dictionary keys to str
|
||||
# 2011-04-24 - Changed date format to YYYY-MM-DD for versioning, bigger
|
||||
# integer denotes a newer version
|
||||
# - Fixed a bug that would treat False as an integral type but
|
||||
# encode it using the 'False' string, attempting to encode a
|
||||
# boolean now results in an error
|
||||
# - Fixed a bug where an integer value of 0 in a list or
|
||||
# dictionary resulted in a parse error while decoding
|
||||
#
|
||||
# 2011-04-03 - Original release
|
||||
|
||||
import sys
|
||||
|
||||
_py3 = sys.version_info[0] == 3
|
||||
|
||||
if _py3:
|
||||
_VALID_STRING_TYPES = (str,)
|
||||
else:
|
||||
_VALID_STRING_TYPES = (str, unicode) # @UndefinedVariable
|
||||
|
||||
_TYPE_INT = 1
|
||||
_TYPE_STRING = 2
|
||||
_TYPE_LIST = 3
|
||||
_TYPE_DICTIONARY = 4
|
||||
_TYPE_END = 5
|
||||
_TYPE_INVALID = 6
|
||||
|
||||
# Function to determine the type of he next value/item
|
||||
# Arguments:
|
||||
# char First character of the string that is to be decoded
|
||||
# Return value:
|
||||
# Returns an integer that describes what type the next value/item is
|
||||
|
||||
|
||||
def _gettype(char):
|
||||
if not isinstance(char, int):
|
||||
char = ord(char)
|
||||
if char == 0x6C: # 'l'
|
||||
return _TYPE_LIST
|
||||
elif char == 0x64: # 'd'
|
||||
return _TYPE_DICTIONARY
|
||||
elif char == 0x69: # 'i'
|
||||
return _TYPE_INT
|
||||
elif char == 0x65: # 'e'
|
||||
return _TYPE_END
|
||||
elif char >= 0x30 and char <= 0x39: # '0' '9'
|
||||
return _TYPE_STRING
|
||||
else:
|
||||
return _TYPE_INVALID
|
||||
|
||||
# Function to parse a string from the bendcoded data
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranteed to be a string
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed string
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
|
||||
|
||||
def _decode_string(data):
|
||||
end = 1
|
||||
# if py3, data[end] is going to be an int
|
||||
# if py2, data[end] will be a string
|
||||
if _py3:
|
||||
char = 0x3A
|
||||
else:
|
||||
char = chr(0x3A)
|
||||
|
||||
while data[end] != char: # ':'
|
||||
end = end + 1
|
||||
strlen = int(data[:end])
|
||||
return (data[end + 1:strlen + end + 1], data[strlen + end + 1:])
|
||||
|
||||
# Function to parse an integer from the bencoded data
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranteed to be an integer
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed string
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
|
||||
|
||||
def _decode_int(data):
|
||||
end = 1
|
||||
# if py3, data[end] is going to be an int
|
||||
# if py2, data[end] will be a string
|
||||
if _py3:
|
||||
char = 0x65
|
||||
else:
|
||||
char = chr(0x65)
|
||||
|
||||
while data[end] != char: # 'e'
|
||||
end = end + 1
|
||||
return (int(data[1:end]), data[end + 1:])
|
||||
|
||||
# Function to parse a bencoded list
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranted to be the start of a list
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed list
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
|
||||
|
||||
def _decode_list(data):
|
||||
x = []
|
||||
overflow = data[1:]
|
||||
while True: # Loop over the data
|
||||
if _gettype(overflow[0]) == _TYPE_END: # - Break if we reach the end of the list
|
||||
return (x, overflow[1:]) # and return the list and overflow
|
||||
|
||||
value, overflow = _decode(overflow) #
|
||||
if isinstance(value, bool) or overflow == '': # - if we have a parse error
|
||||
return (False, False) # Die with error
|
||||
else: # - Otherwise
|
||||
x.append(value) # add the value to the list
|
||||
|
||||
|
||||
# Function to parse a bencoded list
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranted to be the start of a list
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed dictionary
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
def _decode_dict(data):
|
||||
x = {}
|
||||
overflow = data[1:]
|
||||
while True: # Loop over the data
|
||||
if _gettype(overflow[0]) != _TYPE_STRING: # - If the key is not a string
|
||||
return (False, False) # Die with error
|
||||
key, overflow = _decode(overflow) #
|
||||
if key == False or overflow == '': # - If parse error
|
||||
return (False, False) # Die with error
|
||||
value, overflow = _decode(overflow) #
|
||||
if isinstance(value, bool) or overflow == '': # - If parse error
|
||||
print("Error parsing value")
|
||||
print(value)
|
||||
print(overflow)
|
||||
return (False, False) # Die with error
|
||||
else:
|
||||
# don't use bytes for the key
|
||||
key = key.decode()
|
||||
x[key] = value
|
||||
if _gettype(overflow[0]) == _TYPE_END:
|
||||
return (x, overflow[1:])
|
||||
|
||||
# Arguments:
|
||||
# data bencoded data in bytes format
|
||||
# Return Values:
|
||||
# Returns a tuple, the first member is the parsed data, could be a string,
|
||||
# an integer, a list or a dictionary, or a combination of those
|
||||
# The second member is the leftover of parsing, if everything parses correctly this
|
||||
# should be an empty byte string
|
||||
|
||||
|
||||
def _decode(data):
|
||||
btype = _gettype(data[0])
|
||||
if btype == _TYPE_INT:
|
||||
return _decode_int(data)
|
||||
elif btype == _TYPE_STRING:
|
||||
return _decode_string(data)
|
||||
elif btype == _TYPE_LIST:
|
||||
return _decode_list(data)
|
||||
elif btype == _TYPE_DICTIONARY:
|
||||
return _decode_dict(data)
|
||||
else:
|
||||
return (False, False)
|
||||
|
||||
# Function to decode bencoded data
|
||||
# Arguments:
|
||||
# data bencoded data, can be str or bytes
|
||||
# Return Values:
|
||||
# Returns the decoded data on success, this coud be bytes, int, dict or list
|
||||
# or a combinatin of those
|
||||
# If an error occurs the return value is False
|
||||
|
||||
|
||||
def decode(data):
|
||||
# if isinstance(data, str):
|
||||
# data = data.encode()
|
||||
decoded, overflow = _decode(data)
|
||||
return decoded
|
||||
|
||||
# Args: data as integer
|
||||
# return: encoded byte string
|
||||
|
||||
|
||||
def _encode_int(data):
|
||||
return b'i' + str(data).encode() + b'e'
|
||||
|
||||
# Args: data as string or bytes
|
||||
# Return: encoded byte string
|
||||
|
||||
|
||||
def _encode_string(data):
|
||||
return str(len(data)).encode() + b':' + data
|
||||
|
||||
# Args: data as list
|
||||
# Return: Encoded byte string, false on error
|
||||
|
||||
|
||||
def _encode_list(data):
|
||||
elist = b'l'
|
||||
for item in data:
|
||||
eitem = encode(item)
|
||||
if eitem == False:
|
||||
return False
|
||||
elist += eitem
|
||||
return elist + b'e'
|
||||
|
||||
# Args: data as dict
|
||||
# Return: encoded byte string, false on error
|
||||
|
||||
|
||||
def _encode_dict(data):
|
||||
edict = b'd'
|
||||
keys = []
|
||||
for key in data:
|
||||
if not isinstance(key, _VALID_STRING_TYPES) and not isinstance(key, bytes):
|
||||
return False
|
||||
keys.append(key)
|
||||
keys.sort()
|
||||
for key in keys:
|
||||
ekey = encode(key)
|
||||
eitem = encode(data[key])
|
||||
if ekey == False or eitem == False:
|
||||
return False
|
||||
edict += ekey + eitem
|
||||
return edict + b'e'
|
||||
|
||||
# Function to encode a variable in bencoding
|
||||
# Arguments:
|
||||
# data Variable to be encoded, can be a list, dict, str, bytes, int or a combination of those
|
||||
# Return Values:
|
||||
# Returns the encoded data as a byte string when successful
|
||||
# If an error occurs the return value is False
|
||||
|
||||
|
||||
def encode(data):
|
||||
if isinstance(data, bool):
|
||||
return False
|
||||
elif isinstance(data, int):
|
||||
return _encode_int(data)
|
||||
elif isinstance(data, bytes):
|
||||
return _encode_string(data)
|
||||
elif isinstance(data, _VALID_STRING_TYPES):
|
||||
return _encode_string(data.encode())
|
||||
elif isinstance(data, list):
|
||||
return _encode_list(data)
|
||||
elif isinstance(data, dict):
|
||||
return _encode_dict(data)
|
||||
else:
|
||||
return False
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# 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 AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from rtorrent.compat import is_py3
|
||||
import os.path
|
||||
import re
|
||||
import rtorrent.lib.bencode as bencode
|
||||
import hashlib
|
||||
|
||||
if is_py3():
|
||||
from urllib.request import urlopen # @UnresolvedImport @UnusedImport
|
||||
else:
|
||||
from urllib2 import urlopen # @UnresolvedImport @Reimport
|
||||
|
||||
|
||||
class TorrentParser():
|
||||
def __init__(self, torrent):
|
||||
"""Decode and parse given torrent
|
||||
|
||||
@param torrent: handles: urls, file paths, string of torrent data
|
||||
@type torrent: str
|
||||
|
||||
@raise AssertionError: Can be raised for a couple reasons:
|
||||
- If _get_raw_torrent() couldn't figure out
|
||||
what X{torrent} is
|
||||
- if X{torrent} isn't a valid bencoded torrent file
|
||||
"""
|
||||
self.torrent = torrent
|
||||
self._raw_torrent = None # : testing yo
|
||||
self._torrent_decoded = None # : what up
|
||||
self.file_type = None
|
||||
|
||||
self._get_raw_torrent()
|
||||
assert self._raw_torrent is not None, "Couldn't get raw_torrent."
|
||||
if self._torrent_decoded is None:
|
||||
self._decode_torrent()
|
||||
assert isinstance(self._torrent_decoded, dict), "Invalid torrent file."
|
||||
self._parse_torrent()
|
||||
|
||||
def _is_raw(self):
|
||||
raw = False
|
||||
if isinstance(self.torrent, (str, bytes)):
|
||||
if isinstance(self._decode_torrent(self.torrent), dict):
|
||||
raw = True
|
||||
else:
|
||||
# reset self._torrent_decoded (currently equals False)
|
||||
self._torrent_decoded = None
|
||||
|
||||
return(raw)
|
||||
|
||||
def _get_raw_torrent(self):
|
||||
"""Get raw torrent data by determining what self.torrent is"""
|
||||
# already raw?
|
||||
if self._is_raw():
|
||||
self.file_type = "raw"
|
||||
self._raw_torrent = self.torrent
|
||||
return
|
||||
# local file?
|
||||
if os.path.isfile(self.torrent):
|
||||
self.file_type = "file"
|
||||
self._raw_torrent = open(self.torrent, "rb").read()
|
||||
# url?
|
||||
elif re.search("^(http|ftp):\/\/", self.torrent, re.I):
|
||||
self.file_type = "url"
|
||||
self._raw_torrent = urlopen(self.torrent).read()
|
||||
|
||||
def _decode_torrent(self, raw_torrent=None):
|
||||
if raw_torrent is None:
|
||||
raw_torrent = self._raw_torrent
|
||||
self._torrent_decoded = bencode.decode(raw_torrent)
|
||||
return(self._torrent_decoded)
|
||||
|
||||
def _calc_info_hash(self):
|
||||
self.info_hash = None
|
||||
if "info" in self._torrent_decoded.keys():
|
||||
info_dict = self._torrent_decoded["info"]
|
||||
self.info_hash = hashlib.sha1(bencode.encode(
|
||||
info_dict)).hexdigest().upper()
|
||||
|
||||
return(self.info_hash)
|
||||
|
||||
def _parse_torrent(self):
|
||||
for k in self._torrent_decoded:
|
||||
key = k.replace(" ", "_").lower()
|
||||
setattr(self, key, self._torrent_decoded[k])
|
||||
|
||||
self._calc_info_hash()
|
||||
|
||||
|
||||
class NewTorrentParser(object):
|
||||
@staticmethod
|
||||
def _read_file(fp):
|
||||
return fp.read()
|
||||
|
||||
@staticmethod
|
||||
def _write_file(fp):
|
||||
fp.write()
|
||||
return fp
|
||||
|
||||
@staticmethod
|
||||
def _decode_torrent(data):
|
||||
return bencode.decode(data)
|
||||
|
||||
def __init__(self, input):
|
||||
self.input = input
|
||||
self._raw_torrent = None
|
||||
self._decoded_torrent = None
|
||||
self._hash_outdated = False
|
||||
|
||||
if isinstance(self.input, (str, bytes)):
|
||||
# path to file?
|
||||
if os.path.isfile(self.input):
|
||||
self._raw_torrent = self._read_file(open(self.input, "rb"))
|
||||
else:
|
||||
# assume input was the raw torrent data (do we really want
|
||||
# this?)
|
||||
self._raw_torrent = self.input
|
||||
|
||||
# file-like object?
|
||||
elif self.input.hasattr("read"):
|
||||
self._raw_torrent = self._read_file(self.input)
|
||||
|
||||
assert self._raw_torrent is not None, "Invalid input: input must be a path or a file-like object"
|
||||
|
||||
self._decoded_torrent = self._decode_torrent(self._raw_torrent)
|
||||
|
||||
assert isinstance(
|
||||
self._decoded_torrent, dict), "File could not be decoded"
|
||||
|
||||
def _calc_info_hash(self):
|
||||
self.info_hash = None
|
||||
info_dict = self._torrent_decoded["info"]
|
||||
self.info_hash = hashlib.sha1(bencode.encode(
|
||||
info_dict)).hexdigest().upper()
|
||||
|
||||
return(self.info_hash)
|
||||
|
||||
def set_tracker(self, tracker):
|
||||
self._decoded_torrent["announce"] = tracker
|
||||
|
||||
def get_tracker(self):
|
||||
return self._decoded_torrent.get("announce")
|
||||
Executable
Executable
+23
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# 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 AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from rtorrent.compat import xmlrpclib
|
||||
|
||||
HTTPServerProxy = xmlrpclib.ServerProxy
|
||||
Reference in New Issue
Block a user