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 http://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-2015 PhiloSoft Design"
44 from os.path import dirname, getmtime
46 from time import localtime, asctime
47 from eff_bdecode import decode
56 # Get the default charset.
58 lcAll = locale.getdefaultlocale()
59 except locale.Error, err:
60 print >>sys.stderr, "WARNING:", err
64 default_encoding = lcAll[1]
67 default_encoding = locale.getpreferredencoding()
68 except locale.Error, err:
69 print >>sys.stderr, "WARNING:", err
70 default_encoding = sys.getdefaultencoding()
72 default_encoding = sys.getdefaultencoding()
75 logger = logging.getLogger('torrent-mcextfs')
76 log_err_handler = logging.StreamHandler(sys.stderr)
77 logger.addHandler(log_err_handler)
78 logger.setLevel(logging.INFO)
82 Torrent Virtual FileSystem for Midnight Commander version %s
86 This is not a program. Put the script in $HOME/[.local/share/].mc/extfs.d or
87 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
88 __version__, __author__, __copyright__)
91 locale.setlocale(locale.LC_ALL, '')
95 """List the entire VFS"""
97 if 'info' not in torrent:
98 torrent_error('Info absent')
100 info = torrent['info']
101 if 'name' not in info and 'name.utf-8' not in info:
102 torrent_error('Unknown name')
104 codepage = torrent.get('codepage', None)
105 encoding = torrent.get('encoding', None)
106 if not encoding and codepage:
107 encoding = str(codepage)
110 name_utf8 = info.get('name.utf-8', None)
114 files = info['files']
117 if 'path' not in file and 'path.utf-8' not in file:
118 torrent_error('Unknown path')
119 if 'length' not in file:
120 torrent_error('Unknown length')
121 if 'path.utf-8' in file:
123 path = '/'.join([name_utf8] + file['path.utf-8'])
124 if default_encoding != 'utf-8':
125 path = path.decode('utf-8', 'replace').encode(
126 default_encoding, 'replace')
129 if encoding and (encoding != 'utf-8'):
130 _name_utf8 = _name_utf8.decode(
131 encoding, 'replace').encode('utf-8', 'replace')
132 path = '/'.join([_name_utf8] + file['path.utf-8'])
133 if default_encoding != 'utf-8':
134 path = path.decode('utf-8', 'replace').encode(
135 default_encoding, 'replace')
139 if encoding and (encoding != 'utf-8'):
140 path = path.decode(encoding, 'replace').encode(
142 path = '/'.join([name_utf8] + path)
143 if default_encoding != 'utf-8':
144 path = path.decode('utf-8', 'replace').encode(
145 default_encoding, 'replace')
147 path = '/'.join([name] + file['path'])
148 if encoding and (default_encoding != encoding):
149 path = path.decode(encoding, 'replace').encode(
150 default_encoding, 'replace')
151 length = file['length']
152 paths.append((path, length))
153 else: # One-file torrent
154 if 'length' not in info:
155 torrent_error('Unknown length')
156 length = info['length']
158 if default_encoding != 'utf-8':
159 name = name_utf8.decode('utf-8', 'replace').encode(
160 default_encoding, 'replace')
161 elif encoding and (default_encoding != encoding):
162 name = name.decode(encoding, 'replace').encode(
163 default_encoding, 'replace')
164 paths = [(name, length)]
167 for name in 'announce', 'announce-list', 'codepage', 'comment', \
168 'created by', 'creation date', 'encoding', \
169 'nodes', 'publisher', 'publisher-url':
170 if name == 'comment' and 'comment.utf-8' in torrent:
171 data = torrent['comment.utf-8'].decode('utf-8').encode(
172 default_encoding, 'replace')
173 meta.append(('.META/' + name, len(data)))
174 elif name in torrent:
175 if name == 'announce-list':
176 data = decode_announce_list(torrent[name])
177 elif name == 'codepage':
178 data = str(torrent[name])
179 elif name == 'creation date':
181 data = decode_datetime_asc(dt)
182 dt = decode_datetime(dt)
183 elif name == 'nodes':
184 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
185 data = '\n'.join(data)
188 meta.append(('.META/' + name, len(data)))
190 if 'private' in info:
191 meta.append(('.META/private', 1))
193 if 'piece length' in info:
194 meta.append(('.META/piece length', len(str(info['piece length']))))
198 for name, size in paths:
200 dirs.add(dirname(name))
203 dt = decode_datetime(getmtime(sys.argv[2]))
205 for name in sorted(dirs):
206 print "dr-xr-xr-x 1 user group 0 %s %s" % (dt, name)
208 for name, size in sorted(paths):
209 print "-r--r--r-- 1 user group %d %s %s" % (size, dt, name)
212 def mctorrent_copyout():
213 """Extract a file from the VFS"""
215 torrent_filename = sys.argv[3]
216 real_filename = sys.argv[4]
219 for name in 'announce', 'announce-list', 'codepage', 'comment', \
220 'created by', 'creation date', 'encoding', \
221 'nodes', 'publisher', 'publisher-url':
222 if name == 'comment' and 'comment.utf-8' in torrent:
223 data = torrent['comment.utf-8'].decode('utf-8').encode(
224 default_encoding, 'replace')
225 elif torrent_filename == '.META/' + name:
227 if name == 'announce-list':
228 data = decode_announce_list(torrent[name])
229 elif name == 'codepage':
230 data = str(torrent[name])
231 elif name == 'creation date':
232 data = decode_datetime_asc(torrent[name])
233 elif name == 'nodes':
234 data = ['%s:%s' % (host, port)
235 for host, port in torrent[name]]
236 data = '\n'.join(data)
238 data = str(torrent[name])
240 torrent_error('Unknown ' + name)
243 if torrent_filename in ('.META/private', '.META/piece length'):
244 if 'info' not in torrent:
245 torrent_error('Info absent')
246 info = torrent['info']
247 if torrent_filename == '.META/private':
248 if 'private' not in info:
249 torrent_error('Info absent')
250 if torrent_filename == '.META/piece length':
251 if 'piece length' not in info:
252 torrent_error('Info absent')
253 data = str(info[torrent_filename[len('.META/'):]])
255 if not torrent_filename.startswith('.META/'):
259 torrent_error('Unknown file name')
261 outfile = open(real_filename, 'w')
266 def mctorrent_copyin():
267 """Put a file to the VFS"""
268 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
272 """Remove a file from the VFS"""
273 sys.exit("Torrent VFS doesn't support removing files/directories "
274 "(read-only filesystem)")
277 mctorrent_rmdir = mctorrent_rm
280 def mctorrent_mkdir():
281 """Create a directory in the VFS"""
282 sys.exit("Torrent VFS doesn't support creating directories "
283 "(read-only filesystem)")
286 def torrent_error(error_str):
287 logger.critical("Error parsing the torrent metafile: %s", error_str)
291 def decode_torrent():
293 torrent_file = open(sys.argv[2], 'r')
294 data = torrent_file.read()
297 except IOError, error_str:
298 torrent_error(error_str)
301 def decode_datetime_asc(dt):
302 return asctime(localtime(float(dt)))
305 def decode_datetime(dt):
306 Y, m, d, H, M = localtime(float(dt))[0:5]
307 return "%02d-%02d-%d %02d:%02d" % (m, d, Y, H, M)
310 def decode_announce_list(announce):
311 return '\n'.join(l[0] for l in announce)
314 command = sys.argv[1]
315 procname = "mctorrent_" + command
318 if procname not in g:
319 logger.critical("Unknown command %s", command)
322 torrent = decode_torrent()
329 logger.exception("Error during run")