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+ just put the script in $HOME/[.local/share/].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 For mc 4.7+ run this "cd" command in the Midnight Commander (in the "bindings"
14 file the command is "%cd"): cd file/torrent://; In older versions it is
15 cd file#torrent, where "file" is the name of your torrent metafile.
17 See detailed installation instructions at
18 https://phdru.name/Software/mc/torrent_INSTALL.html.
20 The VFS lists all files and directories from the torrent metafile; all files
21 appear empty, of course, but the sizes are shown. Filenames are reencoded from
22 the metafile's encoding/codepage to the current locale.
24 Along with the files/directories in the torrent metafile the VFS also presents
25 meta information - in the form of files in .META directory. The size and
26 contents of these files are taken from the corresponding fields in the torrent
27 metafile. The script doesn't check if the torrent consists of a .META file or
28 directory (quite unlikely).
30 Date/time for all directories/files is set to the value of 'creation date'
31 field, if it exists; if not date/time is set to the last modification time of
32 the torrent file itself.
34 The filesystem is, naturally, read-only.
39 __author__ = "Oleg Broytman <phd@phdru.name>"
40 __copyright__ = "Copyright (C) 2010-2018 PhiloSoft Design"
44 from datetime import datetime
45 from os.path import dirname, getmtime
47 from time import localtime, asctime
48 from eff_bdecode import decode
57 # Get the default charset.
59 lcAll = locale.getdefaultlocale()
60 except locale.Error, err:
61 print >>sys.stderr, "WARNING:", err
65 default_encoding = lcAll[1]
68 default_encoding = locale.getpreferredencoding()
69 except locale.Error, err:
70 print >>sys.stderr, "WARNING:", err
71 default_encoding = sys.getdefaultencoding()
73 default_encoding = sys.getdefaultencoding()
76 logger = logging.getLogger('torrent-mcextfs')
77 log_err_handler = logging.StreamHandler(sys.stderr)
78 logger.addHandler(log_err_handler)
79 logger.setLevel(logging.INFO)
83 Torrent Virtual FileSystem for Midnight Commander version %s
87 This is not a program. Put the script in $HOME/[.local/share/].mc/extfs.d or
88 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
89 __version__, __author__, __copyright__)
92 locale.setlocale(locale.LC_ALL, '')
96 """List the entire VFS"""
98 if 'info' not in torrent:
99 torrent_error('Info absent')
101 info = torrent['info']
102 if 'name' not in info and 'name.utf-8' not in info:
103 torrent_error('Unknown name')
105 codepage = torrent.get('codepage', None)
106 encoding = torrent.get('encoding', None)
107 if not encoding and codepage:
108 encoding = str(codepage)
111 name_utf8 = info.get('name.utf-8', None)
115 files = info['files']
118 if 'path' not in file and 'path.utf-8' not in file:
119 torrent_error('Unknown path')
120 if 'length' not in file:
121 torrent_error('Unknown length')
122 if 'path.utf-8' in file:
124 path = '/'.join([name_utf8] + file['path.utf-8'])
125 if default_encoding != 'utf-8':
126 path = path.decode('utf-8', 'replace').encode(
127 default_encoding, 'replace')
130 if encoding and (encoding != 'utf-8'):
131 _name_utf8 = _name_utf8.decode(
132 encoding, 'replace').encode('utf-8', 'replace')
133 path = '/'.join([_name_utf8] + file['path.utf-8'])
134 if default_encoding != 'utf-8':
135 path = path.decode('utf-8', 'replace').encode(
136 default_encoding, 'replace')
140 if encoding and (encoding != 'utf-8'):
141 path = path.decode(encoding, 'replace').encode(
143 path = '/'.join([name_utf8] + path)
144 if default_encoding != 'utf-8':
145 path = path.decode('utf-8', 'replace').encode(
146 default_encoding, 'replace')
148 path = '/'.join([name] + file['path'])
149 if encoding and (default_encoding != encoding):
150 path = path.decode(encoding, 'replace').encode(
151 default_encoding, 'replace')
152 length = file['length']
153 paths.append((path, length))
154 else: # One-file torrent
155 if 'length' not in info:
156 torrent_error('Unknown length')
157 length = info['length']
159 if default_encoding != 'utf-8':
160 name = name_utf8.decode('utf-8', 'replace').encode(
161 default_encoding, 'replace')
162 elif encoding and (default_encoding != encoding):
163 name = name.decode(encoding, 'replace').encode(
164 default_encoding, 'replace')
165 paths = [(name, length)]
168 for name in 'announce', 'announce-list', 'codepage', 'comment', \
169 'created by', 'creation date', 'encoding', \
170 'nodes', 'publisher', 'publisher-url':
171 if name == 'comment' and 'comment.utf-8' in torrent:
172 data = torrent['comment.utf-8'].decode('utf-8').encode(
173 default_encoding, 'replace')
174 meta.append(('.META/' + name, len(data)))
175 elif name in torrent:
176 if name == 'announce-list':
177 data = decode_announce_list(torrent[name])
178 elif name == 'codepage':
179 data = str(torrent[name])
180 elif name == 'creation date':
182 data = decode_datetime_asc(dt)
183 dt = decode_datetime(dt)
184 elif name == 'nodes':
185 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
186 data = '\n'.join(data)
189 meta.append(('.META/' + name, len(data)))
191 if 'private' in info:
192 meta.append(('.META/private', 1))
194 if 'piece length' in info:
195 meta.append(('.META/piece length', len(str(info['piece length']))))
199 for name, size in paths:
201 dirs.add(dirname(name))
204 dt = decode_datetime(getmtime(sys.argv[2]))
206 for name in sorted(dirs):
207 print "dr-xr-xr-x 1 user group 0 %s %s" % (dt, name)
209 for name, size in sorted(paths):
210 print "-r--r--r-- 1 user group %d %s %s" % (size, dt, name)
213 def mctorrent_copyout():
214 """Extract a file from the VFS"""
216 torrent_filename = sys.argv[3]
217 real_filename = sys.argv[4]
220 for name in 'announce', 'announce-list', 'codepage', 'comment', \
221 'created by', 'creation date', 'encoding', \
222 'nodes', 'publisher', 'publisher-url':
223 if name == 'comment' and 'comment.utf-8' in torrent:
224 data = torrent['comment.utf-8'].decode('utf-8').encode(
225 default_encoding, 'replace')
226 elif torrent_filename == '.META/' + name:
228 if name == 'announce-list':
229 data = decode_announce_list(torrent[name])
230 elif name == 'codepage':
231 data = str(torrent[name])
232 elif name == 'creation date':
233 data = decode_datetime_asc(torrent[name])
234 elif name == 'nodes':
235 data = ['%s:%s' % (host, port)
236 for host, port in torrent[name]]
237 data = '\n'.join(data)
239 data = str(torrent[name])
241 torrent_error('Unknown ' + name)
244 if torrent_filename in ('.META/private', '.META/piece length'):
245 if 'info' not in torrent:
246 torrent_error('Info absent')
247 info = torrent['info']
248 if torrent_filename == '.META/private':
249 if 'private' not in info:
250 torrent_error('Info absent')
251 if torrent_filename == '.META/piece length':
252 if 'piece length' not in info:
253 torrent_error('Info absent')
254 data = str(info[torrent_filename[len('.META/'):]])
256 if not torrent_filename.startswith('.META/'):
260 torrent_error('Unknown file name')
262 outfile = open(real_filename, 'w')
267 def mctorrent_copyin():
268 """Put a file to the VFS"""
269 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
273 """Remove a file from the VFS"""
274 sys.exit("Torrent VFS doesn't support removing files/directories "
275 "(read-only filesystem)")
278 mctorrent_rmdir = mctorrent_rm
281 def mctorrent_mkdir():
282 """Create a directory in the VFS"""
283 sys.exit("Torrent VFS doesn't support creating directories "
284 "(read-only filesystem)")
287 def torrent_error(error_str):
288 logger.critical("Error parsing the torrent metafile: %s", error_str)
292 def decode_torrent():
294 torrent_file = open(sys.argv[2], 'r')
295 data = torrent_file.read()
298 except IOError, error_str:
299 torrent_error(error_str)
302 def decode_datetime_asc(dt):
304 return asctime(localtime(float(dt)))
306 return datetime.max.ctime()
309 def decode_datetime(dt):
311 Y, m, d, H, M = localtime(float(dt))[0:5]
313 return datetime.max.ctime()
315 return "%02d-%02d-%d %02d:%02d" % (m, d, Y, H, M)
318 def decode_announce_list(announce):
319 return '\n'.join(l[0] for l in announce if l)
322 command = sys.argv[1]
323 procname = "mctorrent_" + command
326 if procname not in g:
327 logger.critical("Unknown command %s", command)
330 torrent = decode_torrent()
337 logger.exception("Error during run")