Ticket #3100: Embedded torrent viewer/vfs

Currently ext.d viewer for torrent is partially broken since ctorrent
is used and it broken in most distributions (http://sourceforge.net/p/dtorrent/bugs/21/).

This patch adds viewer and vfs support based on script found here: http://phdru.name/Software/mc/torrent

Signed-off-by: Slava Zanko <slavazanko@gmail.com>
This commit is contained in:
Slava Zanko 2013-11-08 14:58:48 +03:00
parent 91027154a2
commit 10401bb70e
4 changed files with 305 additions and 2 deletions

View File

@ -45,7 +45,7 @@ do_view_action() {
lyxcat "${MC_EXT_FILENAME}"
;;
torrent)
ctorrent -x "${MC_EXT_FILENAME}" 2>/dev/null
@EXTHELPERSDIR@/../extfs.d/torrent view "${MC_EXT_FILENAME}" 2>/dev/null
;;
javaclass)
jad -p "${MC_EXT_FILENAME}" 2>/dev/null

View File

@ -663,6 +663,7 @@ shell/i/.lyx
# torrent
shell/i/.torrent
Open=%cd %p/torrent://
View=%view{ascii} @EXTHELPERSDIR@/misc.sh view torrent
### Plain compressed files ###

View File

@ -6,7 +6,7 @@ EXTFSCONFFILES = sfs.ini
EXTFS_MISC = README README.extfs
# Scripts hat don't need adaptation to the local system
EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm u7z
EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm torrent u7z
# Scripts that need adaptation to the local system - source files
EXTFS_IN = \

View File

@ -0,0 +1,302 @@
#! /usr/bin/env python
"""Torrent Virtual FileSystem for Midnight Commander
The script requires:
Midnight Commander 3.1+ (http://www.midnight-commander.org/),
Python 2.4+ (http://www.python.org/).
Includes: eff_bdecode.py (http://effbot.org/zone/bencode.htm).
For mc 4.7+ put the script in $HOME/.mc/extfs.d.
For older versions put it in /usr/[local/][lib|share]/mc/extfs
and add a line "torrent" to the /usr/[local/][lib|share]/mc/extfs/extfs.ini.
Make the script executable.
Run this "cd" command in the Midnight Commander (in the "bindings" file the
command is "%cd"): cd file#torrent, where "file" is the name of your torrent
metafile. The VFS lists all files and directories from the torrent metafile;
all files appear empty, of course, but the sizes are shown. Filenames are
reencoded from the metafile's encoding/codepage to the current locale.
Along with the files/directories in the torrent metafile the VFS also presents
meta information - in the form of files in .META directory. The size and
contents of these files are taken from the corresponding fields in the torrent
metafile. The script doesn't check if the torrent consists of a .META file or
directory (quite unlikely).
Date/time for all files is set to midnight of the 1st January of the current
year. The filesystem is, naturally, read-only.
"""
__version__ = "1.1.1"
__author__ = "Oleg Broytman <phd@phdru.name>"
__copyright__ = "Copyright (C) 2010-2013 PhiloSoft Design"
__license__ = "GPL"
import locale, sys, os, re, logging
logger = logging.getLogger('torrent-mcextfs')
log_err_handler = logging.StreamHandler(sys.stderr)
logger.addHandler(log_err_handler)
logger.setLevel(logging.INFO)
if len(sys.argv) < 3:
logger.critical("""\
Torrent Virtual FileSystem for Midnight Commander version %s
Author: %s
%s
This is not a program. Put the script in /usr/[local/][lib|share]/mc/extfs.
For more information read the source!""",
__version__, __author__, __copyright__
)
sys.exit(1)
locale.setlocale(locale.LC_ALL, '')
charset = locale.getpreferredencoding()
def effbtokenize(text, match=re.compile("([idel])|(\d+):|(-?\d+)").match):
i = 0
while i < len(text):
m = match(text, i)
s = m.group(m.lastindex)
i = m.end()
if m.lastindex == 2:
yield "s"
yield text[i:i+int(s)]
i = i + int(s)
else:
yield s
def effbdecode_item(next, token):
if token == "i":
# integer: "i" value "e"
data = int(next())
if next() != "e":
raise ValueError
elif token == "s":
# string: "s" value (virtual tokens)
data = next()
elif token == "l" or token == "d":
# container: "l" (or "d") values "e"
data = []
tok = next()
while tok != "e":
data.append(effbdecode_item(next, tok))
tok = next()
if token == "d":
data = dict(zip(data[0::2], data[1::2]))
else:
raise ValueError
return data
def effbdecode(text):
try:
src = effbtokenize(text)
data = effbdecode_item(src.next, src.next())
for token in src: # look for more tokens
raise SyntaxError("trailing junk")
except (AttributeError, ValueError, StopIteration):
raise SyntaxError("syntax error")
return data
def mctorrent_list():
"""List the entire VFS"""
if 'info' not in torrent:
torrent_error('Info absent')
info = torrent['info']
if 'name' not in info and 'name.utf-8' not in info:
torrent_error('Unknown name')
codepage = torrent.get('codepage', None)
encoding = torrent.get('encoding', None)
if not encoding and codepage:
encoding = str(codepage)
name = info['name']
name_utf8 = info.get('name.utf-8', None)
if 'files' in info:
files = info['files']
paths = []
for file in files:
if 'path' not in file and 'path.utf-8' not in file:
torrent_error('Unknown path')
if 'length' not in file:
torrent_error('Unknown length')
if 'path.utf-8' in file:
if name_utf8:
path = '/'.join([name_utf8] + file['path.utf-8'])
if charset and (charset != 'utf-8'):
path = path.decode('utf-8', 'replace').encode(charset, 'replace')
else:
_name_utf8 = name
if encoding and (encoding != 'utf-8'):
_name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
path = '/'.join([_name_utf8] + file['path.utf-8'])
if charset and (charset != 'utf-8'):
path = path.decode('utf-8', 'replace').encode(charset, 'replace')
else:
if name_utf8:
path = file['path']
if encoding and (encoding != 'utf-8'):
path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
path = '/'.join([name_utf8] + path)
if charset and (charset != 'utf-8'):
path = path.decode('utf-8', 'replace').encode(charset, 'replace')
else:
path = '/'.join([name] + file['path'])
if charset and encoding and (charset != encoding):
path = path.decode(encoding, 'replace').encode(charset, 'replace')
length = file['length']
paths.append((path, length))
else: # One-file torrent
if 'length' not in info:
torrent_error('Unknown length')
length = info['length']
if name_utf8:
if charset and (charset != 'utf-8'):
name = name_utf8.decode('utf-8', 'replace').encode(charset, 'replace')
elif charset and encoding and (charset != encoding):
name = name.decode(encoding, 'replace').encode(charset, 'replace')
paths = [(name, length)]
meta = []
for name in 'announce', 'announce-list', 'codepage', 'comment', \
'created by', 'creation date', 'encoding', \
'nodes', 'publisher', 'publisher-url':
if name == 'comment' and 'comment.utf-8' in torrent:
data = torrent['comment.utf-8'].decode('utf-8').encode(charset, 'replace')
meta.append(('.META/' + name, len(data)))
elif name in torrent:
if name == 'announce-list':
data = decode_announce_list(torrent[name])
elif name == 'codepage':
data = str(torrent[name])
elif name == 'creation date':
data = decode_datetime(torrent[name])
elif name == 'nodes':
data = ['%s:%s' % (host, port) for host, port in torrent[name]]
data = '\n'.join(data)
else:
data = torrent[name]
meta.append(('.META/' + name, len(data)))
if 'private' in info:
meta.append(('.META/private', 1))
if 'piece length' in info:
meta.append(('.META/piece length', len(str(info['piece length']))))
for name, size in paths + meta:
print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
def mctorrent_copyout():
"""Extract a file from the VFS"""
torrent_filename = sys.argv[3]
real_filename = sys.argv[4]
data = None
for name in 'announce', 'announce-list', 'codepage', 'comment', \
'created by', 'creation date', 'encoding', \
'nodes', 'publisher', 'publisher-url':
if name == 'comment' and 'comment.utf-8' in torrent:
data = torrent['comment.utf-8'].decode('utf-8').encode(charset, 'replace')
meta.append(('.META/' + name, len(data)))
elif torrent_filename == '.META/' + name:
if name in torrent:
if name == 'announce-list':
data = decode_announce_list(torrent[name])
elif name == 'codepage':
data = str(torrent[name])
elif name == 'creation date':
data = decode_datetime(torrent[name])
elif name == 'nodes':
data = ['%s:%s' % (host, port) for host, port in torrent[name]]
data = '\n'.join(data)
else:
data = str(torrent[name])
else:
torrent_error('Unknown ' + name)
break
if torrent_filename in ('.META/private', '.META/piece length'):
if 'info' not in torrent:
torrent_error('Info absent')
info = torrent['info']
if torrent_filename == '.META/private':
if 'private' not in info:
torrent_error('Info absent')
if torrent_filename == '.META/piece length':
if 'piece length' not in info:
torrent_error('Info absent')
data = str(info[torrent_filename[len('.META/'):]])
if not torrent_filename.startswith('.META/'):
data = ''
if data is None:
torrent_error('Unknown file name')
else:
outfile = open(real_filename, 'w')
outfile.write(data)
outfile.close()
def mctorrent_copyin():
"""Put a file to the VFS"""
sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
def mctorrent_rm():
"""Remove a file from the VFS"""
sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
mctorrent_rmdir = mctorrent_rm
def mctorrent_mkdir():
"""Create a directory in the VFS"""
sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
def torrent_error(error_str):
logger.critical("Error parsing the torrent metafile: %s", error_str)
sys.exit(1)
def decode_torrent():
try:
torrent_file = open(sys.argv[2], 'r')
data = torrent_file.read()
torrent_file.close()
return effbdecode(data)
except IOError, error_str:
torrent_error(error_str)
def decode_datetime(dt):
from time import localtime, asctime
the_time = float(dt)
l_now = localtime(the_time)
return asctime(l_now)
def decode_announce_list(announce):
return '\n'.join(l[0] for l in announce)
command = sys.argv[1]
procname = "mctorrent_" + command
g = globals()
if not g.has_key(procname):
logger.critical("Unknown command %s", command)
sys.exit(1)
torrent = decode_torrent()
try:
g[procname]()
except SystemExit:
raise
except:
logger.exception("Error during run")