mirror of https://github.com/MidnightCommander/mc
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:
parent
91027154a2
commit
10401bb70e
|
@ -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
|
||||
|
|
|
@ -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 ###
|
||||
|
|
|
@ -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 = \
|
||||
|
|
|
@ -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")
|
Loading…
Reference in New Issue