Browse Source

Merge branch 'refs/heads/develop' into tv

pull/2139/merge
Ruud 12 years ago
parent
commit
b106229a78
  1. 89
      couchpotato/core/downloaders/deluge/__init__.py
  2. 239
      couchpotato/core/downloaders/deluge/main.py
  3. 71
      couchpotato/core/downloaders/rtorrent/__init__.py
  4. 202
      couchpotato/core/downloaders/rtorrent/main.py
  5. 6
      couchpotato/core/plugins/renamer/__init__.py
  6. 151
      couchpotato/core/plugins/renamer/main.py
  7. 2
      couchpotato/core/plugins/scanner/main.py
  8. 588
      libs/rtorrent/__init__.py
  9. 86
      libs/rtorrent/common.py
  10. 30
      libs/rtorrent/compat.py
  11. 40
      libs/rtorrent/err.py
  12. 91
      libs/rtorrent/file.py
  13. 84
      libs/rtorrent/group.py
  14. 0
      libs/rtorrent/lib/__init__.py
  15. 281
      libs/rtorrent/lib/bencode.py
  16. 159
      libs/rtorrent/lib/torrentparser.py
  17. 0
      libs/rtorrent/lib/xmlrpc/__init__.py
  18. 23
      libs/rtorrent/lib/xmlrpc/http.py
  19. 98
      libs/rtorrent/peer.py
  20. 369
      libs/rtorrent/rpc/__init__.py
  21. 506
      libs/rtorrent/torrent.py
  22. 138
      libs/rtorrent/tracker.py
  23. 24
      libs/synchronousdeluge/__init__.py
  24. 135
      libs/synchronousdeluge/client.py
  25. 11
      libs/synchronousdeluge/exceptions.py
  26. 38
      libs/synchronousdeluge/protocol.py
  27. 433
      libs/synchronousdeluge/rencode.py
  28. 57
      libs/synchronousdeluge/transfer.py
  29. 27
      libs/unrar2/PKG-INFO
  30. 191
      libs/unrar2/UnRAR2.html
  31. 18
      libs/unrar2/UnRARDLL/license.txt
  32. BIN
      libs/unrar2/UnRARDLL/unrar.dll
  33. 140
      libs/unrar2/UnRARDLL/unrar.h
  34. BIN
      libs/unrar2/UnRARDLL/unrar.lib
  35. 606
      libs/unrar2/UnRARDLL/unrardll.txt
  36. 80
      libs/unrar2/UnRARDLL/whatsnew.txt
  37. 1
      libs/unrar2/UnRARDLL/x64/readme.txt
  38. BIN
      libs/unrar2/UnRARDLL/x64/unrar64.dll
  39. BIN
      libs/unrar2/UnRARDLL/x64/unrar64.lib
  40. 177
      libs/unrar2/__init__.py
  41. 21
      libs/unrar2/license.txt
  42. 30
      libs/unrar2/rar_exceptions.py
  43. 175
      libs/unrar2/unix.py
  44. 309
      libs/unrar2/windows.py

89
couchpotato/core/downloaders/deluge/__init__.py

@ -0,0 +1,89 @@
from .main import Deluge
def start():
return Deluge()
config = [{
'name': 'deluge',
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'deluge',
'label': 'Deluge',
'description': 'Use <a href="http://www.deluge-torrent.org/" target="_blank">Deluge</a> to download torrents.',
'wizard': True,
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
'radio_group': 'torrent',
},
{
'name': 'host',
'default': 'localhost:58846',
'description': 'Hostname with port. Usually <strong>localhost:58846</strong>',
},
{
'name': 'username',
},
{
'name': 'password',
'type': 'password',
},
{
'name': 'paused',
'type': 'bool',
'default': False,
'description': 'Add the torrent paused.',
},
{
'name': 'directory',
'type': 'directory',
'description': 'Download to this directory. Keep empty for default Deluge download directory.',
},
{
'name': 'completed_directory',
'type': 'directory',
'description': 'Move completed torrent to this directory. Keep empty for default Deluge options.',
'advanced': True,
},
{
'name': 'label',
'description': 'Label to add to torrents in the Deluge UI.',
},
{
'name': 'remove_complete',
'label': 'Remove torrent',
'type': 'bool',
'default': True,
'advanced': True,
'description': 'Remove the torrent from Deluge after it has finished seeding.',
},
{
'name': 'delete_files',
'label': 'Remove files',
'default': True,
'type': 'bool',
'advanced': True,
'description': 'Also remove the leftover files.',
},
{
'name': 'manual',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
{
'name': 'delete_failed',
'default': True,
'advanced': True,
'type': 'bool',
'description': 'Delete a release after the download has failed.',
},
],
}
],
}]

239
couchpotato/core/downloaders/deluge/main.py

@ -0,0 +1,239 @@
from base64 import b64encode
from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import isInt
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from datetime import timedelta
from synchronousdeluge import DelugeClient
import os.path
import traceback
log = CPLog(__name__)
class Deluge(Downloader):
protocol = ['torrent', 'torrent_magnet']
log = CPLog(__name__)
drpc = None
def connect(self):
# Load host from config and split out port.
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
if not self.drpc:
self.drpc = DelugeRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
return self.drpc
def download(self, data, movie, filedata = None):
log.info('Sending "%s" (%s) to Deluge.', (data.get('name'), data.get('protocol')))
if not self.connect():
return False
if not filedata and data.get('protocol') == 'torrent':
log.error('Failed sending torrent, no data')
return False
# Set parameters for Deluge
options = {
'add_paused': self.conf('paused', default = 0),
'label': self.conf('label')
}
if self.conf('directory'):
if os.path.isdir(self.conf('directory')):
options['download_location'] = self.conf('directory')
else:
log.error('Download directory from Deluge settings: %s doesn\'t exist', self.conf('directory'))
if self.conf('completed_directory'):
if os.path.isdir(self.conf('completed_directory')):
options['move_completed'] = 1
options['move_completed_path'] = self.conf('completed_directory')
else:
log.error('Download directory from Deluge settings: %s doesn\'t exist', self.conf('directory'))
if data.get('seed_ratio'):
options['stop_at_ratio'] = 1
options['stop_ratio'] = tryFloat(data.get('seed_ratio'))
# Deluge only has seed time as a global option. Might be added in
# in a future API release.
# if data.get('seed_time'):
# Send request to Deluge
if data.get('protocol') == 'torrent_magnet':
remote_torrent = self.drpc.add_torrent_magnet(data.get('url'), options)
else:
remote_torrent = self.drpc.add_torrent_file(movie, b64encode(filedata), options)
if not remote_torrent:
log.error('Failed sending torrent to Deluge')
return False
log.info('Torrent sent to Deluge successfully.')
return self.downloadReturnId(remote_torrent)
def getAllDownloadStatus(self):
log.debug('Checking Deluge download status.')
if not self.connect():
return False
statuses = StatusList(self)
queue = self.drpc.get_alltorrents()
if not (queue and queue.get('torrents')):
log.debug('Nothing in queue or error')
return False
for torrent_id in queue:
item = queue[torrent_id]
log.debug('name=%s / id=%s / save_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / conf_ratio=%s/ is_seed=%s / is_finished=%s', (item['name'], item['hash'], item['save_path'], item['hash'], item['progress'], item['state'], item['eta'], item['ratio'], self.conf('ratio'), item['is_seed'], item['is_finished']))
if not os.path.isdir(Env.setting('from', 'renamer')):
log.error('Renamer "from" folder doesn\'t to exist.')
return
status = 'busy'
# Deluge seems to set both is_seed and is_finished once everything has been downloaded.
if item['is_seed'] or item['is_finished']:
status = 'seeding'
elif item['is_seed'] and item['is_finished'] and item['paused']:
status = 'completed'
download_dir = item['save_path']
if item['move_on_completed']:
download_dir = item['move_completed_path']
statuses.append({
'id': item['hash'],
'name': item['name'],
'status': status,
'original_status': item['state'],
'seed_ratio': item['ratio'],
'timeleft': str(timedelta(seconds = item['eta'])),
'folder': os.path.join(download_dir, item['name']),
})
return statuses
def pause(self, item, pause = True):
if pause:
return self.drpc.pause_torrent([item['id']])
else:
return self.drpc.resume_torrent([item['id']])
def removeFailed(self, item):
log.info('%s failed downloading, deleting...', item['name'])
return self.drpc.remove_torrent(item['id'], True)
def processComplete(self, item, delete_files = False):
log.debug('Requesting Deluge to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
return self.drpc.remove_torrent(item['id'], remove_local_data = delete_files)
class DelugeRPC(object):
host = 'localhost'
port = 58846
username = None
password = None
client = None
def __init__(self, host = 'localhost', port = 58846, username = None, password = None):
super(DelugeRPC, self).__init__()
self.host = host
self.port = port
self.username = username
self.password = password
def connect(self):
self.client = DelugeClient()
self.client.connect(self.host, int(self.port), self.username, self.password)
def add_torrent_magnet(self, torrent, options):
torrent_id = False
try:
self.connect()
torrent_id = self.client.core.add_torrent_magnet(torrent, options).get()
if options['label']:
self.client.label.set_torrent(torrent_id, options['label']).get()
except Exception, err:
log.error('Failed to add torrent magnet: %s %s', err, traceback.format_exc())
finally:
if self.client:
self.disconnect()
return torrent_id
def add_torrent_file(self, movie, torrent, options):
torrent_id = False
try:
self.connect()
torrent_id = self.client.core.add_torrent_file(movie, torrent, options).get()
if options['label']:
self.client.label.set_torrent(torrent_id, options['label']).get()
except Exception, err:
log.error('Failed to add torrent file: %s %s', err, traceback.format_exc())
finally:
if self.client:
self.disconnect()
return torrent_id
def get_alltorrents(self):
ret = False
try:
self.connect()
ret = self.client.core.get_torrents_status({}, {}).get()
except Exception, err:
log.error('Failed to get all torrents: %s %s', err, traceback.format_exc())
finally:
if self.client:
self.disconnect()
return ret
def pause_torrent(self, torrent_ids):
try:
self.connect()
self.client.core.pause_torrent(torrent_ids).get()
except Exception, err:
log.error('Failed to pause torrent: %s %s', err, traceback.format_exc())
finally:
if self.client:
self.disconnect()
def resume_torrent(self, torrent_ids):
try:
self.connect()
self.client.core.resume_torrent(torrent_ids).get()
except Exception, err:
log.error('Failed to resume torrent: %s %s', err, traceback.format_exc())
finally:
if self.client:
self.disconnect()
def remove_torrent(self, torrent_id, remove_local_data):
ret = False
try:
self.connect()
ret = self.client.core.remove_torrent(torrent_id, remove_local_data).get()
except Exception, err:
log.error('Failed to remove torrent: %s %s', err, traceback.format_exc())
finally:
if self.client:
self.disconnect()
return ret
def disconnect(self):
self.client.disconnect()

71
couchpotato/core/downloaders/rtorrent/__init__.py

@ -0,0 +1,71 @@
from .main import rTorrent
def start():
return rTorrent()
config = [{
'name': 'rtorrent',
'groups': [
{
'tab': 'downloaders',
'list': 'download_providers',
'name': 'rtorrent',
'label': 'rTorrent',
'description': '',
'wizard': True,
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
'radio_group': 'torrent',
},
{
'name': 'url',
'default': 'http://localhost:80/RPC2',
},
{
'name': 'username',
},
{
'name': 'password',
'type': 'password',
},
{
'name': 'label',
'description': 'Label to apply on added torrents.',
},
{
'name': 'remove_complete',
'label': 'Remove torrent',
'default': False,
'advanced': True,
'type': 'bool',
'description': 'Remove the torrent after it finishes seeding.',
},
{
'name': 'delete_files',
'label': 'Remove files',
'default': True,
'type': 'bool',
'advanced': True,
'description': 'Also remove the leftover files.',
},
{
'name': 'paused',
'type': 'bool',
'advanced': True,
'default': False,
'description': 'Add the torrent paused.',
},
{
'name': 'manual',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
],
}
],
}]

202
couchpotato/core/downloaders/rtorrent/main.py

@ -0,0 +1,202 @@
from base64 import b16encode, b32decode
from datetime import timedelta
from hashlib import sha1
import shutil
from rtorrent.err import MethodError
from bencode import bencode, bdecode
from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.logger import CPLog
from rtorrent import RTorrent
log = CPLog(__name__)
class rTorrent(Downloader):
protocol = ['torrent', 'torrent_magnet']
rt = None
def connect(self):
# Already connected?
if self.rt is not None:
return self.rt
# Ensure url is set
if not self.conf('url'):
log.error('Config properties are not filled in correctly, url is missing.')
return False
if self.conf('username') and self.conf('password'):
self.rt = RTorrent(
self.conf('url'),
self.conf('username'),
self.conf('password')
)
else:
self.rt = RTorrent(self.conf('url'))
return self.rt
def _update_provider_group(self, name, data):
if data.get('seed_time'):
log.info('seeding time ignored, not supported')
if not name:
return False
if not self.connect():
return False
views = self.rt.get_views()
if name not in views:
self.rt.create_group(name)
group = self.rt.get_group(name)
try:
if data.get('seed_ratio'):
ratio = int(float(data.get('seed_ratio')) * 100)
log.debug('Updating provider ratio to %s, group name: %s', (ratio, name))
# Explicitly set all group options to ensure it is setup correctly
group.set_upload('1M')
group.set_min(ratio)
group.set_max(ratio)
group.set_command('d.stop')
group.enable()
else:
# Reset group action and disable it
group.set_command()
group.disable()
except MethodError, err:
log.error('Unable to set group options: %s', err.message)
return False
return True
def download(self, data, movie, filedata = None):
log.debug('Sending "%s" to rTorrent.', (data.get('name')))
if not self.connect():
return False
group_name = 'cp_' + data.get('provider').lower()
if not self._update_provider_group(group_name, data):
return False
torrent_params = {}
if self.conf('label'):
torrent_params['label'] = self.conf('label')
if not filedata and data.get('protocol') == 'torrent':
log.error('Failed sending torrent, no data')
return False
# Try download magnet torrents
if data.get('protocol') == 'torrent_magnet':
filedata = self.magnetToTorrent(data.get('url'))
if filedata is False:
return False
data['protocol'] = 'torrent'
info = bdecode(filedata)["info"]
torrent_hash = sha1(bencode(info)).hexdigest().upper()
# Convert base 32 to hex
if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash))
# Send request to rTorrent
try:
# Send torrent to rTorrent
torrent = self.rt.load_torrent(filedata)
# Set label
if self.conf('label'):
torrent.set_custom(1, self.conf('label'))
# Set Ratio Group
torrent.set_visible(group_name)
# Start torrent
if not self.conf('paused', default = 0):
torrent.start()
return self.downloadReturnId(torrent_hash)
except Exception, err:
log.error('Failed to send torrent to rTorrent: %s', err)
return False
def getAllDownloadStatus(self):
log.debug('Checking rTorrent download status.')
if not self.connect():
return False
try:
torrents = self.rt.get_torrents()
statuses = StatusList(self)
for item in torrents:
status = 'busy'
if item.complete:
if item.active:
status = 'seeding'
else:
status = 'completed'
statuses.append({
'id': item.info_hash,
'name': item.name,
'status': status,
'seed_ratio': item.ratio,
'original_status': item.state,
'timeleft': str(timedelta(seconds = float(item.left_bytes) / item.down_rate))
if item.down_rate > 0 else -1,
'folder': item.directory
})
return statuses
except Exception, err:
log.error('Failed to get status from rTorrent: %s', err)
return False
def pause(self, download_info, pause = True):
if not self.connect():
return False
torrent = self.rt.find_torrent(download_info['id'])
if torrent is None:
return False
if pause:
return torrent.pause()
return torrent.resume()
def removeFailed(self, item):
log.info('%s failed downloading, deleting...', item['name'])
return self.processComplete(item, delete_files = True)
def processComplete(self, item, delete_files):
log.debug('Requesting rTorrent to remove the torrent %s%s.',
(item['name'], ' and cleanup the downloaded files' if delete_files else ''))
if not self.connect():
return False
torrent = self.rt.find_torrent(item['id'])
if torrent is None:
return False
torrent.erase() # just removes the torrent, doesn't delete data
if delete_files:
shutil.rmtree(item['folder'], True)
return True

6
couchpotato/core/plugins/renamer/__init__.py

@ -74,6 +74,12 @@ config = [{
'options': rename_options
},
{
'name': 'unrar',
'type': 'bool',
'description': 'Extract rar files if found.',
'default': False,
},
{
'name': 'cleanup',
'type': 'bool',
'description': 'Cleanup leftover files after successful rename.',

151
couchpotato/core/plugins/renamer/main.py

@ -9,6 +9,8 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library, File, Profile, Release, \
ReleaseInfo
from couchpotato.environment import Env
from unrar2 import RarFile, RarInfo
from unrar2.rar_exceptions import *
import errno
import fnmatch
import os
@ -126,6 +128,11 @@ class Renamer(Plugin):
# Extend the download info with info stored in the downloaded release
download_info = self.extendDownloadInfo(download_info)
# Unpack any archives
if self.conf('unrar'):
folder, movie_folder, files, extr_files = self.extractFiles(folder = folder, movie_folder = movie_folder, files = files, \
cleanup = self.conf('cleanup') and not self.downloadIsTorrent(download_info))
groups = fireEvent('scanner.scan', folder = folder if folder else self.conf('from'),
files = files, download_info = download_info, return_ignored = False, single = True)
@ -179,6 +186,9 @@ class Renamer(Plugin):
group['before_rename'] = []
fireEvent('renamer.before', group)
# Add extracted files to the before_rename list
group['before_rename'].extend(extr_files)
# Remove weird chars from moviename
movie_name = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie_title)
@ -213,8 +223,8 @@ class Renamer(Plugin):
# Move nfo depending on settings
if file_type is 'nfo' and not self.conf('rename_nfo'):
log.debug('Skipping, renaming of %s disabled', file_type)
if self.conf('cleanup') and not self.downloadIsTorrent(download_info):
for current_file in group['files'][file_type]:
for current_file in group['files'][file_type]:
if self.conf('cleanup') and (not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)) or self.fileIsAdded(current_file, group)):
remove_files.append(current_file)
continue
@ -394,14 +404,15 @@ class Renamer(Plugin):
db.commit()
# Remove leftover files
if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers and \
not self.downloadIsTorrent(download_info):
log.debug('Removing leftover files')
for current_file in group['files']['leftover']:
remove_files.append(current_file)
elif not remove_leftovers: # Don't remove anything
if not remove_leftovers: # Don't remove anything
break
log.debug('Removing leftover files')
for current_file in group['files']['leftover']:
if self.conf('cleanup') and not self.conf('move_leftover') and \
(not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)) or self.fileIsAdded(current_file, group)):
remove_files.append(current_file)
# Remove files
delete_folders = []
for src in remove_files:
@ -834,3 +845,127 @@ Remove it if you want it to be renamed (again, or at least let it try again)
def movieInFromFolder(self, movie_folder):
return movie_folder and self.conf('from') in movie_folder or not movie_folder
def extractFiles(self, folder = None, movie_folder = None, files = [], cleanup = False):
# RegEx for finding rar files
archive_regex = '(?P<file>^(?P<base>(?:(?!\.part\d+\.rar$).)*)\.(?:(?:part0*1\.)?rar)$)'
restfile_regex = '(^%s\.(?:part(?!0*1\.rar$)\d+\.rar$|[rstuvw]\d+$))'
extr_files = []
# Check input variables
if not folder:
folder = self.conf('from')
check_file_date = True
if movie_folder:
check_file_date = False
if not files:
for root, folders, names in os.walk(folder):
files.extend([os.path.join(root, name) for name in names])
# Find all archive files
archives = [re.search(archive_regex, name).groupdict() for name in files if re.search(archive_regex, name)]
#Extract all found archives
for archive in archives:
# Check if it has already been processed by CPS
if (self.hastagDir(os.path.dirname(archive['file']))):
continue
# Find all related archive files
archive['files'] = [name for name in files if re.search(restfile_regex % re.escape(archive['base']), name)]
archive['files'].append(archive['file'])
# Check if archive is fresh and maybe still copying/moving/downloading, ignore files newer than 1 minute
if check_file_date:
file_too_new = False
for cur_file in archive['files']:
if not os.path.isfile(cur_file):
file_too_new = time.time()
break
file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
for t in file_time:
if t > time.time() - 60:
file_too_new = tryInt(time.time() - t)
break
if file_too_new:
break
if file_too_new:
try:
time_string = time.ctime(file_time[0])
except:
try:
time_string = time.ctime(file_time[1])
except:
time_string = 'unknown'
log.info('Archive seems to be still copying/moving/downloading or just copied/moved/downloaded (created on %s), ignoring for now: %s', (time_string, os.path.basename(archive['file'])))
continue
log.info('Archive %s found. Extracting...', os.path.basename(archive['file']))
try:
rar_handle = RarFile(archive['file'])
extr_path = os.path.join(self.conf('from'), os.path.relpath(os.path.dirname(archive['file']), folder))
self.makeDir(extr_path)
for packedinfo in rar_handle.infolist():
if not packedinfo.isdir and not os.path.isfile(os.path.join(extr_path, os.path.basename(packedinfo.filename))):
log.debug('Extracting %s...', packedinfo.filename)
rar_handle.extract(condition = [packedinfo.index], path = extr_path, withSubpath = False, overwrite = False)
extr_files.append(os.path.join(extr_path, os.path.basename(packedinfo.filename)))
del rar_handle
except Exception, e:
log.error('Failed to extract %s: %s %s', (archive['file'], e, traceback.format_exc()))
continue
# Delete the archive files
for filename in archive['files']:
if cleanup:
try:
os.remove(filename)
except Exception, e:
log.error('Failed to remove %s: %s %s', (filename, e, traceback.format_exc()))
continue
files.remove(filename)
# Move the rest of the files and folders if any files are extracted to the from folder (only if folder was provided)
if extr_files and os.path.normpath(os.path.normcase(folder)) != os.path.normpath(os.path.normcase(self.conf('from'))):
for leftoverfile in list(files):
move_to = os.path.join(self.conf('from'), os.path.relpath(leftoverfile, folder))
try:
self.makeDir(os.path.dirname(move_to))
self.moveFile(leftoverfile, move_to, cleanup)
except Exception, e:
log.error('Failed moving left over file %s to %s: %s %s',(leftoverfile, move_to, e, traceback.format_exc()))
# As we probably tried to overwrite the nfo file, check if it exists and then remove the original
if os.path.isfile(move_to):
if cleanup:
log.info('Deleting left over file %s instead...', leftoverfile)
os.unlink(leftoverfile)
else:
continue
files.remove(leftoverfile)
extr_files.append(move_to)
if cleanup:
# Remove all left over folders
log.debug('Removing old movie folder %s...', movie_folder)
self.deleteEmptyFolder(movie_folder)
movie_folder = os.path.join(self.conf('from'), os.path.relpath(movie_folder, folder))
folder = self.conf('from')
if extr_files:
files.extend(extr_files)
# Cleanup files and folder if movie_folder was not provided
if not movie_folder:
files = []
folder = None
return (folder, movie_folder, files, extr_files)

2
couchpotato/core/plugins/scanner/main.py

@ -277,7 +277,7 @@ class Scanner(Plugin):
except:
break
# Check if movie is fresh and maybe still unpacking, ignore files new then 1 minute
# Check if movie is fresh and maybe still unpacking, ignore files newer than 1 minute
file_too_new = False
for cur_file in group['unsorted_files']:
if not os.path.isfile(cur_file):

588
libs/rtorrent/__init__.py

@ -0,0 +1,588 @@
# 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.common import find_torrent, \
is_valid_port, convert_version_tuple_to_str
from rtorrent.lib.torrentparser import TorrentParser
from rtorrent.lib.xmlrpc.http import HTTPServerProxy
from rtorrent.rpc import Method, BasicAuthTransport
from rtorrent.torrent import Torrent
from rtorrent.group import Group
import os.path
import rtorrent.rpc # @UnresolvedImport
import time
import xmlrpclib
__version__ = "0.2.9"
__author__ = "Chris Lucas"
__contact__ = "chris@chrisjlucas.com"
__license__ = "MIT"
MIN_RTORRENT_VERSION = (0, 8, 1)
MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION)
class RTorrent:
""" Create a new rTorrent connection """
rpc_prefix = None
def __init__(self, url, username=None, password=None,
verify=False, sp=HTTPServerProxy, sp_kwargs={}):
self.url = url # : From X{__init__(self, url)}
self.username = username
self.password = password
self.sp = sp
self.sp_kwargs = sp_kwargs
self.torrents = [] # : List of L{Torrent} instances
self._rpc_methods = [] # : List of rTorrent RPC methods
self._torrent_cache = []
self._client_version_tuple = ()
if verify is True:
self._verify_conn()
def _get_conn(self):
"""Get ServerProxy instance"""
if self.username is not None and self.password is not None:
return self.sp(
self.url,
transport=BasicAuthTransport(self.username, self.password),
**self.sp_kwargs
)
return self.sp(self.url, **self.sp_kwargs)
def _verify_conn(self):
# check for rpc methods that should be available
assert {"system.client_version",
"system.library_version"}.issubset(set(self._get_rpc_methods())),\
"Required RPC methods not available."
# minimum rTorrent version check
assert self._meets_version_requirement() is True,\
"Error: Minimum rTorrent version required is {0}".format(
MIN_RTORRENT_VERSION_STR)
def _meets_version_requirement(self):
return self._get_client_version_tuple() >= MIN_RTORRENT_VERSION
def _get_client_version_tuple(self):
conn = self._get_conn()
if not self._client_version_tuple:
if not hasattr(self, "client_version"):
setattr(self, "client_version",
conn.system.client_version())
rtver = getattr(self, "client_version")
self._client_version_tuple = tuple([int(i) for i in
rtver.split(".")])
return self._client_version_tuple
def _get_rpc_methods(self):
""" Get list of raw RPC commands
@return: raw RPC commands
@rtype: list
"""
if self._rpc_methods == []:
self._rpc_methods = self._get_conn().system.listMethods()
return(self._rpc_methods)
def get_torrents(self, view="main"):
"""Get list of all torrents in specified view
@return: list of L{Torrent} instances
@rtype: list
@todo: add validity check for specified view
"""
self.torrents = []
methods = rtorrent.torrent.methods
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self)]
m = rtorrent.rpc.Multicall(self)
m.add("d.multicall", view, "d.get_hash=",
*[method.rpc_call + "=" for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result[1:]): # result[0] is the info_hash
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
self.torrents.append(
Torrent(self, info_hash=result[0], **results_dict)
)
self._manage_torrent_cache()
return(self.torrents)
def _manage_torrent_cache(self):
"""Carry tracker/peer/file lists over to new torrent list"""
for torrent in self._torrent_cache:
new_torrent = rtorrent.common.find_torrent(torrent.info_hash,
self.torrents)
if new_torrent is not None:
new_torrent.files = torrent.files
new_torrent.peers = torrent.peers
new_torrent.trackers = torrent.trackers
self._torrent_cache = self.torrents
def _get_load_function(self, file_type, start, verbose):
"""Determine correct "load torrent" RPC method"""
func_name = None
if file_type == "url":
# url strings can be input directly
if start and verbose:
func_name = "load_start_verbose"
elif start:
func_name = "load_start"
elif verbose:
func_name = "load_verbose"
else:
func_name = "load"
elif file_type in ["file", "raw"]:
if start and verbose:
func_name = "load_raw_start_verbose"
elif start:
func_name = "load_raw_start"
elif verbose:
func_name = "load_raw_verbose"
else:
func_name = "load_raw"
return(func_name)
def load_torrent(self, torrent, start=False, verbose=False, verify_load=True):
"""
Loads torrent into rTorrent (with various enhancements)
@param torrent: can be a url, a path to a local file, or the raw data
of a torrent file
@type torrent: str
@param start: start torrent when loaded
@type start: bool
@param verbose: print error messages to rTorrent log
@type verbose: bool
@param verify_load: verify that torrent was added to rTorrent successfully
@type verify_load: bool
@return: Depends on verify_load:
- if verify_load is True, (and the torrent was
loaded successfully), it'll return a L{Torrent} instance
- if verify_load is False, it'll return None
@rtype: L{Torrent} instance or None
@raise AssertionError: If the torrent wasn't successfully added to rTorrent
- Check L{TorrentParser} for the AssertionError's
it raises
@note: Because this function includes url verification (if a url was input)
as well as verification as to whether the torrent was successfully added,
this function doesn't execute instantaneously. If that's what you're
looking for, use load_torrent_simple() instead.
"""
p = self._get_conn()
tp = TorrentParser(torrent)
torrent = xmlrpclib.Binary(tp._raw_torrent)
info_hash = tp.info_hash
func_name = self._get_load_function("raw", start, verbose)
# load torrent
getattr(p, func_name)(torrent)
if verify_load:
MAX_RETRIES = 3
i = 0
while i < MAX_RETRIES:
self.get_torrents()
if info_hash in [t.info_hash for t in self.torrents]:
break
# was still getting AssertionErrors, delay should help
time.sleep(1)
i += 1
assert info_hash in [t.info_hash for t in self.torrents],\
"Adding torrent was unsuccessful."
return(find_torrent(info_hash, self.torrents))
def load_torrent_simple(self, torrent, file_type,
start=False, verbose=False):
"""Loads torrent into rTorrent
@param torrent: can be a url, a path to a local file, or the raw data
of a torrent file
@type torrent: str
@param file_type: valid options: "url", "file", or "raw"
@type file_type: str
@param start: start torrent when loaded
@type start: bool
@param verbose: print error messages to rTorrent log
@type verbose: bool
@return: None
@raise AssertionError: if incorrect file_type is specified
@note: This function was written for speed, it includes no enhancements.
If you input a url, it won't check if it's valid. You also can't get
verification that the torrent was successfully added to rTorrent.
Use load_torrent() if you would like these features.
"""
p = self._get_conn()
assert file_type in ["raw", "file", "url"], \
"Invalid file_type, options are: 'url', 'file', 'raw'."
func_name = self._get_load_function(file_type, start, verbose)
if file_type == "file":
# since we have to assume we're connected to a remote rTorrent
# client, we have to read the file and send it to rT as raw
assert os.path.isfile(torrent), \
"Invalid path: \"{0}\"".format(torrent)
torrent = open(torrent, "rb").read()
if file_type in ["raw", "file"]:
finput = xmlrpclib.Binary(torrent)
elif file_type == "url":
finput = torrent
getattr(p, func_name)(finput)
def get_views(self):
p = self._get_conn()
return p.view_list()
def create_group(self, name, persistent=True, view=None):
p = self._get_conn()
if persistent is True:
p.group.insert_persistent_view('', name)
else:
assert view is not None, "view parameter required on non-persistent groups"
p.group.insert('', name, view)
def get_group(self, name):
assert name is not None, "group name required"
group = Group(self, name)
group.update()
return group
def set_dht_port(self, port):
"""Set DHT port
@param port: port
@type port: int
@raise AssertionError: if invalid port is given
"""
assert is_valid_port(port), "Valid port range is 0-65535"
self.dht_port = self._p.set_dht_port(port)
def enable_check_hash(self):
"""Alias for set_check_hash(True)"""
self.set_check_hash(True)
def disable_check_hash(self):
"""Alias for set_check_hash(False)"""
self.set_check_hash(False)
def find_torrent(self, info_hash):
"""Frontend for rtorrent.common.find_torrent"""
return(rtorrent.common.find_torrent(info_hash, self.get_torrents()))
def poll(self):
""" poll rTorrent to get latest torrent/peer/tracker/file information
@note: This essentially refreshes every aspect of the rTorrent
connection, so it can be very slow if working with a remote
connection that has a lot of torrents loaded.
@return: None
"""
self.update()
torrents = self.get_torrents()
for t in torrents:
t.poll()
def update(self):
"""Refresh rTorrent client info
@note: All fields are stored as attributes to self.
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self)]
for method in retriever_methods:
multicall.add(method)
multicall.call()
def _build_class_methods(class_obj):
# multicall add class
caller = lambda self, multicall, method, *args:\
multicall.add(method, self.rpc_id, *args)
caller.__doc__ = """Same as Multicall.add(), but with automatic inclusion
of the rpc_id
@param multicall: A L{Multicall} instance
@type: multicall: Multicall
@param method: L{Method} instance or raw rpc method
@type: Method or str
@param args: optional arguments to pass
"""
setattr(class_obj, "multicall_add", caller)
def __compare_rpc_methods(rt_new, rt_old):
from pprint import pprint
rt_new_methods = set(rt_new._get_rpc_methods())
rt_old_methods = set(rt_old._get_rpc_methods())
print("New Methods:")
pprint(rt_new_methods - rt_old_methods)
print("Methods not in new rTorrent:")
pprint(rt_old_methods - rt_new_methods)
def __check_supported_methods(rt):
from pprint import pprint
supported_methods = set([m.rpc_call for m in
methods +
rtorrent.file.methods +
rtorrent.torrent.methods +
rtorrent.tracker.methods +
rtorrent.peer.methods])
all_methods = set(rt._get_rpc_methods())
print("Methods NOT in supported methods")
pprint(all_methods - supported_methods)
print("Supported methods NOT in all methods")
pprint(supported_methods - all_methods)
methods = [
# RETRIEVERS
Method(RTorrent, 'get_xmlrpc_size_limit', 'get_xmlrpc_size_limit'),
Method(RTorrent, 'get_proxy_address', 'get_proxy_address'),
Method(RTorrent, 'get_split_suffix', 'get_split_suffix'),
Method(RTorrent, 'get_up_limit', 'get_upload_rate'),
Method(RTorrent, 'get_max_memory_usage', 'get_max_memory_usage'),
Method(RTorrent, 'get_max_open_files', 'get_max_open_files'),
Method(RTorrent, 'get_min_peers_seed', 'get_min_peers_seed'),
Method(RTorrent, 'get_use_udp_trackers', 'get_use_udp_trackers'),
Method(RTorrent, 'get_preload_min_size', 'get_preload_min_size'),
Method(RTorrent, 'get_max_uploads', 'get_max_uploads'),
Method(RTorrent, 'get_max_peers', 'get_max_peers'),
Method(RTorrent, 'get_timeout_sync', 'get_timeout_sync'),
Method(RTorrent, 'get_receive_buffer_size', 'get_receive_buffer_size'),
Method(RTorrent, 'get_split_file_size', 'get_split_file_size'),
Method(RTorrent, 'get_dht_throttle', 'get_dht_throttle'),
Method(RTorrent, 'get_max_peers_seed', 'get_max_peers_seed'),
Method(RTorrent, 'get_min_peers', 'get_min_peers'),
Method(RTorrent, 'get_tracker_numwant', 'get_tracker_numwant'),
Method(RTorrent, 'get_max_open_sockets', 'get_max_open_sockets'),
Method(RTorrent, 'get_session', 'get_session'),
Method(RTorrent, 'get_ip', 'get_ip'),
Method(RTorrent, 'get_scgi_dont_route', 'get_scgi_dont_route'),
Method(RTorrent, 'get_hash_read_ahead', 'get_hash_read_ahead'),
Method(RTorrent, 'get_http_cacert', 'get_http_cacert'),
Method(RTorrent, 'get_dht_port', 'get_dht_port'),
Method(RTorrent, 'get_handshake_log', 'get_handshake_log'),
Method(RTorrent, 'get_preload_type', 'get_preload_type'),
Method(RTorrent, 'get_max_open_http', 'get_max_open_http'),
Method(RTorrent, 'get_http_capath', 'get_http_capath'),
Method(RTorrent, 'get_max_downloads_global', 'get_max_downloads_global'),
Method(RTorrent, 'get_name', 'get_name'),
Method(RTorrent, 'get_session_on_completion', 'get_session_on_completion'),
Method(RTorrent, 'get_down_limit', 'get_download_rate'),
Method(RTorrent, 'get_down_total', 'get_down_total'),
Method(RTorrent, 'get_up_rate', 'get_up_rate'),
Method(RTorrent, 'get_hash_max_tries', 'get_hash_max_tries'),
Method(RTorrent, 'get_peer_exchange', 'get_peer_exchange'),
Method(RTorrent, 'get_down_rate', 'get_down_rate'),
Method(RTorrent, 'get_connection_seed', 'get_connection_seed'),
Method(RTorrent, 'get_http_proxy', 'get_http_proxy'),
Method(RTorrent, 'get_stats_preloaded', 'get_stats_preloaded'),
Method(RTorrent, 'get_timeout_safe_sync', 'get_timeout_safe_sync'),
Method(RTorrent, 'get_hash_interval', 'get_hash_interval'),
Method(RTorrent, 'get_port_random', 'get_port_random'),
Method(RTorrent, 'get_directory', 'get_directory'),
Method(RTorrent, 'get_port_open', 'get_port_open'),
Method(RTorrent, 'get_max_file_size', 'get_max_file_size'),
Method(RTorrent, 'get_stats_not_preloaded', 'get_stats_not_preloaded'),
Method(RTorrent, 'get_memory_usage', 'get_memory_usage'),
Method(RTorrent, 'get_connection_leech', 'get_connection_leech'),
Method(RTorrent, 'get_check_hash', 'get_check_hash',
boolean=True,
),
Method(RTorrent, 'get_session_lock', 'get_session_lock'),
Method(RTorrent, 'get_preload_required_rate', 'get_preload_required_rate'),
Method(RTorrent, 'get_max_uploads_global', 'get_max_uploads_global'),
Method(RTorrent, 'get_send_buffer_size', 'get_send_buffer_size'),
Method(RTorrent, 'get_port_range', 'get_port_range'),
Method(RTorrent, 'get_max_downloads_div', 'get_max_downloads_div'),
Method(RTorrent, 'get_max_uploads_div', 'get_max_uploads_div'),
Method(RTorrent, 'get_safe_sync', 'get_safe_sync'),
Method(RTorrent, 'get_bind', 'get_bind'),
Method(RTorrent, 'get_up_total', 'get_up_total'),
Method(RTorrent, 'get_client_version', 'system.client_version'),
Method(RTorrent, 'get_library_version', 'system.library_version'),
Method(RTorrent, 'get_api_version', 'system.api_version',
min_version=(0, 9, 1)
),
Method(RTorrent, "get_system_time", "system.time",
docstring="""Get the current time of the system rTorrent is running on
@return: time (posix)
@rtype: int""",
),
# MODIFIERS
Method(RTorrent, 'set_http_proxy', 'set_http_proxy'),
Method(RTorrent, 'set_max_memory_usage', 'set_max_memory_usage'),
Method(RTorrent, 'set_max_file_size', 'set_max_file_size'),
Method(RTorrent, 'set_bind', 'set_bind',
docstring="""Set address bind
@param arg: ip address
@type arg: str
""",
),
Method(RTorrent, 'set_up_limit', 'set_upload_rate',
docstring="""Set global upload limit (in bytes)
@param arg: speed limit
@type arg: int
""",
),
Method(RTorrent, 'set_port_random', 'set_port_random'),
Method(RTorrent, 'set_connection_leech', 'set_connection_leech'),
Method(RTorrent, 'set_tracker_numwant', 'set_tracker_numwant'),
Method(RTorrent, 'set_max_peers', 'set_max_peers'),
Method(RTorrent, 'set_min_peers', 'set_min_peers'),
Method(RTorrent, 'set_max_uploads_div', 'set_max_uploads_div'),
Method(RTorrent, 'set_max_open_files', 'set_max_open_files'),
Method(RTorrent, 'set_max_downloads_global', 'set_max_downloads_global'),
Method(RTorrent, 'set_session_lock', 'set_session_lock'),
Method(RTorrent, 'set_session', 'set_session'),
Method(RTorrent, 'set_split_suffix', 'set_split_suffix'),
Method(RTorrent, 'set_hash_interval', 'set_hash_interval'),
Method(RTorrent, 'set_handshake_log', 'set_handshake_log'),
Method(RTorrent, 'set_port_range', 'set_port_range'),
Method(RTorrent, 'set_min_peers_seed', 'set_min_peers_seed'),
Method(RTorrent, 'set_scgi_dont_route', 'set_scgi_dont_route'),
Method(RTorrent, 'set_preload_min_size', 'set_preload_min_size'),
Method(RTorrent, 'set_log.tracker', 'set_log.tracker'),
Method(RTorrent, 'set_max_uploads_global', 'set_max_uploads_global'),
Method(RTorrent, 'set_down_limit', 'set_download_rate',
docstring="""Set global download limit (in bytes)
@param arg: speed limit
@type arg: int
""",
),
Method(RTorrent, 'set_preload_required_rate', 'set_preload_required_rate'),
Method(RTorrent, 'set_hash_read_ahead', 'set_hash_read_ahead'),
Method(RTorrent, 'set_max_peers_seed', 'set_max_peers_seed'),
Method(RTorrent, 'set_max_uploads', 'set_max_uploads'),
Method(RTorrent, 'set_session_on_completion', 'set_session_on_completion'),
Method(RTorrent, 'set_max_open_http', 'set_max_open_http'),
Method(RTorrent, 'set_directory', 'set_directory'),
Method(RTorrent, 'set_http_cacert', 'set_http_cacert'),
Method(RTorrent, 'set_dht_throttle', 'set_dht_throttle'),
Method(RTorrent, 'set_hash_max_tries', 'set_hash_max_tries'),
Method(RTorrent, 'set_proxy_address', 'set_proxy_address'),
Method(RTorrent, 'set_split_file_size', 'set_split_file_size'),
Method(RTorrent, 'set_receive_buffer_size', 'set_receive_buffer_size'),
Method(RTorrent, 'set_use_udp_trackers', 'set_use_udp_trackers'),
Method(RTorrent, 'set_connection_seed', 'set_connection_seed'),
Method(RTorrent, 'set_xmlrpc_size_limit', 'set_xmlrpc_size_limit'),
Method(RTorrent, 'set_xmlrpc_dialect', 'set_xmlrpc_dialect'),
Method(RTorrent, 'set_safe_sync', 'set_safe_sync'),
Method(RTorrent, 'set_http_capath', 'set_http_capath'),
Method(RTorrent, 'set_send_buffer_size', 'set_send_buffer_size'),
Method(RTorrent, 'set_max_downloads_div', 'set_max_downloads_div'),
Method(RTorrent, 'set_name', 'set_name'),
Method(RTorrent, 'set_port_open', 'set_port_open'),
Method(RTorrent, 'set_timeout_sync', 'set_timeout_sync'),
Method(RTorrent, 'set_peer_exchange', 'set_peer_exchange'),
Method(RTorrent, 'set_ip', 'set_ip',
docstring="""Set IP
@param arg: ip address
@type arg: str
""",
),
Method(RTorrent, 'set_timeout_safe_sync', 'set_timeout_safe_sync'),
Method(RTorrent, 'set_preload_type', 'set_preload_type'),
Method(RTorrent, 'set_check_hash', 'set_check_hash',
docstring="""Enable/Disable hash checking on finished torrents
@param arg: True to enable, False to disable
@type arg: bool
""",
boolean=True,
),
]
_all_methods_list = [methods,
rtorrent.file.methods,
rtorrent.torrent.methods,
rtorrent.tracker.methods,
rtorrent.peer.methods,
]
class_methods_pair = {
RTorrent: methods,
rtorrent.file.File: rtorrent.file.methods,
rtorrent.torrent.Torrent: rtorrent.torrent.methods,
rtorrent.tracker.Tracker: rtorrent.tracker.methods,
rtorrent.peer.Peer: rtorrent.peer.methods,
}
for c in class_methods_pair.keys():
rtorrent.rpc._build_rpc_methods(c, class_methods_pair[c])
_build_class_methods(c)

86
libs/rtorrent/common.py

@ -0,0 +1,86 @@
# 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
def bool_to_int(value):
"""Translates python booleans to RPC-safe integers"""
if value is True:
return("1")
elif value is False:
return("0")
else:
return(value)
def cmd_exists(cmds_list, cmd):
"""Check if given command is in list of available commands
@param cmds_list: see L{RTorrent._rpc_methods}
@type cmds_list: list
@param cmd: name of command to be checked
@type cmd: str
@return: bool
"""
return(cmd in cmds_list)
def find_torrent(info_hash, torrent_list):
"""Find torrent file in given list of Torrent classes
@param info_hash: info hash of torrent
@type info_hash: str
@param torrent_list: list of L{Torrent} instances (see L{RTorrent.get_torrents})
@type torrent_list: list
@return: L{Torrent} instance, or -1 if not found
"""
for t in torrent_list:
if t.info_hash == info_hash:
return t
return None
def is_valid_port(port):
"""Check if given port is valid"""
return(0 <= int(port) <= 65535)
def convert_version_tuple_to_str(t):
return(".".join([str(n) for n in t]))
def safe_repr(fmt, *args, **kwargs):
""" Formatter that handles unicode arguments """
if not is_py3():
# unicode fmt can take str args, str fmt cannot take unicode args
fmt = fmt.decode("utf-8")
out = fmt.format(*args, **kwargs)
return out.encode("utf-8")
else:
return fmt.format(*args, **kwargs)

30
libs/rtorrent/compat.py

@ -0,0 +1,30 @@
# 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.
import sys
def is_py3():
return sys.version_info[0] == 3
if is_py3():
import xmlrpc.client as xmlrpclib
else:
import xmlrpclib

40
libs/rtorrent/err.py

@ -0,0 +1,40 @@
# 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.common import convert_version_tuple_to_str
class RTorrentVersionError(Exception):
def __init__(self, min_version, cur_version):
self.min_version = min_version
self.cur_version = cur_version
self.msg = "Minimum version required: {0}".format(
convert_version_tuple_to_str(min_version))
def __str__(self):
return(self.msg)
class MethodError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return(self.msg)

91
libs/rtorrent/file.py

@ -0,0 +1,91 @@
# 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.rpc import Method
import rtorrent.rpc
from rtorrent.common import safe_repr
Method = rtorrent.rpc.Method
class File:
"""Represents an individual file within a L{Torrent} instance."""
def __init__(self, _rt_obj, info_hash, index, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent the file is associated with
self.index = index # : The position of the file within the file list
for k in kwargs.keys():
setattr(self, k, kwargs.get(k, None))
self.rpc_id = "{0}:f{1}".format(
self.info_hash, self.index) # : unique id to pass to rTorrent
def update(self):
"""Refresh file data
@note: All fields are stored as attributes to self.
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
multicall.call()
def __repr__(self):
return safe_repr("File(index={0} path=\"{1}\")", self.index, self.path)
methods = [
# RETRIEVERS
Method(File, 'get_last_touched', 'f.get_last_touched'),
Method(File, 'get_range_second', 'f.get_range_second'),
Method(File, 'get_size_bytes', 'f.get_size_bytes'),
Method(File, 'get_priority', 'f.get_priority'),
Method(File, 'get_match_depth_next', 'f.get_match_depth_next'),
Method(File, 'is_resize_queued', 'f.is_resize_queued',
boolean=True,
),
Method(File, 'get_range_first', 'f.get_range_first'),
Method(File, 'get_match_depth_prev', 'f.get_match_depth_prev'),
Method(File, 'get_path', 'f.get_path'),
Method(File, 'get_completed_chunks', 'f.get_completed_chunks'),
Method(File, 'get_path_components', 'f.get_path_components'),
Method(File, 'is_created', 'f.is_created',
boolean=True,
),
Method(File, 'is_open', 'f.is_open',
boolean=True,
),
Method(File, 'get_size_chunks', 'f.get_size_chunks'),
Method(File, 'get_offset', 'f.get_offset'),
Method(File, 'get_frozen_path', 'f.get_frozen_path'),
Method(File, 'get_path_depth', 'f.get_path_depth'),
Method(File, 'is_create_queued', 'f.is_create_queued',
boolean=True,
),
# MODIFIERS
]

84
libs/rtorrent/group.py

@ -0,0 +1,84 @@
# Copyright (c) 2013 Dean Gardiner, <gardiner91@gmail.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.
import rtorrent.rpc
Method = rtorrent.rpc.Method
class Group:
__name__ = 'Group'
def __init__(self, _rt_obj, name):
self._rt_obj = _rt_obj
self.name = name
self.methods = [
# RETRIEVERS
Method(Group, 'get_max', 'group.' + self.name + '.ratio.max', varname='max'),
Method(Group, 'get_min', 'group.' + self.name + '.ratio.min', varname='min'),
Method(Group, 'get_upload', 'group.' + self.name + '.ratio.upload', varname='upload'),
# MODIFIERS
Method(Group, 'set_max', 'group.' + self.name + '.ratio.max.set', varname='max'),
Method(Group, 'set_min', 'group.' + self.name + '.ratio.min.set', varname='min'),
Method(Group, 'set_upload', 'group.' + self.name + '.ratio.upload.set', varname='upload')
]
rtorrent.rpc._build_rpc_methods(self, self.methods)
# Setup multicall_add method
caller = lambda multicall, method, *args: \
multicall.add(method, *args)
setattr(self, "multicall_add", caller)
def _get_prefix(self):
return 'group.' + self.name + '.ratio.'
def update(self):
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in self.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method)
multicall.call()
def enable(self):
p = self._rt_obj._get_conn()
return getattr(p, self._get_prefix() + 'enable')()
def disable(self):
p = self._rt_obj._get_conn()
return getattr(p, self._get_prefix() + 'disable')()
def set_command(self, *methods):
methods = [m + '=' for m in methods]
m = rtorrent.rpc.Multicall(self)
self.multicall_add(
m, 'system.method.set',
self._get_prefix() + 'command',
*methods
)
return(m.call()[-1])

0
libs/rtorrent/lib/__init__.py

281
libs/rtorrent/lib/bencode.py

@ -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

159
libs/rtorrent/lib/torrentparser.py

@ -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")

0
libs/rtorrent/lib/xmlrpc/__init__.py

23
libs/rtorrent/lib/xmlrpc/http.py

@ -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

98
libs/rtorrent/peer.py

@ -0,0 +1,98 @@
# 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.rpc import Method
import rtorrent.rpc
from rtorrent.common import safe_repr
Method = rtorrent.rpc.Method
class Peer:
"""Represents an individual peer within a L{Torrent} instance."""
def __init__(self, _rt_obj, info_hash, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent the peer is associated with
for k in kwargs.keys():
setattr(self, k, kwargs.get(k, None))
self.rpc_id = "{0}:p{1}".format(
self.info_hash, self.id) # : unique id to pass to rTorrent
def __repr__(self):
return safe_repr("Peer(id={0})", self.id)
def update(self):
"""Refresh peer data
@note: All fields are stored as attributes to self.
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
multicall.call()
methods = [
# RETRIEVERS
Method(Peer, 'is_preferred', 'p.is_preferred',
boolean=True,
),
Method(Peer, 'get_down_rate', 'p.get_down_rate'),
Method(Peer, 'is_unwanted', 'p.is_unwanted',
boolean=True,
),
Method(Peer, 'get_peer_total', 'p.get_peer_total'),
Method(Peer, 'get_peer_rate', 'p.get_peer_rate'),
Method(Peer, 'get_port', 'p.get_port'),
Method(Peer, 'is_snubbed', 'p.is_snubbed',
boolean=True,
),
Method(Peer, 'get_id_html', 'p.get_id_html'),
Method(Peer, 'get_up_rate', 'p.get_up_rate'),
Method(Peer, 'is_banned', 'p.banned',
boolean=True,
),
Method(Peer, 'get_completed_percent', 'p.get_completed_percent'),
Method(Peer, 'completed_percent', 'p.completed_percent'),
Method(Peer, 'get_id', 'p.get_id'),
Method(Peer, 'is_obfuscated', 'p.is_obfuscated',
boolean=True,
),
Method(Peer, 'get_down_total', 'p.get_down_total'),
Method(Peer, 'get_client_version', 'p.get_client_version'),
Method(Peer, 'get_address', 'p.get_address'),
Method(Peer, 'is_incoming', 'p.is_incoming',
boolean=True,
),
Method(Peer, 'is_encrypted', 'p.is_encrypted',
boolean=True,
),
Method(Peer, 'get_options_str', 'p.get_options_str'),
Method(Peer, 'get_client_version', 'p.client_version'),
Method(Peer, 'get_up_total', 'p.get_up_total'),
# MODIFIERS
]

369
libs/rtorrent/rpc/__init__.py

@ -0,0 +1,369 @@
# 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 base64 import encodestring
import httplib
import inspect
import string
import rtorrent
import re
from rtorrent.common import bool_to_int, convert_version_tuple_to_str,\
safe_repr
from rtorrent.err import RTorrentVersionError, MethodError
from rtorrent.compat import xmlrpclib
class BasicAuthTransport(xmlrpclib.Transport):
def __init__(self, username=None, password=None):
xmlrpclib.Transport.__init__(self)
self.username = username
self.password = password
def send_auth(self, h):
if self.username is not None and self.password is not None:
h.putheader('AUTHORIZATION', "Basic %s" % string.replace(
encodestring("%s:%s" % (self.username, self.password)),
"\012", ""
))
def single_request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request
h = self.make_connection(host)
if verbose:
h.set_debuglevel(1)
try:
self.send_request(h, handler, request_body)
self.send_host(h, host)
self.send_user_agent(h)
self.send_auth(h)
self.send_content(h, request_body)
response = h.getresponse(buffering=True)
if response.status == 200:
self.verbose = verbose
return self.parse_response(response)
except xmlrpclib.Fault:
raise
except Exception:
self.close()
raise
#discard any response data and raise exception
if (response.getheader("content-length", 0)):
response.read()
raise xmlrpclib.ProtocolError(
host + handler,
response.status, response.reason,
response.msg,
)
def get_varname(rpc_call):
"""Transform rpc method into variable name.
@newfield example: Example
@example: if the name of the rpc method is 'p.get_down_rate', the variable
name will be 'down_rate'
"""
# extract variable name from xmlrpc func name
r = re.search(
"([ptdf]\.|system\.|get\_|is\_|set\_)+([^=]*)", rpc_call, re.I)
if r:
return(r.groups()[-1])
else:
return(None)
def _handle_unavailable_rpc_method(method, rt_obj):
msg = "Method isn't available."
if rt_obj._get_client_version_tuple() < method.min_version:
msg = "This method is only available in " \
"RTorrent version v{0} or later".format(
convert_version_tuple_to_str(method.min_version))
raise MethodError(msg)
class DummyClass:
def __init__(self):
pass
class Method:
"""Represents an individual RPC method"""
def __init__(self, _class, method_name,
rpc_call, docstring=None, varname=None, **kwargs):
self._class = _class # : Class this method is associated with
self.class_name = _class.__name__
self.method_name = method_name # : name of public-facing method
self.rpc_call = rpc_call # : name of rpc method
self.docstring = docstring # : docstring for rpc method (optional)
self.varname = varname # : variable for the result of the method call, usually set to self.varname
self.min_version = kwargs.get("min_version", (
0, 0, 0)) # : Minimum version of rTorrent required
self.boolean = kwargs.get("boolean", False) # : returns boolean value?
self.post_process_func = kwargs.get(
"post_process_func", None) # : custom post process function
self.aliases = kwargs.get(
"aliases", []) # : aliases for method (optional)
self.required_args = []
#: Arguments required when calling the method (not utilized)
self.method_type = self._get_method_type()
if self.varname is None:
self.varname = get_varname(self.rpc_call)
assert self.varname is not None, "Couldn't get variable name."
def __repr__(self):
return safe_repr("Method(method_name='{0}', rpc_call='{1}')",
self.method_name, self.rpc_call)
def _get_method_type(self):
"""Determine whether method is a modifier or a retriever"""
if self.method_name[:4] == "set_": return('m') # modifier
else:
return('r') # retriever
def is_modifier(self):
if self.method_type == 'm':
return(True)
else:
return(False)
def is_retriever(self):
if self.method_type == 'r':
return(True)
else:
return(False)
def is_available(self, rt_obj):
if rt_obj._get_client_version_tuple() < self.min_version or \
self.rpc_call not in rt_obj._get_rpc_methods():
return(False)
else:
return(True)
class Multicall:
def __init__(self, class_obj, **kwargs):
self.class_obj = class_obj
if class_obj.__class__.__name__ == "RTorrent":
self.rt_obj = class_obj
else:
self.rt_obj = class_obj._rt_obj
self.calls = []
def add(self, method, *args):
"""Add call to multicall
@param method: L{Method} instance or name of raw RPC method
@type method: Method or str
@param args: call arguments
"""
# if a raw rpc method was given instead of a Method instance,
# try and find the instance for it. And if all else fails, create a
# dummy Method instance
if isinstance(method, str):
result = find_method(method)
# if result not found
if result == -1:
method = Method(DummyClass, method, method)
else:
method = result
# ensure method is available before adding
if not method.is_available(self.rt_obj):
_handle_unavailable_rpc_method(method, self.rt_obj)
self.calls.append((method, args))
def list_calls(self):
for c in self.calls:
print(c)
def call(self):
"""Execute added multicall calls
@return: the results (post-processed), in the order they were added
@rtype: tuple
"""
m = xmlrpclib.MultiCall(self.rt_obj._get_conn())
for call in self.calls:
method, args = call
rpc_call = getattr(method, "rpc_call")
getattr(m, rpc_call)(*args)
results = m()
results = tuple(results)
results_processed = []
for r, c in zip(results, self.calls):
method = c[0] # Method instance
result = process_result(method, r)
results_processed.append(result)
# assign result to class_obj
exists = hasattr(self.class_obj, method.varname)
if not exists or not inspect.ismethod(getattr(self.class_obj, method.varname)):
setattr(self.class_obj, method.varname, result)
return(tuple(results_processed))
def call_method(class_obj, method, *args):
"""Handles single RPC calls
@param class_obj: Peer/File/Torrent/Tracker/RTorrent instance
@type class_obj: object
@param method: L{Method} instance or name of raw RPC method
@type method: Method or str
"""
if method.is_retriever():
args = args[:-1]
else:
assert args[-1] is not None, "No argument given."
if class_obj.__class__.__name__ == "RTorrent":
rt_obj = class_obj
else:
rt_obj = class_obj._rt_obj
# check if rpc method is even available
if not method.is_available(rt_obj):
_handle_unavailable_rpc_method(method, rt_obj)
m = Multicall(class_obj)
m.add(method, *args)
# only added one method, only getting one result back
ret_value = m.call()[0]
####### OBSOLETE ##########################################################
# if method.is_retriever():
# #value = process_result(method, ret_value)
# value = ret_value #MultiCall already processed the result
# else:
# # we're setting the user's input to method.varname
# # but we'll return the value that xmlrpc gives us
# value = process_result(method, args[-1])
##########################################################################
return(ret_value)
def find_method(rpc_call):
"""Return L{Method} instance associated with given RPC call"""
method_lists = [
rtorrent.methods,
rtorrent.file.methods,
rtorrent.tracker.methods,
rtorrent.peer.methods,
rtorrent.torrent.methods,
]
for l in method_lists:
for m in l:
if m.rpc_call.lower() == rpc_call.lower():
return(m)
return(-1)
def process_result(method, result):
"""Process given C{B{result}} based on flags set in C{B{method}}
@param method: L{Method} instance
@type method: Method
@param result: result to be processed (the result of given L{Method} instance)
@note: Supported Processing:
- boolean - convert ones and zeros returned by rTorrent and
convert to python boolean values
"""
# handle custom post processing function
if method.post_process_func is not None:
result = method.post_process_func(result)
# is boolean?
if method.boolean:
if result in [1, '1']:
result = True
elif result in [0, '0']:
result = False
return(result)
def _build_rpc_methods(class_, method_list):
"""Build glorified aliases to raw RPC methods"""
instance = None
if not inspect.isclass(class_):
instance = class_
class_ = instance.__class__
for m in method_list:
class_name = m.class_name
if class_name != class_.__name__:
continue
if class_name == "RTorrent":
caller = lambda self, arg = None, method = m:\
call_method(self, method, bool_to_int(arg))
elif class_name == "Torrent":
caller = lambda self, arg = None, method = m:\
call_method(self, method, self.rpc_id,
bool_to_int(arg))
elif class_name in ["Tracker", "File"]:
caller = lambda self, arg = None, method = m:\
call_method(self, method, self.rpc_id,
bool_to_int(arg))
elif class_name == "Peer":
caller = lambda self, arg = None, method = m:\
call_method(self, method, self.rpc_id,
bool_to_int(arg))
elif class_name == "Group":
caller = lambda arg = None, method = m: \
call_method(instance, method, bool_to_int(arg))
if m.docstring is None:
m.docstring = ""
# print(m)
docstring = """{0}
@note: Variable where the result for this method is stored: {1}.{2}""".format(
m.docstring,
class_name,
m.varname)
caller.__doc__ = docstring
for method_name in [m.method_name] + list(m.aliases):
if instance is None:
setattr(class_, method_name, caller)
else:
setattr(instance, method_name, caller)

506
libs/rtorrent/torrent.py

@ -0,0 +1,506 @@
# 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.
import rtorrent.rpc
# from rtorrent.rpc import Method
import rtorrent.peer
import rtorrent.tracker
import rtorrent.file
import rtorrent.compat
from rtorrent.common import safe_repr
Peer = rtorrent.peer.Peer
Tracker = rtorrent.tracker.Tracker
File = rtorrent.file.File
Method = rtorrent.rpc.Method
class Torrent:
"""Represents an individual torrent within a L{RTorrent} instance."""
def __init__(self, _rt_obj, info_hash, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent
self.rpc_id = self.info_hash # : unique id to pass to rTorrent
for k in kwargs.keys():
setattr(self, k, kwargs.get(k, None))
self.peers = []
self.trackers = []
self.files = []
self._call_custom_methods()
def __repr__(self):
return safe_repr("Torrent(info_hash=\"{0}\" name=\"{1}\")",
self.info_hash, self.name)
def _call_custom_methods(self):
"""only calls methods that check instance variables."""
self._is_hash_checking_queued()
self._is_started()
self._is_paused()
def get_peers(self):
"""Get list of Peer instances for given torrent.
@return: L{Peer} instances
@rtype: list
@note: also assigns return value to self.peers
"""
self.peers = []
retriever_methods = [m for m in rtorrent.peer.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
# need to leave 2nd arg empty (dunno why)
m = rtorrent.rpc.Multicall(self)
m.add("p.multicall", self.info_hash, "",
*[method.rpc_call + "=" for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
self.peers.append(Peer(
self._rt_obj, self.info_hash, **results_dict))
return(self.peers)
def get_trackers(self):
"""Get list of Tracker instances for given torrent.
@return: L{Tracker} instances
@rtype: list
@note: also assigns return value to self.trackers
"""
self.trackers = []
retriever_methods = [m for m in rtorrent.tracker.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
# need to leave 2nd arg empty (dunno why)
m = rtorrent.rpc.Multicall(self)
m.add("t.multicall", self.info_hash, "",
*[method.rpc_call + "=" for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
self.trackers.append(Tracker(
self._rt_obj, self.info_hash, **results_dict))
return(self.trackers)
def get_files(self):
"""Get list of File instances for given torrent.
@return: L{File} instances
@rtype: list
@note: also assigns return value to self.files
"""
self.files = []
retriever_methods = [m for m in rtorrent.file.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
# 2nd arg can be anything, but it'll return all files in torrent
# regardless
m = rtorrent.rpc.Multicall(self)
m.add("f.multicall", self.info_hash, "",
*[method.rpc_call + "=" for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
offset_method_index = retriever_methods.index(
rtorrent.rpc.find_method("f.get_offset"))
# make a list of the offsets of all the files, sort appropriately
offset_list = sorted([r[offset_method_index] for r in results])
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
# get proper index positions for each file (based on the file
# offset)
f_index = offset_list.index(results_dict["offset"])
self.files.append(File(self._rt_obj, self.info_hash,
f_index, **results_dict))
return(self.files)
def set_directory(self, d):
"""Modify download directory
@note: Needs to stop torrent in order to change the directory.
Also doesn't restart after directory is set, that must be called
separately.
"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_stop")
self.multicall_add(m, "d.set_directory", d)
self.directory = m.call()[-1]
def start(self):
"""Start the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_start")
self.multicall_add(m, "d.is_active")
self.active = m.call()[-1]
return(self.active)
def stop(self):
""""Stop the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_stop")
self.multicall_add(m, "d.is_active")
self.active = m.call()[-1]
return(self.active)
def pause(self):
"""Pause the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.pause")
return(m.call()[-1])
def resume(self):
"""Resume the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.resume")
return(m.call()[-1])
def close(self):
"""Close the torrent and it's files"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.close")
return(m.call()[-1])
def erase(self):
"""Delete the torrent
@note: doesn't delete the downloaded files"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.erase")
return(m.call()[-1])
def check_hash(self):
"""(Re)hash check the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.check_hash")
return(m.call()[-1])
def poll(self):
"""poll rTorrent to get latest peer/tracker/file information"""
self.get_peers()
self.get_trackers()
self.get_files()
def update(self):
"""Refresh torrent data
@note: All fields are stored as attributes to self.
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
multicall.call()
# custom functions (only call private methods, since they only check
# local variables and are therefore faster)
self._call_custom_methods()
def accept_seeders(self, accept_seeds):
"""Enable/disable whether the torrent connects to seeders
@param accept_seeds: enable/disable accepting seeders
@type accept_seeds: bool"""
if accept_seeds:
call = "d.accepting_seeders.enable"
else:
call = "d.accepting_seeders.disable"
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, call)
return(m.call()[-1])
def announce(self):
"""Announce torrent info to tracker(s)"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.tracker_announce")
return(m.call()[-1])
@staticmethod
def _assert_custom_key_valid(key):
assert type(key) == int and key > 0 and key < 6, \
"key must be an integer between 1-5"
def get_custom(self, key):
"""
Get custom value
@param key: the index for the custom field (between 1-5)
@type key: int
@rtype: str
"""
self._assert_custom_key_valid(key)
m = rtorrent.rpc.Multicall(self)
field = "custom{0}".format(key)
self.multicall_add(m, "d.get_{0}".format(field))
setattr(self, field, m.call()[-1])
return (getattr(self, field))
def set_custom(self, key, value):
"""
Set custom value
@param key: the index for the custom field (between 1-5)
@type key: int
@param value: the value to be stored
@type value: str
@return: if successful, value will be returned
@rtype: str
"""
self._assert_custom_key_valid(key)
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.set_custom{0}".format(key), value)
return(m.call()[-1])
def set_visible(self, view, visible=True):
p = self._rt_obj._get_conn()
if visible:
return p.view.set_visible(self.info_hash, view)
else:
return p.view.set_not_visible(self.info_hash, view)
############################################################################
# CUSTOM METHODS (Not part of the official rTorrent API)
##########################################################################
def _is_hash_checking_queued(self):
"""Only checks instance variables, shouldn't be called directly"""
# if hashing == 3, then torrent is marked for hash checking
# if hash_checking == False, then torrent is waiting to be checked
self.hash_checking_queued = (self.hashing == 3 and
self.hash_checking is False)
return(self.hash_checking_queued)
def is_hash_checking_queued(self):
"""Check if torrent is waiting to be hash checked
@note: Variable where the result for this method is stored Torrent.hash_checking_queued"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.get_hashing")
self.multicall_add(m, "d.is_hash_checking")
results = m.call()
setattr(self, "hashing", results[0])
setattr(self, "hash_checking", results[1])
return(self._is_hash_checking_queued())
def _is_paused(self):
"""Only checks instance variables, shouldn't be called directly"""
self.paused = (self.state == 0)
return(self.paused)
def is_paused(self):
"""Check if torrent is paused
@note: Variable where the result for this method is stored: Torrent.paused"""
self.get_state()
return(self._is_paused())
def _is_started(self):
"""Only checks instance variables, shouldn't be called directly"""
self.started = (self.state == 1)
return(self.started)
def is_started(self):
"""Check if torrent is started
@note: Variable where the result for this method is stored: Torrent.started"""
self.get_state()
return(self._is_started())
methods = [
# RETRIEVERS
Method(Torrent, 'is_hash_checked', 'd.is_hash_checked',
boolean=True,
),
Method(Torrent, 'is_hash_checking', 'd.is_hash_checking',
boolean=True,
),
Method(Torrent, 'get_peers_max', 'd.get_peers_max'),
Method(Torrent, 'get_tracker_focus', 'd.get_tracker_focus'),
Method(Torrent, 'get_skip_total', 'd.get_skip_total'),
Method(Torrent, 'get_state', 'd.get_state'),
Method(Torrent, 'get_peer_exchange', 'd.get_peer_exchange'),
Method(Torrent, 'get_down_rate', 'd.get_down_rate'),
Method(Torrent, 'get_connection_seed', 'd.get_connection_seed'),
Method(Torrent, 'get_uploads_max', 'd.get_uploads_max'),
Method(Torrent, 'get_priority_str', 'd.get_priority_str'),
Method(Torrent, 'is_open', 'd.is_open',
boolean=True,
),
Method(Torrent, 'get_peers_min', 'd.get_peers_min'),
Method(Torrent, 'get_peers_complete', 'd.get_peers_complete'),
Method(Torrent, 'get_tracker_numwant', 'd.get_tracker_numwant'),
Method(Torrent, 'get_connection_current', 'd.get_connection_current'),
Method(Torrent, 'is_complete', 'd.get_complete',
boolean=True,
),
Method(Torrent, 'get_peers_connected', 'd.get_peers_connected'),
Method(Torrent, 'get_chunk_size', 'd.get_chunk_size'),
Method(Torrent, 'get_state_counter', 'd.get_state_counter'),
Method(Torrent, 'get_base_filename', 'd.get_base_filename'),
Method(Torrent, 'get_state_changed', 'd.get_state_changed'),
Method(Torrent, 'get_peers_not_connected', 'd.get_peers_not_connected'),
Method(Torrent, 'get_directory', 'd.get_directory'),
Method(Torrent, 'is_incomplete', 'd.incomplete',
boolean=True,
),
Method(Torrent, 'get_tracker_size', 'd.get_tracker_size'),
Method(Torrent, 'is_multi_file', 'd.is_multi_file',
boolean=True,
),
Method(Torrent, 'get_local_id', 'd.get_local_id'),
Method(Torrent, 'get_ratio', 'd.get_ratio',
post_process_func=lambda x: x / 1000.0,
),
Method(Torrent, 'get_loaded_file', 'd.get_loaded_file'),
Method(Torrent, 'get_max_file_size', 'd.get_max_file_size'),
Method(Torrent, 'get_size_chunks', 'd.get_size_chunks'),
Method(Torrent, 'is_pex_active', 'd.is_pex_active',
boolean=True,
),
Method(Torrent, 'get_hashing', 'd.get_hashing'),
Method(Torrent, 'get_bitfield', 'd.get_bitfield'),
Method(Torrent, 'get_local_id_html', 'd.get_local_id_html'),
Method(Torrent, 'get_connection_leech', 'd.get_connection_leech'),
Method(Torrent, 'get_peers_accounted', 'd.get_peers_accounted'),
Method(Torrent, 'get_message', 'd.get_message'),
Method(Torrent, 'is_active', 'd.is_active',
boolean=True,
),
Method(Torrent, 'get_size_bytes', 'd.get_size_bytes'),
Method(Torrent, 'get_ignore_commands', 'd.get_ignore_commands'),
Method(Torrent, 'get_creation_date', 'd.get_creation_date'),
Method(Torrent, 'get_base_path', 'd.get_base_path'),
Method(Torrent, 'get_left_bytes', 'd.get_left_bytes'),
Method(Torrent, 'get_size_files', 'd.get_size_files'),
Method(Torrent, 'get_size_pex', 'd.get_size_pex'),
Method(Torrent, 'is_private', 'd.is_private',
boolean=True,
),
Method(Torrent, 'get_max_size_pex', 'd.get_max_size_pex'),
Method(Torrent, 'get_num_chunks_hashed', 'd.get_chunks_hashed',
aliases=("get_chunks_hashed",)),
Method(Torrent, 'get_num_chunks_wanted', 'd.wanted_chunks'),
Method(Torrent, 'get_priority', 'd.get_priority'),
Method(Torrent, 'get_skip_rate', 'd.get_skip_rate'),
Method(Torrent, 'get_completed_bytes', 'd.get_completed_bytes'),
Method(Torrent, 'get_name', 'd.get_name'),
Method(Torrent, 'get_completed_chunks', 'd.get_completed_chunks'),
Method(Torrent, 'get_throttle_name', 'd.get_throttle_name'),
Method(Torrent, 'get_free_diskspace', 'd.get_free_diskspace'),
Method(Torrent, 'get_directory_base', 'd.get_directory_base'),
Method(Torrent, 'get_hashing_failed', 'd.get_hashing_failed'),
Method(Torrent, 'get_tied_to_file', 'd.get_tied_to_file'),
Method(Torrent, 'get_down_total', 'd.get_down_total'),
Method(Torrent, 'get_bytes_done', 'd.get_bytes_done'),
Method(Torrent, 'get_up_rate', 'd.get_up_rate'),
Method(Torrent, 'get_up_total', 'd.get_up_total'),
Method(Torrent, 'is_accepting_seeders', 'd.accepting_seeders',
boolean=True,
),
Method(Torrent, "get_chunks_seen", "d.chunks_seen",
min_version=(0, 9, 1),
),
Method(Torrent, "is_partially_done", "d.is_partially_done",
boolean=True,
),
Method(Torrent, "is_not_partially_done", "d.is_not_partially_done",
boolean=True,
),
Method(Torrent, "get_time_started", "d.timestamp.started"),
Method(Torrent, "get_custom1", "d.get_custom1"),
Method(Torrent, "get_custom2", "d.get_custom2"),
Method(Torrent, "get_custom3", "d.get_custom3"),
Method(Torrent, "get_custom4", "d.get_custom4"),
Method(Torrent, "get_custom5", "d.get_custom5"),
# MODIFIERS
Method(Torrent, 'set_uploads_max', 'd.set_uploads_max'),
Method(Torrent, 'set_tied_to_file', 'd.set_tied_to_file'),
Method(Torrent, 'set_tracker_numwant', 'd.set_tracker_numwant'),
Method(Torrent, 'set_priority', 'd.set_priority'),
Method(Torrent, 'set_peers_max', 'd.set_peers_max'),
Method(Torrent, 'set_hashing_failed', 'd.set_hashing_failed'),
Method(Torrent, 'set_message', 'd.set_message'),
Method(Torrent, 'set_throttle_name', 'd.set_throttle_name'),
Method(Torrent, 'set_peers_min', 'd.set_peers_min'),
Method(Torrent, 'set_ignore_commands', 'd.set_ignore_commands'),
Method(Torrent, 'set_max_file_size', 'd.set_max_file_size'),
Method(Torrent, 'set_custom5', 'd.set_custom5'),
Method(Torrent, 'set_custom4', 'd.set_custom4'),
Method(Torrent, 'set_custom2', 'd.set_custom2'),
Method(Torrent, 'set_custom1', 'd.set_custom1'),
Method(Torrent, 'set_custom3', 'd.set_custom3'),
Method(Torrent, 'set_connection_current', 'd.set_connection_current'),
]

138
libs/rtorrent/tracker.py

@ -0,0 +1,138 @@
# 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.rpc import Method
import rtorrent.rpc
from rtorrent.common import safe_repr
Method = rtorrent.rpc.Method
class Tracker:
"""Represents an individual tracker within a L{Torrent} instance."""
def __init__(self, _rt_obj, info_hash, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent using this tracker
for k in kwargs.keys():
setattr(self, k, kwargs.get(k, None))
# for clarity's sake...
self.index = self.group # : position of tracker within the torrent's tracker list
self.rpc_id = "{0}:t{1}".format(
self.info_hash, self.index) # : unique id to pass to rTorrent
def __repr__(self):
return safe_repr("Tracker(index={0}, url=\"{1}\")",
self.index, self.url)
def enable(self):
"""Alias for set_enabled("yes")"""
self.set_enabled("yes")
def disable(self):
"""Alias for set_enabled("no")"""
self.set_enabled("no")
def update(self):
"""Refresh tracker data
@note: All fields are stored as attributes to self.
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
multicall.call()
methods = [
# RETRIEVERS
Method(Tracker, 'is_enabled', 't.is_enabled', boolean=True),
Method(Tracker, 'get_id', 't.get_id'),
Method(Tracker, 'get_scrape_incomplete', 't.get_scrape_incomplete'),
Method(Tracker, 'is_open', 't.is_open', boolean=True),
Method(Tracker, 'get_min_interval', 't.get_min_interval'),
Method(Tracker, 'get_scrape_downloaded', 't.get_scrape_downloaded'),
Method(Tracker, 'get_group', 't.get_group'),
Method(Tracker, 'get_scrape_time_last', 't.get_scrape_time_last'),
Method(Tracker, 'get_type', 't.get_type'),
Method(Tracker, 'get_normal_interval', 't.get_normal_interval'),
Method(Tracker, 'get_url', 't.get_url'),
Method(Tracker, 'get_scrape_complete', 't.get_scrape_complete',
min_version=(0, 8, 9),
),
Method(Tracker, 'get_activity_time_last', 't.activity_time_last',
min_version=(0, 8, 9),
),
Method(Tracker, 'get_activity_time_next', 't.activity_time_next',
min_version=(0, 8, 9),
),
Method(Tracker, 'get_failed_time_last', 't.failed_time_last',
min_version=(0, 8, 9),
),
Method(Tracker, 'get_failed_time_next', 't.failed_time_next',
min_version=(0, 8, 9),
),
Method(Tracker, 'get_success_time_last', 't.success_time_last',
min_version=(0, 8, 9),
),
Method(Tracker, 'get_success_time_next', 't.success_time_next',
min_version=(0, 8, 9),
),
Method(Tracker, 'can_scrape', 't.can_scrape',
min_version=(0, 9, 1),
boolean=True
),
Method(Tracker, 'get_failed_counter', 't.failed_counter',
min_version=(0, 8, 9)
),
Method(Tracker, 'get_scrape_counter', 't.scrape_counter',
min_version=(0, 8, 9)
),
Method(Tracker, 'get_success_counter', 't.success_counter',
min_version=(0, 8, 9)
),
Method(Tracker, 'is_usable', 't.is_usable',
min_version=(0, 9, 1),
boolean=True
),
Method(Tracker, 'is_busy', 't.is_busy',
min_version=(0, 9, 1),
boolean=True
),
Method(Tracker, 'is_extra_tracker', 't.is_extra_tracker',
min_version=(0, 9, 1),
boolean=True,
),
Method(Tracker, "get_latest_sum_peers", "t.latest_sum_peers",
min_version=(0, 9, 0)
),
Method(Tracker, "get_latest_new_peers", "t.latest_new_peers",
min_version=(0, 9, 0)
),
# MODIFIERS
Method(Tracker, 'set_enabled', 't.set_enabled'),
]

24
libs/synchronousdeluge/__init__.py

@ -0,0 +1,24 @@
"""A synchronous implementation of the Deluge RPC protocol
based on gevent-deluge by Christopher Rosell.
https://github.com/chrippa/gevent-deluge
Example usage:
from synchronousdeluge import DelgueClient
client = DelugeClient()
client.connect()
# Wait for value
download_location = client.core.get_config_value("download_location").get()
"""
__title__ = "synchronous-deluge"
__version__ = "0.1"
__author__ = "Christian Dale"
from .synchronousdeluge.client import DelugeClient
from .synchronousdeluge.exceptions import DelugeRPCError

135
libs/synchronousdeluge/client.py

@ -0,0 +1,135 @@
import os
from collections import defaultdict
from itertools import imap
from .synchronousdeluge.exceptions import DelugeRPCError
from .synchronousdeluge.protocol import DelugeRPCRequest, DelugeRPCResponse
from .synchronousdeluge.transfer import DelugeTransfer
__all__ = ["DelugeClient"]
RPC_RESPONSE = 1
RPC_ERROR = 2
RPC_EVENT = 3
class DelugeClient(object):
def __init__(self):
"""A deluge client session."""
self.transfer = DelugeTransfer()
self.modules = []
self._request_counter = 0
def _get_local_auth(self):
xdg_config = os.path.expanduser(os.environ.get("XDG_CONFIG_HOME", "~/.config"))
config_home = os.path.join(xdg_config, "deluge")
auth_file = os.path.join(config_home, "auth")
username = password = ""
with open(auth_file) as fd:
for line in fd:
if line.startswith("#"):
continue
auth = line.split(":")
if len(auth) >= 2 and auth[0] == "localclient":
username, password = auth[0], auth[1]
break
return username, password
def _create_module_method(self, module, method):
fullname = "{0}.{1}".format(module, method)
def func(obj, *args, **kwargs):
return self.remote_call(fullname, *args, **kwargs)
func.__name__ = method
return func
def _introspect(self):
self.modules = []
methods = self.remote_call("daemon.get_method_list").get()
methodmap = defaultdict(dict)
splitter = lambda v: v.split(".")
for module, method in imap(splitter, methods):
methodmap[module][method] = self._create_module_method(module, method)
for module, methods in methodmap.items():
clsname = "DelugeModule{0}".format(module.capitalize())
cls = type(clsname, (), methods)
setattr(self, module, cls())
self.modules.append(module)
def remote_call(self, method, *args, **kwargs):
req = DelugeRPCRequest(self._request_counter, method, *args, **kwargs)
message = next(self.transfer.send_request(req))
response = DelugeRPCResponse()
if not isinstance(message, tuple):
return
if len(message) < 3:
return
message_type = message[0]
# if message_type == RPC_EVENT:
# event = message[1]
# values = message[2]
#
# if event in self._event_handlers:
# for handler in self._event_handlers[event]:
# gevent.spawn(handler, *values)
#
# elif message_type in (RPC_RESPONSE, RPC_ERROR):
if message_type in (RPC_RESPONSE, RPC_ERROR):
request_id = message[1]
value = message[2]
if request_id == self._request_counter :
if message_type == RPC_RESPONSE:
response.set(value)
elif message_type == RPC_ERROR:
err = DelugeRPCError(*value)
response.set_exception(err)
self._request_counter += 1
return response
def connect(self, host="127.0.0.1", port=58846, username="", password=""):
"""Connects to a daemon process.
:param host: str, the hostname of the daemon
:param port: int, the port of the daemon
:param username: str, the username to login with
:param password: str, the password to login with
"""
# Connect transport
self.transfer.connect((host, port))
# Attempt to fetch local auth info if needed
if not username and host in ("127.0.0.1", "localhost"):
username, password = self._get_local_auth()
# Authenticate
self.remote_call("daemon.login", username, password).get()
# Introspect available methods
self._introspect()
@property
def connected(self):
return self.transfer.connected
def disconnect(self):
"""Disconnects from the daemon."""
self.transfer.disconnect()

11
libs/synchronousdeluge/exceptions.py

@ -0,0 +1,11 @@
__all__ = ["DelugeRPCError"]
class DelugeRPCError(Exception):
def __init__(self, name, msg, traceback):
self.name = name
self.msg = msg
self.traceback = traceback
def __str__(self):
return "{0}: {1}: {2}".format(self.__class__.__name__, self.name, self.msg)

38
libs/synchronousdeluge/protocol.py

@ -0,0 +1,38 @@
__all__ = ["DelugeRPCRequest", "DelugeRPCResponse"]
class DelugeRPCRequest(object):
def __init__(self, request_id, method, *args, **kwargs):
self.request_id = request_id
self.method = method
self.args = args
self.kwargs = kwargs
def format(self):
return (self.request_id, self.method, self.args, self.kwargs)
class DelugeRPCResponse(object):
def __init__(self):
self.value = None
self._exception = None
def successful(self):
return self._exception is None
@property
def exception(self):
if self._exception is not None:
return self._exception
def set(self, value=None):
self.value = value
self._exception = None
def set_exception(self, exception):
self._exception = exception
def get(self):
if self._exception is None:
return self.value
else:
raise self._exception

433
libs/synchronousdeluge/rencode.py

@ -0,0 +1,433 @@
"""
rencode -- Web safe object pickling/unpickling.
Public domain, Connelly Barnes 2006-2007.
The rencode module is a modified version of bencode from the
BitTorrent project. For complex, heterogeneous data structures with
many small elements, r-encodings take up significantly less space than
b-encodings:
>>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
13
>>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
26
The rencode format is not standardized, and may change with different
rencode module versions, so you should check that you are using the
same rencode version throughout your project.
"""
__version__ = '1.0.1'
__all__ = ['dumps', 'loads']
# Original bencode module by Petru Paler, et al.
#
# Modifications by Connelly Barnes:
#
# - Added support for floats (sent as 32-bit or 64-bit in network
# order), bools, None.
# - Allowed dict keys to be of any serializable type.
# - Lists/tuples are always decoded as tuples (thus, tuples can be
# used as dict keys).
# - Embedded extra information in the 'typecodes' to save some space.
# - Added a restriction on integer length, so that malicious hosts
# cannot pass us large integers which take a long time to decode.
#
# Licensed by Bram Cohen under the "MIT license":
#
# "Copyright (C) 2001-2002 Bram Cohen
#
# 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."
#
# (The rencode module is licensed under the above license as well).
#
import struct
import string
from threading import Lock
# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()).
DEFAULT_FLOAT_BITS = 32
# Maximum length of integer when written as base 10 string.
MAX_INT_LENGTH = 64
# The bencode 'typecodes' such as i, d, etc have been extended and
# relocated on the base-256 character set.
CHR_LIST = chr(59)
CHR_DICT = chr(60)
CHR_INT = chr(61)
CHR_INT1 = chr(62)
CHR_INT2 = chr(63)
CHR_INT4 = chr(64)
CHR_INT8 = chr(65)
CHR_FLOAT32 = chr(66)
CHR_FLOAT64 = chr(44)
CHR_TRUE = chr(67)
CHR_FALSE = chr(68)
CHR_NONE = chr(69)
CHR_TERM = chr(127)
# Positive integers with value embedded in typecode.
INT_POS_FIXED_START = 0
INT_POS_FIXED_COUNT = 44
# Dictionaries with length embedded in typecode.
DICT_FIXED_START = 102
DICT_FIXED_COUNT = 25
# Negative integers with value embedded in typecode.
INT_NEG_FIXED_START = 70
INT_NEG_FIXED_COUNT = 32
# Strings with length embedded in typecode.
STR_FIXED_START = 128
STR_FIXED_COUNT = 64
# Lists with length embedded in typecode.
LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT
LIST_FIXED_COUNT = 64
def decode_int(x, f):
f += 1
newf = x.index(CHR_TERM, f)
if newf - f >= MAX_INT_LENGTH:
raise ValueError('overflow')
try:
n = int(x[f:newf])
except (OverflowError, ValueError):
n = long(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_intb(x, f):
f += 1
return (struct.unpack('!b', x[f:f+1])[0], f+1)
def decode_inth(x, f):
f += 1
return (struct.unpack('!h', x[f:f+2])[0], f+2)
def decode_intl(x, f):
f += 1
return (struct.unpack('!l', x[f:f+4])[0], f+4)
def decode_intq(x, f):
f += 1
return (struct.unpack('!q', x[f:f+8])[0], f+8)
def decode_float32(x, f):
f += 1
n = struct.unpack('!f', x[f:f+4])[0]
return (n, f+4)
def decode_float64(x, f):
f += 1
n = struct.unpack('!d', x[f:f+8])[0]
return (n, f+8)
def decode_string(x, f):
colon = x.index(':', f)
try:
n = int(x[f:colon])
except (OverflowError, ValueError):
n = long(x[f:colon])
if x[f] == '0' and colon != f+1:
raise ValueError
colon += 1
s = x[colon:colon+n]
try:
t = s.decode("utf8")
if len(t) != len(s):
s = t
except UnicodeDecodeError:
pass
return (s, colon+n)
def decode_list(x, f):
r, f = [], f+1
while x[f] != CHR_TERM:
v, f = decode_func[x[f]](x, f)
r.append(v)
return (tuple(r), f + 1)
def decode_dict(x, f):
r, f = {}, f+1
while x[f] != CHR_TERM:
k, f = decode_func[x[f]](x, f)
r[k], f = decode_func[x[f]](x, f)
return (r, f + 1)
def decode_true(x, f):
return (True, f+1)
def decode_false(x, f):
return (False, f+1)
def decode_none(x, f):
return (None, f+1)
decode_func = {}
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
decode_func[CHR_LIST ] = decode_list
decode_func[CHR_DICT ] = decode_dict
decode_func[CHR_INT ] = decode_int
decode_func[CHR_INT1 ] = decode_intb
decode_func[CHR_INT2 ] = decode_inth
decode_func[CHR_INT4 ] = decode_intl
decode_func[CHR_INT8 ] = decode_intq
decode_func[CHR_FLOAT32] = decode_float32
decode_func[CHR_FLOAT64] = decode_float64
decode_func[CHR_TRUE ] = decode_true
decode_func[CHR_FALSE ] = decode_false
decode_func[CHR_NONE ] = decode_none
def make_fixed_length_string_decoders():
def make_decoder(slen):
def f(x, f):
s = x[f+1:f+1+slen]
try:
t = s.decode("utf8")
if len(t) != len(s):
s = t
except UnicodeDecodeError:
pass
return (s, f+1+slen)
return f
for i in range(STR_FIXED_COUNT):
decode_func[chr(STR_FIXED_START+i)] = make_decoder(i)
make_fixed_length_string_decoders()
def make_fixed_length_list_decoders():
def make_decoder(slen):
def f(x, f):
r, f = [], f+1
for i in range(slen):
v, f = decode_func[x[f]](x, f)
r.append(v)
return (tuple(r), f)
return f
for i in range(LIST_FIXED_COUNT):
decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i)
make_fixed_length_list_decoders()
def make_fixed_length_int_decoders():
def make_decoder(j):
def f(x, f):
return (j, f+1)
return f
for i in range(INT_POS_FIXED_COUNT):
decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i)
for i in range(INT_NEG_FIXED_COUNT):
decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i)
make_fixed_length_int_decoders()
def make_fixed_length_dict_decoders():
def make_decoder(slen):
def f(x, f):
r, f = {}, f+1
for j in range(slen):
k, f = decode_func[x[f]](x, f)
r[k], f = decode_func[x[f]](x, f)
return (r, f)
return f
for i in range(DICT_FIXED_COUNT):
decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i)
make_fixed_length_dict_decoders()
def encode_dict(x,r):
r.append(CHR_DICT)
for k, v in x.items():
encode_func[type(k)](k, r)
encode_func[type(v)](v, r)
r.append(CHR_TERM)
def loads(x):
try:
r, l = decode_func[x[0]](x, 0)
except (IndexError, KeyError):
raise ValueError
if l != len(x):
raise ValueError
return r
from types import StringType, IntType, LongType, DictType, ListType, TupleType, FloatType, NoneType, UnicodeType
def encode_int(x, r):
if 0 <= x < INT_POS_FIXED_COUNT:
r.append(chr(INT_POS_FIXED_START+x))
elif -INT_NEG_FIXED_COUNT <= x < 0:
r.append(chr(INT_NEG_FIXED_START-1-x))
elif -128 <= x < 128:
r.extend((CHR_INT1, struct.pack('!b', x)))
elif -32768 <= x < 32768:
r.extend((CHR_INT2, struct.pack('!h', x)))
elif -2147483648 <= x < 2147483648:
r.extend((CHR_INT4, struct.pack('!l', x)))
elif -9223372036854775808 <= x < 9223372036854775808:
r.extend((CHR_INT8, struct.pack('!q', x)))
else:
s = str(x)
if len(s) >= MAX_INT_LENGTH:
raise ValueError('overflow')
r.extend((CHR_INT, s, CHR_TERM))
def encode_float32(x, r):
r.extend((CHR_FLOAT32, struct.pack('!f', x)))
def encode_float64(x, r):
r.extend((CHR_FLOAT64, struct.pack('!d', x)))
def encode_bool(x, r):
r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)])
def encode_none(x, r):
r.extend(CHR_NONE)
def encode_string(x, r):
if len(x) < STR_FIXED_COUNT:
r.extend((chr(STR_FIXED_START + len(x)), x))
else:
r.extend((str(len(x)), ':', x))
def encode_unicode(x, r):
encode_string(x.encode("utf8"), r)
def encode_list(x, r):
if len(x) < LIST_FIXED_COUNT:
r.append(chr(LIST_FIXED_START + len(x)))
for i in x:
encode_func[type(i)](i, r)
else:
r.append(CHR_LIST)
for i in x:
encode_func[type(i)](i, r)
r.append(CHR_TERM)
def encode_dict(x,r):
if len(x) < DICT_FIXED_COUNT:
r.append(chr(DICT_FIXED_START + len(x)))
for k, v in x.items():
encode_func[type(k)](k, r)
encode_func[type(v)](v, r)
else:
r.append(CHR_DICT)
for k, v in x.items():
encode_func[type(k)](k, r)
encode_func[type(v)](v, r)
r.append(CHR_TERM)
encode_func = {}
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
encode_func[NoneType] = encode_none
encode_func[UnicodeType] = encode_unicode
lock = Lock()
try:
from types import BooleanType
encode_func[BooleanType] = encode_bool
except ImportError:
pass
def dumps(x, float_bits=DEFAULT_FLOAT_BITS):
"""
Dump data structure to str.
Here float_bits is either 32 or 64.
"""
lock.acquire()
try:
if float_bits == 32:
encode_func[FloatType] = encode_float32
elif float_bits == 64:
encode_func[FloatType] = encode_float64
else:
raise ValueError('Float bits (%d) is not 32 or 64' % float_bits)
r = []
encode_func[type(x)](x, r)
finally:
lock.release()
return ''.join(r)
def test():
f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0]
f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0]
f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),)
assert loads(dumps(L)) == L
d = dict(zip(range(-100000,100000),range(-100000,100000)))
d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False})
L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''})
assert loads(dumps(L)) == L
L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000)
assert loads(dumps(L)) == L
L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple([tuple(range(n)) for n in range(100)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple(['a'*n for n in range(1000)]) + ('b',)
assert loads(dumps(L)) == L
L = tuple(['a'*n for n in range(1000)]) + (None,True,None)
assert loads(dumps(L)) == L
assert loads(dumps(None)) == None
assert loads(dumps({None:None})) == {None:None}
assert 1e-10<abs(loads(dumps(1.1))-1.1)<1e-6
assert 1e-10<abs(loads(dumps(1.1,32))-1.1)<1e-6
assert abs(loads(dumps(1.1,64))-1.1)<1e-12
assert loads(dumps(u"Hello World!!"))
try:
import psyco
psyco.bind(dumps)
psyco.bind(loads)
except ImportError:
pass
if __name__ == '__main__':
test()

57
libs/synchronousdeluge/transfer.py

@ -0,0 +1,57 @@
import zlib
import struct
import socket
import ssl
from synchronousdeluge import rencode
__all__ = ["DelugeTransfer"]
class DelugeTransfer(object):
def __init__(self):
self.sock = None
self.conn = None
self.connected = False
def connect(self, hostport):
if self.connected:
self.disconnect()
self.sock = socket.create_connection(hostport)
self.conn = ssl.wrap_socket(self.sock)
self.connected = True
def disconnect(self):
if self.conn:
self.conn.close()
self.connected = False
def send_request(self, request):
data = (request.format(),)
payload = zlib.compress(rencode.dumps(data))
self.conn.sendall(payload)
buf = b""
while True:
data = self.conn.recv(1024)
if not data:
self.connected = False
break
buf += data
dobj = zlib.decompressobj()
try:
message = rencode.loads(dobj.decompress(buf))
except (ValueError, zlib.error, struct.error):
# Probably incomplete data, read more
continue
else:
buf = dobj.unused_data
yield message

27
libs/unrar2/PKG-INFO

@ -0,0 +1,27 @@
Metadata-Version: 1.0
Name: pyUnRAR2
Version: 0.99.2
Summary: Improved Python wrapper around the free UnRAR.dll
Home-page: http://code.google.com/py-unrar2
Author: Konstantin Yegupov
Author-email: yk4ever@gmail.com
License: MIT
Description: pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
stable and foolproof.
Notice that it has INCOMPATIBLE interface.
It enables reading and unpacking of archives created with the
RAR/WinRAR archivers. There is a low-level interface which is very
similar to the C interface provided by UnRAR. There is also a
higher level interface which makes some common operations easier.
Platform: Windows
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Win32 (MS Windows)
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Archiving :: Compression

191
libs/unrar2/UnRAR2.html

@ -0,0 +1,191 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: package UnRAR2</title>
</head><body bgcolor="#f0f0f8">
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong>UnRAR2</strong></big></big> (version 0.99.1)</font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///C|/python26/lib/site-packages/unrar2/__init__.py">c:\python26\lib\site-packages\unrar2\__init__.py</a></font></td></tr></table>
<p><tt>pyUnRAR2&nbsp;is&nbsp;a&nbsp;ctypes&nbsp;based&nbsp;wrapper&nbsp;around&nbsp;the&nbsp;free&nbsp;UnRAR.dll.&nbsp;<br>
&nbsp;<br>
It&nbsp;is&nbsp;an&nbsp;modified&nbsp;version&nbsp;of&nbsp;Jimmy&nbsp;Retzlaff's&nbsp;pyUnRAR&nbsp;-&nbsp;more&nbsp;simple,<br>
stable&nbsp;and&nbsp;foolproof.<br>
Notice&nbsp;that&nbsp;it&nbsp;has&nbsp;INCOMPATIBLE&nbsp;interface.<br>
&nbsp;<br>
It&nbsp;enables&nbsp;reading&nbsp;and&nbsp;unpacking&nbsp;of&nbsp;archives&nbsp;created&nbsp;with&nbsp;the<br>
RAR/WinRAR&nbsp;archivers.&nbsp;There&nbsp;is&nbsp;a&nbsp;low-level&nbsp;interface&nbsp;which&nbsp;is&nbsp;very<br>
similar&nbsp;to&nbsp;the&nbsp;C&nbsp;interface&nbsp;provided&nbsp;by&nbsp;UnRAR.&nbsp;There&nbsp;is&nbsp;also&nbsp;a<br>
higher&nbsp;level&nbsp;interface&nbsp;which&nbsp;makes&nbsp;some&nbsp;common&nbsp;operations&nbsp;easier.</tt></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="UnRAR2.rar_exceptions.html">rar_exceptions</a><br>
<a href="UnRAR2.setup.html">setup</a><br>
</td><td width="25%" valign=top><a href="UnRAR2.test_UnRAR2.html">test_UnRAR2</a><br>
<a href="UnRAR2.unix.html">unix</a><br>
</td><td width="25%" valign=top><a href="UnRAR2.windows.html">windows</a><br>
</td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl>
<dt><font face="helvetica, arial"><a href="UnRAR2.windows.html#RarFileImplementation">UnRAR2.windows.RarFileImplementation</a>(<a href="__builtin__.html#object">__builtin__.object</a>)
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="UnRAR2.html#RarFile">RarFile</a>
</font></dt></dl>
</dd>
<dt><font face="helvetica, arial"><a href="__builtin__.html#object">__builtin__.object</a>
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="UnRAR2.html#RarInfo">RarInfo</a>
</font></dt></dl>
</dd>
</dl>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="RarFile">class <strong>RarFile</strong></a>(<a href="UnRAR2.windows.html#RarFileImplementation">UnRAR2.windows.RarFileImplementation</a>)</font></td></tr>
<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl><dt>Method resolution order:</dt>
<dd><a href="UnRAR2.html#RarFile">RarFile</a></dd>
<dd><a href="UnRAR2.windows.html#RarFileImplementation">UnRAR2.windows.RarFileImplementation</a></dd>
<dd><a href="__builtin__.html#object">__builtin__.object</a></dd>
</dl>
<hr>
Methods defined here:<br>
<dl><dt><a name="RarFile-__del__"><strong>__del__</strong></a>(self)</dt></dl>
<dl><dt><a name="RarFile-__init__"><strong>__init__</strong></a>(self, archiveName, password<font color="#909090">=None</font>)</dt><dd><tt>Instantiate&nbsp;the&nbsp;archive.<br>
&nbsp;<br>
archiveName&nbsp;is&nbsp;the&nbsp;name&nbsp;of&nbsp;the&nbsp;RAR&nbsp;file.<br>
password&nbsp;is&nbsp;used&nbsp;to&nbsp;decrypt&nbsp;the&nbsp;files&nbsp;in&nbsp;the&nbsp;archive.<br>
&nbsp;<br>
Properties:<br>
&nbsp;&nbsp;&nbsp;&nbsp;comment&nbsp;-&nbsp;comment&nbsp;associated&nbsp;with&nbsp;the&nbsp;archive<br>
&nbsp;<br>
&gt;&gt;&gt;&nbsp;print&nbsp;<a href="#RarFile">RarFile</a>('test.rar').comment<br>
This&nbsp;is&nbsp;a&nbsp;test.</tt></dd></dl>
<dl><dt><a name="RarFile-extract"><strong>extract</strong></a>(self, condition<font color="#909090">='*'</font>, path<font color="#909090">='.'</font>, withSubpath<font color="#909090">=True</font>, overwrite<font color="#909090">=True</font>)</dt><dd><tt>Extract&nbsp;specific&nbsp;files&nbsp;from&nbsp;archive&nbsp;to&nbsp;disk.<br>
&nbsp;<br>
If&nbsp;"condition"&nbsp;is&nbsp;a&nbsp;list&nbsp;of&nbsp;numbers,&nbsp;then&nbsp;extract&nbsp;files&nbsp;which&nbsp;have&nbsp;those&nbsp;positions&nbsp;in&nbsp;infolist.<br>
If&nbsp;"condition"&nbsp;is&nbsp;a&nbsp;string,&nbsp;then&nbsp;it&nbsp;is&nbsp;treated&nbsp;as&nbsp;a&nbsp;wildcard&nbsp;for&nbsp;names&nbsp;of&nbsp;files&nbsp;to&nbsp;extract.<br>
If&nbsp;"condition"&nbsp;is&nbsp;a&nbsp;function,&nbsp;it&nbsp;is&nbsp;treated&nbsp;as&nbsp;a&nbsp;callback&nbsp;function,&nbsp;which&nbsp;accepts&nbsp;a&nbsp;<a href="#RarInfo">RarInfo</a>&nbsp;<a href="__builtin__.html#object">object</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;and&nbsp;returns&nbsp;either&nbsp;boolean&nbsp;True&nbsp;(extract)&nbsp;or&nbsp;boolean&nbsp;False&nbsp;(skip).<br>
DEPRECATED:&nbsp;If&nbsp;"condition"&nbsp;callback&nbsp;returns&nbsp;string&nbsp;(only&nbsp;supported&nbsp;for&nbsp;Windows)&nbsp;-&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;that&nbsp;string&nbsp;will&nbsp;be&nbsp;used&nbsp;as&nbsp;a&nbsp;new&nbsp;name&nbsp;to&nbsp;save&nbsp;the&nbsp;file&nbsp;under.<br>
If&nbsp;"condition"&nbsp;is&nbsp;omitted,&nbsp;all&nbsp;files&nbsp;are&nbsp;extracted.<br>
&nbsp;<br>
"path"&nbsp;is&nbsp;a&nbsp;directory&nbsp;to&nbsp;extract&nbsp;to<br>
"withSubpath"&nbsp;flag&nbsp;denotes&nbsp;whether&nbsp;files&nbsp;are&nbsp;extracted&nbsp;with&nbsp;their&nbsp;full&nbsp;path&nbsp;in&nbsp;the&nbsp;archive.<br>
"overwrite"&nbsp;flag&nbsp;denotes&nbsp;whether&nbsp;extracted&nbsp;files&nbsp;will&nbsp;overwrite&nbsp;old&nbsp;ones.&nbsp;Defaults&nbsp;to&nbsp;true.<br>
&nbsp;<br>
Returns&nbsp;list&nbsp;of&nbsp;RarInfos&nbsp;for&nbsp;extracted&nbsp;files.</tt></dd></dl>
<dl><dt><a name="RarFile-infoiter"><strong>infoiter</strong></a>(self)</dt><dd><tt>Iterate&nbsp;over&nbsp;all&nbsp;the&nbsp;files&nbsp;in&nbsp;the&nbsp;archive,&nbsp;generating&nbsp;RarInfos.<br>
&nbsp;<br>
&gt;&gt;&gt;&nbsp;import&nbsp;os<br>
&gt;&gt;&gt;&nbsp;for&nbsp;fileInArchive&nbsp;in&nbsp;<a href="#RarFile">RarFile</a>('test.rar').<a href="#RarFile-infoiter">infoiter</a>():<br>
...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print&nbsp;os.path.split(fileInArchive.filename)[-1],<br>
...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print&nbsp;fileInArchive.isdir,<br>
...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print&nbsp;fileInArchive.size,<br>
...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print&nbsp;fileInArchive.comment,<br>
...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print&nbsp;tuple(fileInArchive.datetime)[0:5],<br>
...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print&nbsp;time.strftime('%a,&nbsp;%d&nbsp;%b&nbsp;%Y&nbsp;%H:%M',&nbsp;fileInArchive.datetime)<br>
test&nbsp;True&nbsp;0&nbsp;None&nbsp;(2003,&nbsp;6,&nbsp;30,&nbsp;1,&nbsp;59)&nbsp;Mon,&nbsp;30&nbsp;Jun&nbsp;2003&nbsp;01:59<br>
test.txt&nbsp;False&nbsp;20&nbsp;None&nbsp;(2003,&nbsp;6,&nbsp;30,&nbsp;2,&nbsp;1)&nbsp;Mon,&nbsp;30&nbsp;Jun&nbsp;2003&nbsp;02:01<br>
this.py&nbsp;False&nbsp;1030&nbsp;None&nbsp;(2002,&nbsp;2,&nbsp;8,&nbsp;16,&nbsp;47)&nbsp;Fri,&nbsp;08&nbsp;Feb&nbsp;2002&nbsp;16:47</tt></dd></dl>
<dl><dt><a name="RarFile-infolist"><strong>infolist</strong></a>(self)</dt><dd><tt>Return&nbsp;a&nbsp;list&nbsp;of&nbsp;RarInfos,&nbsp;descripting&nbsp;the&nbsp;contents&nbsp;of&nbsp;the&nbsp;archive.</tt></dd></dl>
<dl><dt><a name="RarFile-read_files"><strong>read_files</strong></a>(self, condition<font color="#909090">='*'</font>)</dt><dd><tt>Read&nbsp;specific&nbsp;files&nbsp;from&nbsp;archive&nbsp;into&nbsp;memory.<br>
If&nbsp;"condition"&nbsp;is&nbsp;a&nbsp;list&nbsp;of&nbsp;numbers,&nbsp;then&nbsp;return&nbsp;files&nbsp;which&nbsp;have&nbsp;those&nbsp;positions&nbsp;in&nbsp;infolist.<br>
If&nbsp;"condition"&nbsp;is&nbsp;a&nbsp;string,&nbsp;then&nbsp;it&nbsp;is&nbsp;treated&nbsp;as&nbsp;a&nbsp;wildcard&nbsp;for&nbsp;names&nbsp;of&nbsp;files&nbsp;to&nbsp;extract.<br>
If&nbsp;"condition"&nbsp;is&nbsp;a&nbsp;function,&nbsp;it&nbsp;is&nbsp;treated&nbsp;as&nbsp;a&nbsp;callback&nbsp;function,&nbsp;which&nbsp;accepts&nbsp;a&nbsp;<a href="#RarInfo">RarInfo</a>&nbsp;<a href="__builtin__.html#object">object</a>&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;and&nbsp;returns&nbsp;boolean&nbsp;True&nbsp;(extract)&nbsp;or&nbsp;False&nbsp;(skip).<br>
If&nbsp;"condition"&nbsp;is&nbsp;omitted,&nbsp;all&nbsp;files&nbsp;are&nbsp;returned.<br>
&nbsp;<br>
Returns&nbsp;list&nbsp;of&nbsp;tuples&nbsp;(<a href="#RarInfo">RarInfo</a>&nbsp;info,&nbsp;str&nbsp;contents)</tt></dd></dl>
<hr>
Methods inherited from <a href="UnRAR2.windows.html#RarFileImplementation">UnRAR2.windows.RarFileImplementation</a>:<br>
<dl><dt><a name="RarFile-destruct"><strong>destruct</strong></a>(self)</dt></dl>
<dl><dt><a name="RarFile-init"><strong>init</strong></a>(self, password<font color="#909090">=None</font>)</dt></dl>
<dl><dt><a name="RarFile-make_sure_ready"><strong>make_sure_ready</strong></a>(self)</dt></dl>
<hr>
Data descriptors inherited from <a href="UnRAR2.windows.html#RarFileImplementation">UnRAR2.windows.RarFileImplementation</a>:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
</td></tr></table> <p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="RarInfo">class <strong>RarInfo</strong></a>(<a href="__builtin__.html#object">__builtin__.object</a>)</font></td></tr>
<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
<td colspan=2><tt>Represents&nbsp;a&nbsp;file&nbsp;header&nbsp;in&nbsp;an&nbsp;archive.&nbsp;Don't&nbsp;instantiate&nbsp;directly.<br>
Use&nbsp;only&nbsp;to&nbsp;obtain&nbsp;information&nbsp;about&nbsp;file.<br>
YOU&nbsp;CANNOT&nbsp;EXTRACT&nbsp;FILE&nbsp;CONTENTS&nbsp;USING&nbsp;THIS&nbsp;OBJECT.<br>
USE&nbsp;METHODS&nbsp;OF&nbsp;<a href="#RarFile">RarFile</a>&nbsp;CLASS&nbsp;INSTEAD.<br>
&nbsp;<br>
Properties:<br>
&nbsp;&nbsp;&nbsp;&nbsp;index&nbsp;-&nbsp;index&nbsp;of&nbsp;file&nbsp;within&nbsp;the&nbsp;archive<br>
&nbsp;&nbsp;&nbsp;&nbsp;filename&nbsp;-&nbsp;name&nbsp;of&nbsp;the&nbsp;file&nbsp;in&nbsp;the&nbsp;archive&nbsp;including&nbsp;path&nbsp;(if&nbsp;any)<br>
&nbsp;&nbsp;&nbsp;&nbsp;datetime&nbsp;-&nbsp;file&nbsp;date/time&nbsp;as&nbsp;a&nbsp;struct_time&nbsp;suitable&nbsp;for&nbsp;time.strftime<br>
&nbsp;&nbsp;&nbsp;&nbsp;isdir&nbsp;-&nbsp;True&nbsp;if&nbsp;the&nbsp;file&nbsp;is&nbsp;a&nbsp;directory<br>
&nbsp;&nbsp;&nbsp;&nbsp;size&nbsp;-&nbsp;size&nbsp;in&nbsp;bytes&nbsp;of&nbsp;the&nbsp;uncompressed&nbsp;file<br>
&nbsp;&nbsp;&nbsp;&nbsp;comment&nbsp;-&nbsp;comment&nbsp;associated&nbsp;with&nbsp;the&nbsp;file<br>
&nbsp;&nbsp;&nbsp;&nbsp;<br>
Note&nbsp;-&nbsp;this&nbsp;is&nbsp;not&nbsp;currently&nbsp;intended&nbsp;to&nbsp;be&nbsp;a&nbsp;Python&nbsp;file-like&nbsp;<a href="__builtin__.html#object">object</a>.<br>&nbsp;</tt></td></tr>
<tr><td>&nbsp;</td>
<td width="100%">Methods defined here:<br>
<dl><dt><a name="RarInfo-__init__"><strong>__init__</strong></a>(self, rarfile, data)</dt></dl>
<dl><dt><a name="RarInfo-__str__"><strong>__str__</strong></a>(self)</dt></dl>
<hr>
Data descriptors defined here:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
</td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#eeaa77">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
<tr><td bgcolor="#eeaa77"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl><dt><a name="-condition2checker"><strong>condition2checker</strong></a>(condition)</dt><dd><tt>Converts&nbsp;different&nbsp;condition&nbsp;types&nbsp;to&nbsp;callback</tt></dd></dl>
</td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#55aa55">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><strong>__version__</strong> = '0.99.1'<br>
<strong>in_windows</strong> = True</td></tr></table>
</body></html>

18
libs/unrar2/UnRARDLL/license.txt

@ -0,0 +1,18 @@
The unrar.dll library is freeware. This means:
1. All copyrights to RAR and the unrar.dll are exclusively
owned by the author - Alexander Roshal.
2. The unrar.dll library may be used in any software to handle RAR
archives without limitations free of charge.
3. THE RAR ARCHIVER AND THE UNRAR.DLL LIBRARY ARE DISTRIBUTED "AS IS".
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
OR MISUSING THIS SOFTWARE.
Thank you for your interest in RAR and unrar.dll.
Alexander L. Roshal

BIN
libs/unrar2/UnRARDLL/unrar.dll

Binary file not shown.

140
libs/unrar2/UnRARDLL/unrar.h

@ -0,0 +1,140 @@
#ifndef _UNRAR_DLL_
#define _UNRAR_DLL_
#define ERAR_END_ARCHIVE 10
#define ERAR_NO_MEMORY 11
#define ERAR_BAD_DATA 12
#define ERAR_BAD_ARCHIVE 13
#define ERAR_UNKNOWN_FORMAT 14
#define ERAR_EOPEN 15
#define ERAR_ECREATE 16
#define ERAR_ECLOSE 17
#define ERAR_EREAD 18
#define ERAR_EWRITE 19
#define ERAR_SMALL_BUF 20
#define ERAR_UNKNOWN 21
#define ERAR_MISSING_PASSWORD 22
#define RAR_OM_LIST 0
#define RAR_OM_EXTRACT 1
#define RAR_OM_LIST_INCSPLIT 2
#define RAR_SKIP 0
#define RAR_TEST 1
#define RAR_EXTRACT 2
#define RAR_VOL_ASK 0
#define RAR_VOL_NOTIFY 1
#define RAR_DLL_VERSION 4
#ifdef _UNIX
#define CALLBACK
#define PASCAL
#define LONG long
#define HANDLE void *
#define LPARAM long
#define UINT unsigned int
#endif
struct RARHeaderData
{
char ArcName[260];
char FileName[260];
unsigned int Flags;
unsigned int PackSize;
unsigned int UnpSize;
unsigned int HostOS;
unsigned int FileCRC;
unsigned int FileTime;
unsigned int UnpVer;
unsigned int Method;
unsigned int FileAttr;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
};
struct RARHeaderDataEx
{
char ArcName[1024];
wchar_t ArcNameW[1024];
char FileName[1024];
wchar_t FileNameW[1024];
unsigned int Flags;
unsigned int PackSize;
unsigned int PackSizeHigh;
unsigned int UnpSize;
unsigned int UnpSizeHigh;
unsigned int HostOS;
unsigned int FileCRC;
unsigned int FileTime;
unsigned int UnpVer;
unsigned int Method;
unsigned int FileAttr;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
unsigned int Reserved[1024];
};
struct RAROpenArchiveData
{
char *ArcName;
unsigned int OpenMode;
unsigned int OpenResult;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
};
struct RAROpenArchiveDataEx
{
char *ArcName;
wchar_t *ArcNameW;
unsigned int OpenMode;
unsigned int OpenResult;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
unsigned int Flags;
unsigned int Reserved[32];
};
enum UNRARCALLBACK_MESSAGES {
UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD
};
typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2);
typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode);
typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size);
#ifdef __cplusplus
extern "C" {
#endif
HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData);
HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData);
int PASCAL RARCloseArchive(HANDLE hArcData);
int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData);
int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData);
int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName);
int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName);
void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData);
void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc);
void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc);
void PASCAL RARSetPassword(HANDLE hArcData,char *Password);
int PASCAL RARGetDllVersion();
#ifdef __cplusplus
}
#endif
#endif

BIN
libs/unrar2/UnRARDLL/unrar.lib

Binary file not shown.

606
libs/unrar2/UnRARDLL/unrardll.txt

@ -0,0 +1,606 @@
UnRAR.dll Manual
~~~~~~~~~~~~~~~~
UnRAR.dll is a 32-bit Windows dynamic-link library which provides
file extraction from RAR archives.
Exported functions
====================================================================
HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData)
====================================================================
Description
~~~~~~~~~~~
Open RAR archive and allocate memory structures
Parameters
~~~~~~~~~~
ArchiveData Points to RAROpenArchiveData structure
struct RAROpenArchiveData
{
char *ArcName;
UINT OpenMode;
UINT OpenResult;
char *CmtBuf;
UINT CmtBufSize;
UINT CmtSize;
UINT CmtState;
};
Structure fields:
ArcName
Input parameter which should point to zero terminated string
containing the archive name.
OpenMode
Input parameter.
Possible values
RAR_OM_LIST
Open archive for reading file headers only.
RAR_OM_EXTRACT
Open archive for testing and extracting files.
RAR_OM_LIST_INCSPLIT
Open archive for reading file headers only. If you open an archive
in such mode, RARReadHeader[Ex] will return all file headers,
including those with "file continued from previous volume" flag.
In case of RAR_OM_LIST such headers are automatically skipped.
So if you process RAR volumes in RAR_OM_LIST_INCSPLIT mode, you will
get several file header records for same file if file is split between
volumes. For such files only the last file header record will contain
the correct file CRC and if you wish to get the correct packed size,
you need to sum up packed sizes of all parts.
OpenResult
Output parameter.
Possible values
0 Success
ERAR_NO_MEMORY Not enough memory to initialize data structures
ERAR_BAD_DATA Archive header broken
ERAR_BAD_ARCHIVE File is not valid RAR archive
ERAR_UNKNOWN_FORMAT Unknown encryption used for archive headers
ERAR_EOPEN File open error
CmtBuf
Input parameter which should point to the buffer for archive
comments. Maximum comment size is limited to 64Kb. Comment text is
zero terminated. If the comment text is larger than the buffer
size, the comment text will be truncated. If CmtBuf is set to
NULL, comments will not be read.
CmtBufSize
Input parameter which should contain size of buffer for archive
comments.
CmtSize
Output parameter containing size of comments actually read into the
buffer, cannot exceed CmtBufSize.
CmtState
Output parameter.
Possible values
0 comments not present
1 Comments read completely
ERAR_NO_MEMORY Not enough memory to extract comments
ERAR_BAD_DATA Broken comment
ERAR_UNKNOWN_FORMAT Unknown comment format
ERAR_SMALL_BUF Buffer too small, comments not completely read
Return values
~~~~~~~~~~~~~
Archive handle or NULL in case of error
========================================================================
HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData)
========================================================================
Description
~~~~~~~~~~~
Similar to RAROpenArchive, but uses RAROpenArchiveDataEx structure
allowing to specify Unicode archive name and returning information
about archive flags.
Parameters
~~~~~~~~~~
ArchiveData Points to RAROpenArchiveDataEx structure
struct RAROpenArchiveDataEx
{
char *ArcName;
wchar_t *ArcNameW;
unsigned int OpenMode;
unsigned int OpenResult;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
unsigned int Flags;
unsigned int Reserved[32];
};
Structure fields:
ArcNameW
Input parameter which should point to zero terminated Unicode string
containing the archive name or NULL if Unicode name is not specified.
Flags
Output parameter. Combination of bit flags.
Possible values
0x0001 - Volume attribute (archive volume)
0x0002 - Archive comment present
0x0004 - Archive lock attribute
0x0008 - Solid attribute (solid archive)
0x0010 - New volume naming scheme ('volname.partN.rar')
0x0020 - Authenticity information present
0x0040 - Recovery record present
0x0080 - Block headers are encrypted
0x0100 - First volume (set only by RAR 3.0 and later)
Reserved[32]
Reserved for future use. Must be zero.
Information on other structure fields and function return values
is available above, in RAROpenArchive function description.
====================================================================
int PASCAL RARCloseArchive(HANDLE hArcData)
====================================================================
Description
~~~~~~~~~~~
Close RAR archive and release allocated memory. It must be called when
archive processing is finished, even if the archive processing was stopped
due to an error.
Parameters
~~~~~~~~~~
hArcData
This parameter should contain the archive handle obtained from the
RAROpenArchive function call.
Return values
~~~~~~~~~~~~~
0 Success
ERAR_ECLOSE Archive close error
====================================================================
int PASCAL RARReadHeader(HANDLE hArcData,
struct RARHeaderData *HeaderData)
====================================================================
Description
~~~~~~~~~~~
Read header of file in archive.
Parameters
~~~~~~~~~~
hArcData
This parameter should contain the archive handle obtained from the
RAROpenArchive function call.
HeaderData
It should point to RARHeaderData structure:
struct RARHeaderData
{
char ArcName[260];
char FileName[260];
UINT Flags;
UINT PackSize;
UINT UnpSize;
UINT HostOS;
UINT FileCRC;
UINT FileTime;
UINT UnpVer;
UINT Method;
UINT FileAttr;
char *CmtBuf;
UINT CmtBufSize;
UINT CmtSize;
UINT CmtState;
};
Structure fields:
ArcName
Output parameter which contains a zero terminated string of the
current archive name. May be used to determine the current volume
name.
FileName
Output parameter which contains a zero terminated string of the
file name in OEM (DOS) encoding.
Flags
Output parameter which contains file flags:
0x01 - file continued from previous volume
0x02 - file continued on next volume
0x04 - file encrypted with password
0x08 - file comment present
0x10 - compression of previous files is used (solid flag)
bits 7 6 5
0 0 0 - dictionary size 64 Kb
0 0 1 - dictionary size 128 Kb
0 1 0 - dictionary size 256 Kb
0 1 1 - dictionary size 512 Kb
1 0 0 - dictionary size 1024 Kb
1 0 1 - dictionary size 2048 KB
1 1 0 - dictionary size 4096 KB
1 1 1 - file is directory
Other bits are reserved.
PackSize
Output parameter means packed file size or size of the
file part if file was split between volumes.
UnpSize
Output parameter - unpacked file size.
HostOS
Output parameter - operating system used for archiving:
0 - MS DOS;
1 - OS/2.
2 - Win32
3 - Unix
FileCRC
Output parameter which contains unpacked file CRC. In case of file parts
split between volumes only the last part contains the correct CRC
and it is accessible only in RAR_OM_LIST_INCSPLIT listing mode.
FileTime
Output parameter - contains date and time in standard MS DOS format.
UnpVer
Output parameter - RAR version needed to extract file.
It is encoded as 10 * Major version + minor version.
Method
Output parameter - packing method.
FileAttr
Output parameter - file attributes.
CmtBuf
File comments support is not implemented in the new DLL version yet.
Now CmtState is always 0.
/*
* Input parameter which should point to the buffer for file
* comments. Maximum comment size is limited to 64Kb. Comment text is
* a zero terminated string in OEM encoding. If the comment text is
* larger than the buffer size, the comment text will be truncated.
* If CmtBuf is set to NULL, comments will not be read.
*/
CmtBufSize
Input parameter which should contain size of buffer for archive
comments.
CmtSize
Output parameter containing size of comments actually read into the
buffer, should not exceed CmtBufSize.
CmtState
Output parameter.
Possible values
0 Absent comments
1 Comments read completely
ERAR_NO_MEMORY Not enough memory to extract comments
ERAR_BAD_DATA Broken comment
ERAR_UNKNOWN_FORMAT Unknown comment format
ERAR_SMALL_BUF Buffer too small, comments not completely read
Return values
~~~~~~~~~~~~~
0 Success
ERAR_END_ARCHIVE End of archive
ERAR_BAD_DATA File header broken
====================================================================
int PASCAL RARReadHeaderEx(HANDLE hArcData,
struct RARHeaderDataEx *HeaderData)
====================================================================
Description
~~~~~~~~~~~
Similar to RARReadHeader, but uses RARHeaderDataEx structure,
containing information about Unicode file names and 64 bit file sizes.
struct RARHeaderDataEx
{
char ArcName[1024];
wchar_t ArcNameW[1024];
char FileName[1024];
wchar_t FileNameW[1024];
unsigned int Flags;
unsigned int PackSize;
unsigned int PackSizeHigh;
unsigned int UnpSize;
unsigned int UnpSizeHigh;
unsigned int HostOS;
unsigned int FileCRC;
unsigned int FileTime;
unsigned int UnpVer;
unsigned int Method;
unsigned int FileAttr;
char *CmtBuf;
unsigned int CmtBufSize;
unsigned int CmtSize;
unsigned int CmtState;
unsigned int Reserved[1024];
};
====================================================================
int PASCAL RARProcessFile(HANDLE hArcData,
int Operation,
char *DestPath,
char *DestName)
====================================================================
Description
~~~~~~~~~~~
Performs action and moves the current position in the archive to
the next file. Extract or test the current file from the archive
opened in RAR_OM_EXTRACT mode. If the mode RAR_OM_LIST is set,
then a call to this function will simply skip the archive position
to the next file.
Parameters
~~~~~~~~~~
hArcData
This parameter should contain the archive handle obtained from the
RAROpenArchive function call.
Operation
File operation.
Possible values
RAR_SKIP Move to the next file in the archive. If the
archive is solid and RAR_OM_EXTRACT mode was set
when the archive was opened, the current file will
be processed - the operation will be performed
slower than a simple seek.
RAR_TEST Test the current file and move to the next file in
the archive. If the archive was opened with
RAR_OM_LIST mode, the operation is equal to
RAR_SKIP.
RAR_EXTRACT Extract the current file and move to the next file.
If the archive was opened with RAR_OM_LIST mode,
the operation is equal to RAR_SKIP.
DestPath
This parameter should point to a zero terminated string containing the
destination directory to which to extract files to. If DestPath is equal
to NULL, it means extract to the current directory. This parameter has
meaning only if DestName is NULL.
DestName
This parameter should point to a string containing the full path and name
to assign to extracted file or it can be NULL to use the default name.
If DestName is defined (not NULL), it overrides both the original file
name saved in the archive and path specigied in DestPath setting.
Both DestPath and DestName must be in OEM encoding. If necessary,
use CharToOem to convert text to OEM before passing to this function.
Return values
~~~~~~~~~~~~~
0 Success
ERAR_BAD_DATA File CRC error
ERAR_BAD_ARCHIVE Volume is not valid RAR archive
ERAR_UNKNOWN_FORMAT Unknown archive format
ERAR_EOPEN Volume open error
ERAR_ECREATE File create error
ERAR_ECLOSE File close error
ERAR_EREAD Read error
ERAR_EWRITE Write error
Note: if you wish to cancel extraction, return -1 when processing
UCM_PROCESSDATA callback message.
====================================================================
int PASCAL RARProcessFileW(HANDLE hArcData,
int Operation,
wchar_t *DestPath,
wchar_t *DestName)
====================================================================
Description
~~~~~~~~~~~
Unicode version of RARProcessFile. It uses Unicode DestPath
and DestName parameters, other parameters and return values
are the same as in RARProcessFile.
====================================================================
void PASCAL RARSetCallback(HANDLE hArcData,
int PASCAL (*CallbackProc)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2),
LPARAM UserData);
====================================================================
Description
~~~~~~~~~~~
Set a user-defined callback function to process Unrar events.
Parameters
~~~~~~~~~~
hArcData
This parameter should contain the archive handle obtained from the
RAROpenArchive function call.
CallbackProc
It should point to a user-defined callback function.
The function will be passed four parameters:
msg Type of event. Described below.
UserData User defined value passed to RARSetCallback.
P1 and P2 Event dependent parameters. Described below.
Possible events
UCM_CHANGEVOLUME Process volume change.
P1 Points to the zero terminated name
of the next volume.
P2 The function call mode:
RAR_VOL_ASK Required volume is absent. The function should
prompt user and return a positive value
to retry or return -1 value to terminate
operation. The function may also specify a new
volume name, placing it to the address specified
by P1 parameter.
RAR_VOL_NOTIFY Required volume is successfully opened.
This is a notification call and volume name
modification is not allowed. The function should
return a positive value to continue or -1
to terminate operation.
UCM_PROCESSDATA Process unpacked data. It may be used to read
a file while it is being extracted or tested
without actual extracting file to disk.
Return a positive value to continue process
or -1 to cancel the archive operation
P1 Address pointing to the unpacked data.
Function may refer to the data but must not
change it.
P2 Size of the unpacked data. It is guaranteed
only that the size will not exceed the maximum
dictionary size (4 Mb in RAR 3.0).
UCM_NEEDPASSWORD DLL needs a password to process archive.
This message must be processed if you wish
to be able to handle archives with encrypted
file names. It can be also used as replacement
of RARSetPassword function even for usual
encrypted files with non-encrypted names.
P1 Address pointing to the buffer for a password.
You need to copy a password here.
P2 Size of the password buffer.
UserData
User data passed to callback function.
Other functions of UnRAR.dll should not be called from the callback
function.
Return values
~~~~~~~~~~~~~
None
====================================================================
void PASCAL RARSetChangeVolProc(HANDLE hArcData,
int PASCAL (*ChangeVolProc)(char *ArcName,int Mode));
====================================================================
Obsoleted, use RARSetCallback instead.
====================================================================
void PASCAL RARSetProcessDataProc(HANDLE hArcData,
int PASCAL (*ProcessDataProc)(unsigned char *Addr,int Size))
====================================================================
Obsoleted, use RARSetCallback instead.
====================================================================
void PASCAL RARSetPassword(HANDLE hArcData,
char *Password);
====================================================================
Description
~~~~~~~~~~~
Set a password to decrypt files.
Parameters
~~~~~~~~~~
hArcData
This parameter should contain the archive handle obtained from the
RAROpenArchive function call.
Password
It should point to a string containing a zero terminated password.
Return values
~~~~~~~~~~~~~
None
====================================================================
void PASCAL RARGetDllVersion();
====================================================================
Description
~~~~~~~~~~~
Returns API version.
Parameters
~~~~~~~~~~
None.
Return values
~~~~~~~~~~~~~
Returns an integer value denoting UnRAR.dll API version, which is also
defined in unrar.h as RAR_DLL_VERSION. API version number is incremented
only in case of noticeable changes in UnRAR.dll API. Do not confuse it
with version of UnRAR.dll stored in DLL resources, which is incremented
with every DLL rebuild.
If RARGetDllVersion() returns a value lower than UnRAR.dll which your
application was designed for, it may indicate that DLL version is too old
and it will fail to provide all necessary functions to your application.
This function is absent in old versions of UnRAR.dll, so it is safer
to use LoadLibrary and GetProcAddress to access this function.

80
libs/unrar2/UnRARDLL/whatsnew.txt

@ -0,0 +1,80 @@
List of unrar.dll API changes. We do not include performance and reliability
improvements into this list, but this library and RAR/UnRAR tools share
the same source code. So the latest version of unrar.dll usually contains
same decompression algorithm changes as the latest UnRAR version.
============================================================================
-- 18 January 2008
all LONG parameters of CallbackProc function were changed
to LPARAM type for 64 bit mode compatibility.
-- 12 December 2007
Added new RAR_OM_LIST_INCSPLIT open mode for function RAROpenArchive.
-- 14 August 2007
Added NoCrypt\unrar_nocrypt.dll without decryption code for those
applications where presence of encryption or decryption code is not
allowed because of legal restrictions.
-- 14 December 2006
Added ERAR_MISSING_PASSWORD error type. This error is returned
if empty password is specified for encrypted file.
-- 12 June 2003
Added RARProcessFileW function, Unicode version of RARProcessFile
-- 9 August 2002
Added RAROpenArchiveEx function allowing to specify Unicode archive
name and get archive flags.
-- 24 January 2002
Added RARReadHeaderEx function allowing to read Unicode file names
and 64 bit file sizes.
-- 23 January 2002
Added ERAR_UNKNOWN error type (it is used for all errors which
do not have special ERAR code yet) and UCM_NEEDPASSWORD callback
message.
Unrar.dll automatically opens all next volumes not only when extracting,
but also in RAR_OM_LIST mode.
-- 27 November 2001
RARSetChangeVolProc and RARSetProcessDataProc are replaced by
the single callback function installed with RARSetCallback.
Unlike old style callbacks, the new function accepts the user defined
parameter. Unrar.dll still supports RARSetChangeVolProc and
RARSetProcessDataProc for compatibility purposes, but if you write
a new application, better use RARSetCallback.
File comments support is not implemented in the new DLL version yet.
Now CmtState is always 0.
-- 13 August 2001
Added RARGetDllVersion function, so you may distinguish old unrar.dll,
which used C style callback functions and the new one with PASCAL callbacks.
-- 10 May 2001
Callback functions in RARSetChangeVolProc and RARSetProcessDataProc
use PASCAL style call convention now.

1
libs/unrar2/UnRARDLL/x64/readme.txt

@ -0,0 +1 @@
This is x64 version of unrar.dll.

BIN
libs/unrar2/UnRARDLL/x64/unrar64.dll

Binary file not shown.

BIN
libs/unrar2/UnRARDLL/x64/unrar64.lib

Binary file not shown.

177
libs/unrar2/__init__.py

@ -0,0 +1,177 @@
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
#
# 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.
"""
pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
stable and foolproof.
Notice that it has INCOMPATIBLE interface.
It enables reading and unpacking of archives created with the
RAR/WinRAR archivers. There is a low-level interface which is very
similar to the C interface provided by UnRAR. There is also a
higher level interface which makes some common operations easier.
"""
__version__ = '0.99.2'
try:
WindowsError
in_windows = True
except NameError:
in_windows = False
if in_windows:
from windows import RarFileImplementation
else:
from unix import RarFileImplementation
import fnmatch, time, weakref
class RarInfo(object):
"""Represents a file header in an archive. Don't instantiate directly.
Use only to obtain information about file.
YOU CANNOT EXTRACT FILE CONTENTS USING THIS OBJECT.
USE METHODS OF RarFile CLASS INSTEAD.
Properties:
index - index of file within the archive
filename - name of the file in the archive including path (if any)
datetime - file date/time as a struct_time suitable for time.strftime
isdir - True if the file is a directory
size - size in bytes of the uncompressed file
comment - comment associated with the file
Note - this is not currently intended to be a Python file-like object.
"""
def __init__(self, rarfile, data):
self.rarfile = weakref.proxy(rarfile)
self.index = data['index']
self.filename = data['filename']
self.isdir = data['isdir']
self.size = data['size']
self.datetime = data['datetime']
self.comment = data['comment']
def __str__(self):
try :
arcName = self.rarfile.archiveName
except ReferenceError:
arcName = "[ARCHIVE_NO_LONGER_LOADED]"
return '<RarInfo "%s" in "%s">' % (self.filename, arcName)
class RarFile(RarFileImplementation):
def __init__(self, archiveName, password=None):
"""Instantiate the archive.
archiveName is the name of the RAR file.
password is used to decrypt the files in the archive.
Properties:
comment - comment associated with the archive
>>> print RarFile('test.rar').comment
This is a test.
"""
self.archiveName = archiveName
RarFileImplementation.init(self, password)
def __del__(self):
self.destruct()
def infoiter(self):
"""Iterate over all the files in the archive, generating RarInfos.
>>> import os
>>> for fileInArchive in RarFile('test.rar').infoiter():
... print os.path.split(fileInArchive.filename)[-1],
... print fileInArchive.isdir,
... print fileInArchive.size,
... print fileInArchive.comment,
... print tuple(fileInArchive.datetime)[0:5],
... print time.strftime('%a, %d %b %Y %H:%M', fileInArchive.datetime)
test True 0 None (2003, 6, 30, 1, 59) Mon, 30 Jun 2003 01:59
test.txt False 20 None (2003, 6, 30, 2, 1) Mon, 30 Jun 2003 02:01
this.py False 1030 None (2002, 2, 8, 16, 47) Fri, 08 Feb 2002 16:47
"""
for params in RarFileImplementation.infoiter(self):
yield RarInfo(self, params)
def infolist(self):
"""Return a list of RarInfos, descripting the contents of the archive."""
return list(self.infoiter())
def read_files(self, condition='*'):
"""Read specific files from archive into memory.
If "condition" is a list of numbers, then return files which have those positions in infolist.
If "condition" is a string, then it is treated as a wildcard for names of files to extract.
If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
and returns boolean True (extract) or False (skip).
If "condition" is omitted, all files are returned.
Returns list of tuples (RarInfo info, str contents)
"""
checker = condition2checker(condition)
return RarFileImplementation.read_files(self, checker)
def extract(self, condition='*', path='.', withSubpath=True, overwrite=True):
"""Extract specific files from archive to disk.
If "condition" is a list of numbers, then extract files which have those positions in infolist.
If "condition" is a string, then it is treated as a wildcard for names of files to extract.
If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
and returns either boolean True (extract) or boolean False (skip).
DEPRECATED: If "condition" callback returns string (only supported for Windows) -
that string will be used as a new name to save the file under.
If "condition" is omitted, all files are extracted.
"path" is a directory to extract to
"withSubpath" flag denotes whether files are extracted with their full path in the archive.
"overwrite" flag denotes whether extracted files will overwrite old ones. Defaults to true.
Returns list of RarInfos for extracted files."""
checker = condition2checker(condition)
return RarFileImplementation.extract(self, checker, path, withSubpath, overwrite)
def condition2checker(condition):
"""Converts different condition types to callback"""
if type(condition) in [str, unicode]:
def smatcher(info):
return fnmatch.fnmatch(info.filename, condition)
return smatcher
elif type(condition) in [list, tuple] and type(condition[0]) in [int, long]:
def imatcher(info):
return info.index in condition
return imatcher
elif callable(condition):
return condition
else:
raise TypeError

21
libs/unrar2/license.txt

@ -0,0 +1,21 @@
Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
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.

30
libs/unrar2/rar_exceptions.py

@ -0,0 +1,30 @@
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
#
# 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.
# Low level interface - see UnRARDLL\UNRARDLL.TXT
class ArchiveHeaderBroken(Exception): pass
class InvalidRARArchive(Exception): pass
class FileOpenError(Exception): pass
class IncorrectRARPassword(Exception): pass
class InvalidRARArchiveUsage(Exception): pass

175
libs/unrar2/unix.py

@ -0,0 +1,175 @@
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
#
# 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.
# Unix version uses unrar command line executable
import subprocess
import gc
import os, os.path
import time, re
from rar_exceptions import *
class UnpackerNotInstalled(Exception): pass
rar_executable_cached = None
def call_unrar(params):
"Calls rar/unrar command line executable, returns stdout pipe"
global rar_executable_cached
if rar_executable_cached is None:
for command in ('unrar', 'rar'):
try:
subprocess.Popen([command], stdout=subprocess.PIPE)
rar_executable_cached = command
break
except OSError:
pass
if rar_executable_cached is None:
raise UnpackerNotInstalled("No suitable RAR unpacker installed")
assert type(params) == list, "params must be list"
args = [rar_executable_cached] + params
try:
gc.disable() # See http://bugs.python.org/issue1336
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
finally:
gc.enable()
class RarFileImplementation(object):
def init(self, password=None):
self.password = password
stdoutdata, stderrdata = self.call('v', []).communicate()
for line in stderrdata.splitlines():
if line.strip().startswith("Cannot open"):
raise FileOpenError
if line.find("CRC failed")>=0:
raise IncorrectRARPassword
accum = []
source = iter(stdoutdata.splitlines())
line = ''
while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
line = source.next()
while not line.startswith('Pathname/Comment'):
accum.append(line.rstrip('\n'))
line = source.next()
if len(accum):
accum[0] = accum[0][9:]
self.comment = '\n'.join(accum[:-1])
else:
self.comment = None
def escaped_password(self):
return '-' if self.password == None else self.password
def call(self, cmd, options=[], files=[]):
options2 = options + ['p'+self.escaped_password()]
soptions = ['-'+x for x in options2]
return call_unrar([cmd]+soptions+['--',self.archiveName]+files)
def infoiter(self):
stdoutdata, stderrdata = self.call('v', ['c-']).communicate()
for line in stderrdata.splitlines():
if line.strip().startswith("Cannot open"):
raise FileOpenError
accum = []
source = iter(stdoutdata.splitlines())
line = ''
while not line.startswith('--------------'):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
if line.find("CRC failed")>=0:
raise IncorrectRARPassword
line = source.next()
line = source.next()
i = 0
re_spaces = re.compile(r"\s+")
while not line.startswith('--------------'):
accum.append(line)
if len(accum)==2:
data = {}
data['index'] = i
data['filename'] = accum[0].strip()
info = re_spaces.split(accum[1].strip())
data['size'] = int(info[0])
attr = info[5]
data['isdir'] = 'd' in attr.lower()
data['datetime'] = time.strptime(info[3]+" "+info[4], '%d-%m-%y %H:%M')
data['comment'] = None
yield data
accum = []
i += 1
line = source.next()
def read_files(self, checker):
res = []
for info in self.infoiter():
checkres = checker(info)
if checkres==True and not info.isdir:
pipe = self.call('p', ['inul'], [info.filename]).stdout
res.append((info, pipe.read()))
return res
def extract(self, checker, path, withSubpath, overwrite):
res = []
command = 'x'
if not withSubpath:
command = 'e'
options = []
if overwrite:
options.append('o+')
else:
options.append('o-')
if not path.endswith(os.sep):
path += os.sep
names = []
for info in self.infoiter():
checkres = checker(info)
if type(checkres) in [str, unicode]:
raise NotImplementedError("Condition callbacks returning strings are deprecated and only supported in Windows")
if checkres==True and not info.isdir:
names.append(info.filename)
res.append(info)
names.append(path)
proc = self.call(command, options, names)
stdoutdata, stderrdata = proc.communicate()
if stderrdata.find("CRC failed")>=0:
raise IncorrectRARPassword
return res
def destruct(self):
pass

309
libs/unrar2/windows.py

@ -0,0 +1,309 @@
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
#
# 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.
# Low level interface - see UnRARDLL\UNRARDLL.TXT
from __future__ import generators
import ctypes, ctypes.wintypes
import os, os.path, sys
import Queue
import time
from rar_exceptions import *
ERAR_END_ARCHIVE = 10
ERAR_NO_MEMORY = 11
ERAR_BAD_DATA = 12
ERAR_BAD_ARCHIVE = 13
ERAR_UNKNOWN_FORMAT = 14
ERAR_EOPEN = 15
ERAR_ECREATE = 16
ERAR_ECLOSE = 17
ERAR_EREAD = 18
ERAR_EWRITE = 19
ERAR_SMALL_BUF = 20
ERAR_UNKNOWN = 21
RAR_OM_LIST = 0
RAR_OM_EXTRACT = 1
RAR_SKIP = 0
RAR_TEST = 1
RAR_EXTRACT = 2
RAR_VOL_ASK = 0
RAR_VOL_NOTIFY = 1
RAR_DLL_VERSION = 3
# enum UNRARCALLBACK_MESSAGES
UCM_CHANGEVOLUME = 0
UCM_PROCESSDATA = 1
UCM_NEEDPASSWORD = 2
architecture_bits = ctypes.sizeof(ctypes.c_voidp)*8
dll_name = "unrar.dll"
if architecture_bits == 64:
dll_name = "x64\\unrar64.dll"
try:
unrar = ctypes.WinDLL(os.path.join(os.path.split(__file__)[0], 'UnRARDLL', dll_name))
except WindowsError:
unrar = ctypes.WinDLL(dll_name)
class RAROpenArchiveDataEx(ctypes.Structure):
def __init__(self, ArcName=None, ArcNameW=u'', OpenMode=RAR_OM_LIST):
self.CmtBuf = ctypes.c_buffer(64*1024)
ctypes.Structure.__init__(self, ArcName=ArcName, ArcNameW=ArcNameW, OpenMode=OpenMode, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf))
_fields_ = [
('ArcName', ctypes.c_char_p),
('ArcNameW', ctypes.c_wchar_p),
('OpenMode', ctypes.c_uint),
('OpenResult', ctypes.c_uint),
('_CmtBuf', ctypes.c_voidp),
('CmtBufSize', ctypes.c_uint),
('CmtSize', ctypes.c_uint),
('CmtState', ctypes.c_uint),
('Flags', ctypes.c_uint),
('Reserved', ctypes.c_uint*32),
]
class RARHeaderDataEx(ctypes.Structure):
def __init__(self):
self.CmtBuf = ctypes.c_buffer(64*1024)
ctypes.Structure.__init__(self, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf))
_fields_ = [
('ArcName', ctypes.c_char*1024),
('ArcNameW', ctypes.c_wchar*1024),
('FileName', ctypes.c_char*1024),
('FileNameW', ctypes.c_wchar*1024),
('Flags', ctypes.c_uint),
('PackSize', ctypes.c_uint),
('PackSizeHigh', ctypes.c_uint),
('UnpSize', ctypes.c_uint),
('UnpSizeHigh', ctypes.c_uint),
('HostOS', ctypes.c_uint),
('FileCRC', ctypes.c_uint),
('FileTime', ctypes.c_uint),
('UnpVer', ctypes.c_uint),
('Method', ctypes.c_uint),
('FileAttr', ctypes.c_uint),
('_CmtBuf', ctypes.c_voidp),
('CmtBufSize', ctypes.c_uint),
('CmtSize', ctypes.c_uint),
('CmtState', ctypes.c_uint),
('Reserved', ctypes.c_uint*1024),
]
def DosDateTimeToTimeTuple(dosDateTime):
"""Convert an MS-DOS format date time to a Python time tuple.
"""
dosDate = dosDateTime >> 16
dosTime = dosDateTime & 0xffff
day = dosDate & 0x1f
month = (dosDate >> 5) & 0xf
year = 1980 + (dosDate >> 9)
second = 2*(dosTime & 0x1f)
minute = (dosTime >> 5) & 0x3f
hour = dosTime >> 11
return time.localtime(time.mktime((year, month, day, hour, minute, second, 0, 1, -1)))
def _wrap(restype, function, argtypes):
result = function
result.argtypes = argtypes
result.restype = restype
return result
RARGetDllVersion = _wrap(ctypes.c_int, unrar.RARGetDllVersion, [])
RAROpenArchiveEx = _wrap(ctypes.wintypes.HANDLE, unrar.RAROpenArchiveEx, [ctypes.POINTER(RAROpenArchiveDataEx)])
RARReadHeaderEx = _wrap(ctypes.c_int, unrar.RARReadHeaderEx, [ctypes.wintypes.HANDLE, ctypes.POINTER(RARHeaderDataEx)])
_RARSetPassword = _wrap(ctypes.c_int, unrar.RARSetPassword, [ctypes.wintypes.HANDLE, ctypes.c_char_p])
def RARSetPassword(*args, **kwargs):
_RARSetPassword(*args, **kwargs)
RARProcessFile = _wrap(ctypes.c_int, unrar.RARProcessFile, [ctypes.wintypes.HANDLE, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p])
RARCloseArchive = _wrap(ctypes.c_int, unrar.RARCloseArchive, [ctypes.wintypes.HANDLE])
UNRARCALLBACK = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint, ctypes.c_long, ctypes.c_long, ctypes.c_long)
RARSetCallback = _wrap(ctypes.c_int, unrar.RARSetCallback, [ctypes.wintypes.HANDLE, UNRARCALLBACK, ctypes.c_long])
RARExceptions = {
ERAR_NO_MEMORY : MemoryError,
ERAR_BAD_DATA : ArchiveHeaderBroken,
ERAR_BAD_ARCHIVE : InvalidRARArchive,
ERAR_EOPEN : FileOpenError,
}
class PassiveReader:
"""Used for reading files to memory"""
def __init__(self, usercallback = None):
self.buf = []
self.ucb = usercallback
def _callback(self, msg, UserData, P1, P2):
if msg == UCM_PROCESSDATA:
data = (ctypes.c_char*P2).from_address(P1).raw
if self.ucb!=None:
self.ucb(data)
else:
self.buf.append(data)
return 1
def get_result(self):
return ''.join(self.buf)
class RarInfoIterator(object):
def __init__(self, arc):
self.arc = arc
self.index = 0
self.headerData = RARHeaderDataEx()
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
if self.res==ERAR_BAD_DATA:
raise IncorrectRARPassword
self.arc.lockStatus = "locked"
self.arc.needskip = False
def __iter__(self):
return self
def next(self):
if self.index>0:
if self.arc.needskip:
RARProcessFile(self.arc._handle, RAR_SKIP, None, None)
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
if self.res:
raise StopIteration
self.arc.needskip = True
data = {}
data['index'] = self.index
data['filename'] = self.headerData.FileName
data['datetime'] = DosDateTimeToTimeTuple(self.headerData.FileTime)
data['isdir'] = ((self.headerData.Flags & 0xE0) == 0xE0)
data['size'] = self.headerData.UnpSize + (self.headerData.UnpSizeHigh << 32)
if self.headerData.CmtState == 1:
data['comment'] = self.headerData.CmtBuf.value
else:
data['comment'] = None
self.index += 1
return data
def __del__(self):
self.arc.lockStatus = "finished"
def generate_password_provider(password):
def password_provider_callback(msg, UserData, P1, P2):
if msg == UCM_NEEDPASSWORD and password!=None:
(ctypes.c_char*P2).from_address(P1).value = password
return 1
return password_provider_callback
class RarFileImplementation(object):
def init(self, password=None):
self.password = password
archiveData = RAROpenArchiveDataEx(ArcNameW=self.archiveName, OpenMode=RAR_OM_EXTRACT)
self._handle = RAROpenArchiveEx(ctypes.byref(archiveData))
self.c_callback = UNRARCALLBACK(generate_password_provider(self.password))
RARSetCallback(self._handle, self.c_callback, 1)
if archiveData.OpenResult != 0:
raise RARExceptions[archiveData.OpenResult]
if archiveData.CmtState == 1:
self.comment = archiveData.CmtBuf.value
else:
self.comment = None
if password:
RARSetPassword(self._handle, password)
self.lockStatus = "ready"
def destruct(self):
if self._handle and RARCloseArchive:
RARCloseArchive(self._handle)
def make_sure_ready(self):
if self.lockStatus == "locked":
raise InvalidRARArchiveUsage("cannot execute infoiter() without finishing previous one")
if self.lockStatus == "finished":
self.destruct()
self.init(self.password)
def infoiter(self):
self.make_sure_ready()
return RarInfoIterator(self)
def read_files(self, checker):
res = []
for info in self.infoiter():
if checker(info) and not info.isdir:
reader = PassiveReader()
c_callback = UNRARCALLBACK(reader._callback)
RARSetCallback(self._handle, c_callback, 1)
tmpres = RARProcessFile(self._handle, RAR_TEST, None, None)
if tmpres==ERAR_BAD_DATA:
raise IncorrectRARPassword
self.needskip = False
res.append((info, reader.get_result()))
return res
def extract(self, checker, path, withSubpath, overwrite):
res = []
for info in self.infoiter():
checkres = checker(info)
if checkres!=False and not info.isdir:
if checkres==True:
fn = info.filename
if not withSubpath:
fn = os.path.split(fn)[-1]
target = os.path.join(path, fn)
else:
raise DeprecationWarning, "Condition callbacks returning strings are deprecated and only supported in Windows"
target = checkres
if overwrite or (not os.path.exists(target)):
tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target)
if tmpres==ERAR_BAD_DATA:
raise IncorrectRARPassword
self.needskip = False
res.append(info)
return res
Loading…
Cancel
Save