#! /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/), module 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 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.0.0" __revision__ = "$Id$" __date__ = "$Date$" __author__ = "Oleg Broytman " __copyright__ = "Copyright (C) 2010, 2011 PhiloSoft Design" __license__ = "GPL" import locale, sys, os from eff_bdecode import decode import 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 mctorrent_list(): """List the entire VFS""" if 'info' not in torrent: torrent_error('Info absent') info = torrent['info'] if 'name' not in info: torrent_error('Unknown name') name = info['name'] encoding = torrent.get('encoding', None) if 'files' in info: files = info['files'] paths = [] for file in files: if 'path' not in file: torrent_error('Unknown path') if 'length' not in file: torrent_error('Unknown length') 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 charset and encoding and (charset != encoding): name = name.decode(encoding, 'replace').encode(charset, 'replace') paths = [(name, length)] meta = [] for name in 'comment', 'encoding', 'creation date', 'announce-list', \ 'created by', 'announce': if name in torrent: if name == 'creation date': data = decode_datetime(torrent[name]) elif name == 'announce-list': data = decode_list(torrent[name]) 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 'comment', 'encoding', 'creation date', 'announce-list', \ 'created by', 'announce': if torrent_filename == '.META/' + name: if name in torrent: if name == 'creation date': data = decode_datetime(torrent[name]) elif name == 'announce-list': data = decode_list(torrent[name]) 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 decode(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_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")