2 """Torrent Virtual FileSystem for Midnight Commander
4 The script requires Midnight Commander 3.1+
5 (http://www.midnight-commander.org/), Python 2.4+ (http://www.python.org/),
6 module eff_bdecode.py (http://effbot.org/zone/bencode.htm).
8 For mc 4.7+ put the script in $HOME/.mc/extfs.d.
9 For older versions put it in /usr/[local/][lib|share]/mc/extfs
10 and add a line "torrent" to the /usr/[local/][lib|share]/mc/extfs/extfs.ini.
11 Make the script executable.
13 Run this "cd" command in the Midnight Commander (in the "bindings" file the
14 command is "%cd"): cd file#torrent, where "file" is the name of your torrent
15 metafile. The VFS lists all files and directories from the torrent metafile;
16 all files appear empty, of course, but the sizes are shown. Filenames are
17 reencoded from the metafile's encoding/codepage to the current locale.
19 Along with the files/directories in the torrent metafile the VFS also presents
20 meta information - in the form of files in .META directory. The size and
21 contents of these files are taken from the corresponding fields in the torrent
22 metafile. The script doesn't check if the torrent consists of a .META file or
23 directory (quite unlikely).
25 Date/time for all files is set to midnight of the 1st January of the current
26 year. The filesystem is, naturally, read-only.
31 __author__ = "Oleg Broytman <phd@phdru.name>"
32 __copyright__ = "Copyright (C) 2010-2013 PhiloSoft Design"
35 import locale, sys, os
36 from eff_bdecode import decode
39 logger = logging.getLogger('torrent-mcextfs')
40 log_err_handler = logging.StreamHandler(sys.stderr)
41 logger.addHandler(log_err_handler)
42 logger.setLevel(logging.INFO)
46 Torrent Virtual FileSystem for Midnight Commander version %s
50 This is not a program. Put the script in /usr/[local/][lib|share]/mc/extfs.
51 For more information read the source!""",
52 __version__, __author__, __copyright__
57 locale.setlocale(locale.LC_ALL, '')
58 charset = locale.getpreferredencoding()
62 """List the entire VFS"""
64 if 'info' not in torrent:
65 torrent_error('Info absent')
67 info = torrent['info']
68 if 'name' not in info:
69 torrent_error('Unknown name')
71 codepage = torrent.get('codepage', None)
72 encoding = torrent.get('encoding', None)
73 if not encoding and codepage:
74 encoding = str(codepage)
82 if 'path' not in file:
83 torrent_error('Unknown path')
84 if 'length' not in file:
85 torrent_error('Unknown length')
86 path = '/'.join([name] + file['path'])
87 if charset and encoding and (charset != encoding):
88 path = path.decode(encoding, 'replace').encode(charset, 'replace')
89 length = file['length']
90 paths.append((path, length))
91 else: # One-file torrent
92 if 'length' not in info:
93 torrent_error('Unknown length')
94 length = info['length']
95 if charset and encoding and (charset != encoding):
96 name = name.decode(encoding, 'replace').encode(charset, 'replace')
97 paths = [(name, length)]
100 for name in 'announce', 'announce-list', 'codepage', 'comment', \
101 'created by', 'creation date', 'encoding':
103 if name == 'announce-list':
104 data = decode_list(torrent[name])
105 elif name == 'codepage':
106 data = str(torrent[name])
107 elif name == 'creation date':
108 data = decode_datetime(torrent[name])
111 meta.append(('.META/' + name, len(data)))
113 if 'private' in info:
114 meta.append(('.META/private', 1))
116 if 'piece length' in info:
117 meta.append(('.META/piece length', len(str(info['piece length']))))
119 for name, size in paths + meta:
120 print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
123 def mctorrent_copyout():
124 """Extract a file from the VFS"""
126 torrent_filename = sys.argv[3]
127 real_filename = sys.argv[4]
130 for name in 'announce', 'announce-list', 'codepage', 'comment', \
131 'created by', 'creation date', 'encoding':
132 if torrent_filename == '.META/' + name:
134 if name == 'announce-list':
135 data = decode_list(torrent[name])
136 elif name == 'codepage':
137 data = str(torrent[name])
138 elif name == 'creation date':
139 data = decode_datetime(torrent[name])
141 data = str(torrent[name])
143 torrent_error('Unknown ' + name)
146 if torrent_filename in ('.META/private', '.META/piece length'):
147 if 'info' not in torrent:
148 torrent_error('Info absent')
149 info = torrent['info']
150 if torrent_filename == '.META/private':
151 if 'private' not in info:
152 torrent_error('Info absent')
153 if torrent_filename == '.META/piece length':
154 if 'piece length' not in info:
155 torrent_error('Info absent')
156 data = str(info[torrent_filename[len('.META/'):]])
158 if not torrent_filename.startswith('.META/'):
162 torrent_error('Unknown file name')
164 outfile = open(real_filename, 'w')
169 def mctorrent_copyin():
170 """Put a file to the VFS"""
171 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
174 """Remove a file from the VFS"""
175 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
177 mctorrent_rmdir = mctorrent_rm
179 def mctorrent_mkdir():
180 """Create a directory in the VFS"""
181 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
184 def torrent_error(error_str):
185 logger.critical("Error parsing the torrent metafile: %s", error_str)
188 def decode_torrent():
190 torrent_file = open(sys.argv[2], 'r')
191 data = torrent_file.read()
194 except IOError, error_str:
195 torrent_error(error_str)
198 def decode_datetime(dt):
199 from time import localtime, asctime
201 l_now = localtime(the_time)
202 return asctime(l_now)
204 def decode_list(announce):
205 return '\n'.join(l[0] for l in announce)
208 command = sys.argv[1]
209 procname = "mctorrent_" + command
212 if not g.has_key(procname):
213 logger.critical("Unknown command %s", command)
216 torrent = decode_torrent()
223 logger.exception("Error during run")