mirror of https://github.com/evilhero/mylar
FIX: Fix for utorrent not setting labels after adding torrent, IMP: Moved bencode to lib folder and set rtorrent/utorrent to use instead of different versions
This commit is contained in:
parent
cf3f177158
commit
651535581f
|
@ -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
|
131
mylar/bencode.py
131
mylar/bencode.py
|
@ -1,131 +0,0 @@
|
||||||
# Got this from here: https://gist.github.com/1126793
|
|
||||||
|
|
||||||
# The contents of this file are subject to the Python Software Foundation
|
|
||||||
# License Version 2.3 (the License). You may not copy or use this file, in
|
|
||||||
# either source code or executable form, except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at http://www.python.org/license.
|
|
||||||
#
|
|
||||||
# Software distributed under the License is distributed on an AS IS basis,
|
|
||||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
# for the specific language governing rights and limitations under the
|
|
||||||
# License.
|
|
||||||
|
|
||||||
# Written by Petru Paler
|
|
||||||
|
|
||||||
# Minor modifications made by Andrew Resch to replace the BTFailure errors with Exceptions
|
|
||||||
|
|
||||||
def decode_int(x, f):
|
|
||||||
f += 1
|
|
||||||
newf = x.index('e', f)
|
|
||||||
n = int(x[f:newf])
|
|
||||||
if x[f] == '-':
|
|
||||||
if x[f + 1] == '0':
|
|
||||||
raise ValueError
|
|
||||||
elif x[f] == '0' and newf != f+1:
|
|
||||||
raise ValueError
|
|
||||||
return (n, newf+1)
|
|
||||||
|
|
||||||
def decode_string(x, f):
|
|
||||||
colon = x.index(':', f)
|
|
||||||
n = int(x[f:colon])
|
|
||||||
if x[f] == '0' and colon != f+1:
|
|
||||||
raise ValueError
|
|
||||||
colon += 1
|
|
||||||
return (x[colon:colon+n], colon+n)
|
|
||||||
|
|
||||||
def decode_list(x, f):
|
|
||||||
r, f = [], f+1
|
|
||||||
while x[f] != 'e':
|
|
||||||
v, f = decode_func[x[f]](x, f)
|
|
||||||
r.append(v)
|
|
||||||
return (r, f + 1)
|
|
||||||
|
|
||||||
def decode_dict(x, f):
|
|
||||||
r, f = {}, f+1
|
|
||||||
while x[f] != 'e':
|
|
||||||
k, f = decode_string(x, f)
|
|
||||||
r[k], f = decode_func[x[f]](x, f)
|
|
||||||
return (r, f + 1)
|
|
||||||
|
|
||||||
decode_func = {}
|
|
||||||
decode_func['l'] = decode_list
|
|
||||||
decode_func['d'] = decode_dict
|
|
||||||
decode_func['i'] = decode_int
|
|
||||||
decode_func['0'] = decode_string
|
|
||||||
decode_func['1'] = decode_string
|
|
||||||
decode_func['2'] = decode_string
|
|
||||||
decode_func['3'] = decode_string
|
|
||||||
decode_func['4'] = decode_string
|
|
||||||
decode_func['5'] = decode_string
|
|
||||||
decode_func['6'] = decode_string
|
|
||||||
decode_func['7'] = decode_string
|
|
||||||
decode_func['8'] = decode_string
|
|
||||||
decode_func['9'] = decode_string
|
|
||||||
|
|
||||||
def bdecode(x):
|
|
||||||
try:
|
|
||||||
r, l = decode_func[x[0]](x, 0)
|
|
||||||
except (IndexError, KeyError, ValueError):
|
|
||||||
raise Exception("not a valid bencoded string")
|
|
||||||
|
|
||||||
return r
|
|
||||||
|
|
||||||
from types import StringType, IntType, LongType, DictType, ListType, TupleType
|
|
||||||
|
|
||||||
|
|
||||||
class Bencached(object):
|
|
||||||
|
|
||||||
__slots__ = ['bencoded']
|
|
||||||
|
|
||||||
def __init__(self, s):
|
|
||||||
self.bencoded = s
|
|
||||||
|
|
||||||
def encode_bencached(x,r):
|
|
||||||
r.append(x.bencoded)
|
|
||||||
|
|
||||||
def encode_int(x, r):
|
|
||||||
r.extend(('i', str(x), 'e'))
|
|
||||||
|
|
||||||
def encode_bool(x, r):
|
|
||||||
if x:
|
|
||||||
encode_int(1, r)
|
|
||||||
else:
|
|
||||||
encode_int(0, r)
|
|
||||||
|
|
||||||
def encode_string(x, r):
|
|
||||||
r.extend((str(len(x)), ':', x))
|
|
||||||
|
|
||||||
def encode_list(x, r):
|
|
||||||
r.append('l')
|
|
||||||
for i in x:
|
|
||||||
encode_func[type(i)](i, r)
|
|
||||||
r.append('e')
|
|
||||||
|
|
||||||
def encode_dict(x,r):
|
|
||||||
r.append('d')
|
|
||||||
ilist = x.items()
|
|
||||||
ilist.sort()
|
|
||||||
for k, v in ilist:
|
|
||||||
r.extend((str(len(k)), ':', k))
|
|
||||||
encode_func[type(v)](v, r)
|
|
||||||
r.append('e')
|
|
||||||
|
|
||||||
encode_func = {}
|
|
||||||
encode_func[Bencached] = encode_bencached
|
|
||||||
encode_func[IntType] = encode_int
|
|
||||||
encode_func[LongType] = encode_int
|
|
||||||
encode_func[StringType] = encode_string
|
|
||||||
encode_func[ListType] = encode_list
|
|
||||||
encode_func[TupleType] = encode_list
|
|
||||||
encode_func[DictType] = encode_dict
|
|
||||||
|
|
||||||
try:
|
|
||||||
from types import BooleanType
|
|
||||||
encode_func[BooleanType] = encode_bool
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def bencode(x):
|
|
||||||
r = []
|
|
||||||
encode_func[type(x)](x, r)
|
|
||||||
return ''.join(r)
|
|
|
@ -21,6 +21,8 @@ import shutil
|
||||||
import traceback
|
import traceback
|
||||||
from base64 import b16encode, b32decode
|
from base64 import b16encode, b32decode
|
||||||
|
|
||||||
|
import hashlib, StringIO
|
||||||
|
import bencode
|
||||||
from torrent.helpers.variable import link, symlink, is_rarfile
|
from torrent.helpers.variable import link, symlink, is_rarfile
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
@ -89,9 +91,6 @@ class RTorrent(object):
|
||||||
return torrent_info
|
return torrent_info
|
||||||
|
|
||||||
def get_the_hash(self, filepath):
|
def get_the_hash(self, filepath):
|
||||||
import hashlib, StringIO
|
|
||||||
import rtorrent.lib.bencode as bencode
|
|
||||||
|
|
||||||
# Open torrent file
|
# Open torrent file
|
||||||
torrent_file = open(filepath, "rb")
|
torrent_file = open(filepath, "rb")
|
||||||
metainfo = bencode.decode(torrent_file.read())
|
metainfo = bencode.decode(torrent_file.read())
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
from bencode import bencode, bdecode
|
import bencode
|
||||||
from hashlib import sha1
|
import hashlib
|
||||||
from cStringIO import StringIO
|
import StringIO
|
||||||
|
|
||||||
import mylar
|
import mylar
|
||||||
from mylar import logger
|
from mylar import logger
|
||||||
|
@ -101,16 +101,16 @@ class utorrentclient(object):
|
||||||
logger.info('Unable to label torrent')
|
logger.info('Unable to label torrent')
|
||||||
return
|
return
|
||||||
|
|
||||||
def calculate_torrent_hash(link=None, filepath=None, data=None):
|
def calculate_torrent_hash(self, link=None, filepath=None, data=None):
|
||||||
thehash = None
|
thehash = None
|
||||||
if not link:
|
if link is None:
|
||||||
if filepath:
|
if filepath:
|
||||||
torrent_file = open(filepath, "rb")
|
torrent_file = open(filepath, "rb")
|
||||||
metainfo = bdecode(torrent_file.read())
|
metainfo = bencode.decode(torrent_file.read())
|
||||||
else:
|
else:
|
||||||
metainfo = bdecode(data)
|
metainfo = bencode.decode(data)
|
||||||
info = metainfo['info']
|
info = metainfo['info']
|
||||||
thehash = hashlib.sha1(bencode(info)).hexdigest().upper()
|
thehash = hashlib.sha1(bencode.encode(info)).hexdigest().upper()
|
||||||
logger.info('Hash: ' + thehash)
|
logger.info('Hash: ' + thehash)
|
||||||
else:
|
else:
|
||||||
if link.startswith("magnet:"):
|
if link.startswith("magnet:"):
|
||||||
|
|
Loading…
Reference in New Issue