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 $HOME/.mc/extfs.d or
51 /usr/[local/][lib|share]/mc/extfs. 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 and 'name.utf-8' 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)
77 name_utf8 = info.get('name.utf-8', None)
83 if 'path' not in file and 'path.utf-8' not in file:
84 torrent_error('Unknown path')
85 if 'length' not in file:
86 torrent_error('Unknown length')
87 if 'path.utf-8' in file:
89 path = '/'.join([name_utf8] + file['path.utf-8'])
90 if charset and (charset != 'utf-8'):
91 path = path.decode('utf-8', 'replace').encode(charset, 'replace')
94 if encoding and (encoding != 'utf-8'):
95 _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
96 path = '/'.join([_name_utf8] + file['path.utf-8'])
97 if charset and (charset != 'utf-8'):
98 path = path.decode('utf-8', 'replace').encode(charset, 'replace')
102 if encoding and (encoding != 'utf-8'):
103 path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
104 path = '/'.join([name_utf8] + path)
105 if charset and (charset != 'utf-8'):
106 path = path.decode('utf-8', 'replace').encode(charset, 'replace')
108 path = '/'.join([name] + file['path'])
109 if charset and encoding and (charset != encoding):
110 path = path.decode(encoding, 'replace').encode(charset, 'replace')
111 length = file['length']
112 paths.append((path, length))
113 else: # One-file torrent
114 if 'length' not in info:
115 torrent_error('Unknown length')
116 length = info['length']
118 if charset and (charset != 'utf-8'):
119 name = name_utf8.decode('utf-8', 'replace').encode(charset, 'replace')
120 elif charset and encoding and (charset != encoding):
121 name = name.decode(encoding, 'replace').encode(charset, 'replace')
122 paths = [(name, length)]
125 for name in 'announce', 'announce-list', 'codepage', 'comment', \
126 'created by', 'creation date', 'encoding', \
127 'nodes', 'publisher', 'publisher-url':
128 if name == 'comment' and 'comment.utf-8' in torrent:
129 data = torrent['comment.utf-8'].decode('utf-8').encode(charset, 'replace')
130 meta.append(('.META/' + name, len(data)))
131 elif name in torrent:
132 if name == 'announce-list':
133 data = decode_announce_list(torrent[name])
134 elif name == 'codepage':
135 data = str(torrent[name])
136 elif name == 'creation date':
137 data = decode_datetime(torrent[name])
138 elif name == 'nodes':
139 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
140 data = '\n'.join(data)
143 meta.append(('.META/' + name, len(data)))
145 if 'private' in info:
146 meta.append(('.META/private', 1))
148 if 'piece length' in info:
149 meta.append(('.META/piece length', len(str(info['piece length']))))
151 for name, size in paths + meta:
152 print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
155 def mctorrent_copyout():
156 """Extract a file from the VFS"""
158 torrent_filename = sys.argv[3]
159 real_filename = sys.argv[4]
162 for name in 'announce', 'announce-list', 'codepage', 'comment', \
163 'created by', 'creation date', 'encoding', \
164 'nodes', 'publisher', 'publisher-url':
165 if name == 'comment' and 'comment.utf-8' in torrent:
166 data = torrent['comment.utf-8'].decode('utf-8').encode(charset, 'replace')
167 meta.append(('.META/' + name, len(data)))
168 elif torrent_filename == '.META/' + name:
170 if name == 'announce-list':
171 data = decode_announce_list(torrent[name])
172 elif name == 'codepage':
173 data = str(torrent[name])
174 elif name == 'creation date':
175 data = decode_datetime(torrent[name])
176 elif name == 'nodes':
177 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
178 data = '\n'.join(data)
180 data = str(torrent[name])
182 torrent_error('Unknown ' + name)
185 if torrent_filename in ('.META/private', '.META/piece length'):
186 if 'info' not in torrent:
187 torrent_error('Info absent')
188 info = torrent['info']
189 if torrent_filename == '.META/private':
190 if 'private' not in info:
191 torrent_error('Info absent')
192 if torrent_filename == '.META/piece length':
193 if 'piece length' not in info:
194 torrent_error('Info absent')
195 data = str(info[torrent_filename[len('.META/'):]])
197 if not torrent_filename.startswith('.META/'):
201 torrent_error('Unknown file name')
203 outfile = open(real_filename, 'w')
208 def mctorrent_copyin():
209 """Put a file to the VFS"""
210 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
213 """Remove a file from the VFS"""
214 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
216 mctorrent_rmdir = mctorrent_rm
218 def mctorrent_mkdir():
219 """Create a directory in the VFS"""
220 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
223 def torrent_error(error_str):
224 logger.critical("Error parsing the torrent metafile: %s", error_str)
227 def decode_torrent():
229 torrent_file = open(sys.argv[2], 'r')
230 data = torrent_file.read()
233 except IOError, error_str:
234 torrent_error(error_str)
237 def decode_datetime(dt):
238 from time import localtime, asctime
240 l_now = localtime(the_time)
241 return asctime(l_now)
243 def decode_announce_list(announce):
244 return '\n'.join(l[0] for l in announce)
247 command = sys.argv[1]
248 procname = "mctorrent_" + command
251 if not g.has_key(procname):
252 logger.critical("Unknown command %s", command)
255 torrent = decode_torrent()
262 logger.exception("Error during run")