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"
37 from eff_bdecode import decode
46 # Get the default charset.
48 lcAll = locale.getdefaultlocale()
49 except locale.Error, err:
50 print >>sys.stderr, "WARNING:", err
54 default_encoding = lcAll[1]
57 default_encoding = locale.getpreferredencoding()
58 except locale.Error, err:
59 print >>sys.stderr, "WARNING:", err
60 default_encoding = sys.getdefaultencoding()
62 default_encoding = sys.getdefaultencoding()
65 logger = logging.getLogger('torrent-mcextfs')
66 log_err_handler = logging.StreamHandler(sys.stderr)
67 logger.addHandler(log_err_handler)
68 logger.setLevel(logging.INFO)
72 Torrent Virtual FileSystem for Midnight Commander version %s
76 This is not a program. Put the script in $HOME/.mc/extfs.d or
77 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
78 __version__, __author__, __copyright__
82 locale.setlocale(locale.LC_ALL, '')
86 """List the entire VFS"""
88 if 'info' not in torrent:
89 torrent_error('Info absent')
91 info = torrent['info']
92 if 'name' not in info and 'name.utf-8' not in info:
93 torrent_error('Unknown name')
95 codepage = torrent.get('codepage', None)
96 encoding = torrent.get('encoding', None)
97 if not encoding and codepage:
98 encoding = str(codepage)
101 name_utf8 = info.get('name.utf-8', None)
104 files = info['files']
107 if 'path' not in file and 'path.utf-8' not in file:
108 torrent_error('Unknown path')
109 if 'length' not in file:
110 torrent_error('Unknown length')
111 if 'path.utf-8' in file:
113 path = '/'.join([name_utf8] + file['path.utf-8'])
114 if default_encoding != 'utf-8':
115 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
118 if encoding and (encoding != 'utf-8'):
119 _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
120 path = '/'.join([_name_utf8] + file['path.utf-8'])
121 if default_encoding != 'utf-8':
122 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
126 if encoding and (encoding != 'utf-8'):
127 path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
128 path = '/'.join([name_utf8] + path)
129 if default_encoding != 'utf-8':
130 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
132 path = '/'.join([name] + file['path'])
133 if encoding and (default_encoding != encoding):
134 path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
135 length = file['length']
136 paths.append((path, length))
137 else: # One-file torrent
138 if 'length' not in info:
139 torrent_error('Unknown length')
140 length = info['length']
142 if default_encoding != 'utf-8':
143 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
144 elif encoding and (default_encoding != encoding):
145 name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
146 paths = [(name, length)]
149 for name in 'announce', 'announce-list', 'codepage', 'comment', \
150 'created by', 'creation date', 'encoding', \
151 'nodes', 'publisher', 'publisher-url':
152 if name == 'comment' and 'comment.utf-8' in torrent:
153 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
154 meta.append(('.META/' + name, len(data)))
155 elif name in torrent:
156 if name == 'announce-list':
157 data = decode_announce_list(torrent[name])
158 elif name == 'codepage':
159 data = str(torrent[name])
160 elif name == 'creation date':
161 data = decode_datetime(torrent[name])
162 elif name == 'nodes':
163 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
164 data = '\n'.join(data)
167 meta.append(('.META/' + name, len(data)))
169 if 'private' in info:
170 meta.append(('.META/private', 1))
172 if 'piece length' in info:
173 meta.append(('.META/piece length', len(str(info['piece length']))))
175 for name, size in paths + meta:
176 print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
179 def mctorrent_copyout():
180 """Extract a file from the VFS"""
182 torrent_filename = sys.argv[3]
183 real_filename = sys.argv[4]
186 for name in 'announce', 'announce-list', 'codepage', 'comment', \
187 'created by', 'creation date', 'encoding', \
188 'nodes', 'publisher', 'publisher-url':
189 if name == 'comment' and 'comment.utf-8' in torrent:
190 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
191 meta.append(('.META/' + name, len(data)))
192 elif torrent_filename == '.META/' + name:
194 if name == 'announce-list':
195 data = decode_announce_list(torrent[name])
196 elif name == 'codepage':
197 data = str(torrent[name])
198 elif name == 'creation date':
199 data = decode_datetime(torrent[name])
200 elif name == 'nodes':
201 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
202 data = '\n'.join(data)
204 data = str(torrent[name])
206 torrent_error('Unknown ' + name)
209 if torrent_filename in ('.META/private', '.META/piece length'):
210 if 'info' not in torrent:
211 torrent_error('Info absent')
212 info = torrent['info']
213 if torrent_filename == '.META/private':
214 if 'private' not in info:
215 torrent_error('Info absent')
216 if torrent_filename == '.META/piece length':
217 if 'piece length' not in info:
218 torrent_error('Info absent')
219 data = str(info[torrent_filename[len('.META/'):]])
221 if not torrent_filename.startswith('.META/'):
225 torrent_error('Unknown file name')
227 outfile = open(real_filename, 'w')
232 def mctorrent_copyin():
233 """Put a file to the VFS"""
234 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
237 """Remove a file from the VFS"""
238 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
240 mctorrent_rmdir = mctorrent_rm
242 def mctorrent_mkdir():
243 """Create a directory in the VFS"""
244 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
247 def torrent_error(error_str):
248 logger.critical("Error parsing the torrent metafile: %s", error_str)
251 def decode_torrent():
253 torrent_file = open(sys.argv[2], 'r')
254 data = torrent_file.read()
257 except IOError, error_str:
258 torrent_error(error_str)
261 def decode_datetime(dt):
262 from time import localtime, asctime
264 l_now = localtime(the_time)
265 return asctime(l_now)
267 def decode_announce_list(announce):
268 return '\n'.join(l[0] for l in announce)
271 command = sys.argv[1]
272 procname = "mctorrent_" + command
275 if not g.has_key(procname):
276 logger.critical("Unknown command %s", command)
279 torrent = decode_torrent()
286 logger.exception("Error during run")