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 files is set to midnight of the 1st January of the current
31 year. The filesystem is, naturally, read-only.
36 __author__ = "Oleg Broytman <phd@phdru.name>"
37 __copyright__ = "Copyright (C) 2010-2015 PhiloSoft Design"
41 from os.path import dirname, getmtime
43 from time import localtime, asctime
44 from eff_bdecode import decode
53 # Get the default charset.
55 lcAll = locale.getdefaultlocale()
56 except locale.Error, err:
57 print >>sys.stderr, "WARNING:", err
61 default_encoding = lcAll[1]
64 default_encoding = locale.getpreferredencoding()
65 except locale.Error, err:
66 print >>sys.stderr, "WARNING:", err
67 default_encoding = sys.getdefaultencoding()
69 default_encoding = sys.getdefaultencoding()
72 logger = logging.getLogger('torrent-mcextfs')
73 log_err_handler = logging.StreamHandler(sys.stderr)
74 logger.addHandler(log_err_handler)
75 logger.setLevel(logging.INFO)
79 Torrent Virtual FileSystem for Midnight Commander version %s
83 This is not a program. Put the script in $HOME/[.local/share/].mc/extfs.d or
84 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
85 __version__, __author__, __copyright__
89 locale.setlocale(locale.LC_ALL, '')
93 """List the entire VFS"""
95 if 'info' not in torrent:
96 torrent_error('Info absent')
98 info = torrent['info']
99 if 'name' not in info and 'name.utf-8' not in info:
100 torrent_error('Unknown name')
102 codepage = torrent.get('codepage', None)
103 encoding = torrent.get('encoding', None)
104 if not encoding and codepage:
105 encoding = str(codepage)
108 name_utf8 = info.get('name.utf-8', None)
112 files = info['files']
115 if 'path' not in file and 'path.utf-8' not in file:
116 torrent_error('Unknown path')
117 if 'length' not in file:
118 torrent_error('Unknown length')
119 if 'path.utf-8' in file:
121 path = '/'.join([name_utf8] + file['path.utf-8'])
122 if default_encoding != 'utf-8':
123 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
126 if encoding and (encoding != 'utf-8'):
127 _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
128 path = '/'.join([_name_utf8] + file['path.utf-8'])
129 if default_encoding != 'utf-8':
130 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
134 if encoding and (encoding != 'utf-8'):
135 path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
136 path = '/'.join([name_utf8] + path)
137 if default_encoding != 'utf-8':
138 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
140 path = '/'.join([name] + file['path'])
141 if encoding and (default_encoding != encoding):
142 path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
143 length = file['length']
144 paths.append((path, length))
145 else: # One-file torrent
146 if 'length' not in info:
147 torrent_error('Unknown length')
148 length = info['length']
150 if default_encoding != 'utf-8':
151 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
152 elif encoding and (default_encoding != encoding):
153 name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
154 paths = [(name, length)]
157 for name in 'announce', 'announce-list', 'codepage', 'comment', \
158 'created by', 'creation date', 'encoding', \
159 'nodes', 'publisher', 'publisher-url':
160 if name == 'comment' and 'comment.utf-8' in torrent:
161 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
162 meta.append(('.META/' + name, len(data)))
163 elif name in torrent:
164 if name == 'announce-list':
165 data = decode_announce_list(torrent[name])
166 elif name == 'codepage':
167 data = str(torrent[name])
168 elif name == 'creation date':
170 data = decode_datetime_asc(dt)
171 dt = decode_datetime(dt)
172 elif name == 'nodes':
173 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
174 data = '\n'.join(data)
177 meta.append(('.META/' + name, len(data)))
179 if 'private' in info:
180 meta.append(('.META/private', 1))
182 if 'piece length' in info:
183 meta.append(('.META/piece length', len(str(info['piece length']))))
187 for name, size in paths:
189 dirs.add(dirname(name))
192 dt = decode_datetime(getmtime(sys.argv[2]))
194 for name in sorted(dirs):
195 print "dr-xr-xr-x 1 user group 0 %s %s" % (dt, name)
197 for name, size in sorted(paths):
198 print "-r--r--r-- 1 user group %d %s %s" % (size, dt, name)
201 def mctorrent_copyout():
202 """Extract a file from the VFS"""
204 torrent_filename = sys.argv[3]
205 real_filename = sys.argv[4]
208 for name in 'announce', 'announce-list', 'codepage', 'comment', \
209 'created by', 'creation date', 'encoding', \
210 'nodes', 'publisher', 'publisher-url':
211 if name == 'comment' and 'comment.utf-8' in torrent:
212 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
213 elif torrent_filename == '.META/' + name:
215 if name == 'announce-list':
216 data = decode_announce_list(torrent[name])
217 elif name == 'codepage':
218 data = str(torrent[name])
219 elif name == 'creation date':
220 data = decode_datetime_asc(torrent[name])
221 elif name == 'nodes':
222 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
223 data = '\n'.join(data)
225 data = str(torrent[name])
227 torrent_error('Unknown ' + name)
230 if torrent_filename in ('.META/private', '.META/piece length'):
231 if 'info' not in torrent:
232 torrent_error('Info absent')
233 info = torrent['info']
234 if torrent_filename == '.META/private':
235 if 'private' not in info:
236 torrent_error('Info absent')
237 if torrent_filename == '.META/piece length':
238 if 'piece length' not in info:
239 torrent_error('Info absent')
240 data = str(info[torrent_filename[len('.META/'):]])
242 if not torrent_filename.startswith('.META/'):
246 torrent_error('Unknown file name')
248 outfile = open(real_filename, 'w')
253 def mctorrent_copyin():
254 """Put a file to the VFS"""
255 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
258 """Remove a file from the VFS"""
259 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
261 mctorrent_rmdir = mctorrent_rm
263 def mctorrent_mkdir():
264 """Create a directory in the VFS"""
265 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
268 def torrent_error(error_str):
269 logger.critical("Error parsing the torrent metafile: %s", error_str)
272 def decode_torrent():
274 torrent_file = open(sys.argv[2], 'r')
275 data = torrent_file.read()
278 except IOError, error_str:
279 torrent_error(error_str)
282 def decode_datetime_asc(dt):
283 return asctime(localtime(float(dt)))
285 def decode_datetime(dt):
286 Y, m, d, H, M = localtime(float(dt))[0:5]
287 return "%02d-%02d-%d %02d:%02d" % (m, d, Y, H, M)
289 def decode_announce_list(announce):
290 return '\n'.join(l[0] for l in announce)
293 command = sys.argv[1]
294 procname = "mctorrent_" + command
297 if not g.has_key(procname):
298 logger.critical("Unknown command %s", command)
301 torrent = decode_torrent()
308 logger.exception("Error during run")