mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-22 04:22:34 +03:00
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}"
|
lyxcat "${MC_EXT_FILENAME}"
|
||||||
;;
|
;;
|
||||||
torrent)
|
torrent)
|
||||||
ctorrent -x "${MC_EXT_FILENAME}" 2>/dev/null
|
@EXTHELPERSDIR@/../extfs.d/torrent view "${MC_EXT_FILENAME}" 2>/dev/null
|
||||||
;;
|
;;
|
||||||
javaclass)
|
javaclass)
|
||||||
jad -p "${MC_EXT_FILENAME}" 2>/dev/null
|
jad -p "${MC_EXT_FILENAME}" 2>/dev/null
|
||||||
|
@ -663,6 +663,7 @@ shell/i/.lyx
|
|||||||
|
|
||||||
# torrent
|
# torrent
|
||||||
shell/i/.torrent
|
shell/i/.torrent
|
||||||
|
Open=%cd %p/torrent://
|
||||||
View=%view{ascii} @EXTHELPERSDIR@/misc.sh view torrent
|
View=%view{ascii} @EXTHELPERSDIR@/misc.sh view torrent
|
||||||
|
|
||||||
### Plain compressed files ###
|
### Plain compressed files ###
|
||||||
|
@ -6,7 +6,7 @@ EXTFSCONFFILES = sfs.ini
|
|||||||
EXTFS_MISC = README README.extfs
|
EXTFS_MISC = README README.extfs
|
||||||
|
|
||||||
# Scripts hat don't need adaptation to the local system
|
# 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
|
# Scripts that need adaptation to the local system - source files
|
||||||
EXTFS_IN = \
|
EXTFS_IN = \
|
||||||
|
302
src/vfs/extfs/helpers/torrent
Normal file
302
src/vfs/extfs/helpers/torrent
Normal 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")
|
Loading…
Reference in New Issue
Block a user