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__
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(default_encoding, 'replace')
129 if encoding and (encoding != 'utf-8'):
130 _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
131 path = '/'.join([_name_utf8] + file['path.utf-8'])
132 if default_encoding != 'utf-8':
133 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
137 if encoding and (encoding != 'utf-8'):
138 path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
139 path = '/'.join([name_utf8] + path)
140 if default_encoding != 'utf-8':
141 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
143 path = '/'.join([name] + file['path'])
144 if encoding and (default_encoding != encoding):
145 path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
146 length = file['length']
147 paths.append((path, length))
148 else: # One-file torrent
149 if 'length' not in info:
150 torrent_error('Unknown length')
151 length = info['length']
153 if default_encoding != 'utf-8':
154 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
155 elif encoding and (default_encoding != encoding):
156 name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
157 paths = [(name, length)]
160 for name in 'announce', 'announce-list', 'codepage', 'comment', \
161 'created by', 'creation date', 'encoding', \
162 'nodes', 'publisher', 'publisher-url':
163 if name == 'comment' and 'comment.utf-8' in torrent:
164 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
165 meta.append(('.META/' + name, len(data)))
166 elif name in torrent:
167 if name == 'announce-list':
168 data = decode_announce_list(torrent[name])
169 elif name == 'codepage':
170 data = str(torrent[name])
171 elif name == 'creation date':
173 data = decode_datetime_asc(dt)
174 dt = decode_datetime(dt)
175 elif name == 'nodes':
176 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
177 data = '\n'.join(data)
180 meta.append(('.META/' + name, len(data)))
182 if 'private' in info:
183 meta.append(('.META/private', 1))
185 if 'piece length' in info:
186 meta.append(('.META/piece length', len(str(info['piece length']))))
190 for name, size in paths:
192 dirs.add(dirname(name))
195 dt = decode_datetime(getmtime(sys.argv[2]))
197 for name in sorted(dirs):
198 print "dr-xr-xr-x 1 user group 0 %s %s" % (dt, name)
200 for name, size in sorted(paths):
201 print "-r--r--r-- 1 user group %d %s %s" % (size, dt, name)
204 def mctorrent_copyout():
205 """Extract a file from the VFS"""
207 torrent_filename = sys.argv[3]
208 real_filename = sys.argv[4]
211 for name in 'announce', 'announce-list', 'codepage', 'comment', \
212 'created by', 'creation date', 'encoding', \
213 'nodes', 'publisher', 'publisher-url':
214 if name == 'comment' and 'comment.utf-8' in torrent:
215 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
216 elif torrent_filename == '.META/' + name:
218 if name == 'announce-list':
219 data = decode_announce_list(torrent[name])
220 elif name == 'codepage':
221 data = str(torrent[name])
222 elif name == 'creation date':
223 data = decode_datetime_asc(torrent[name])
224 elif name == 'nodes':
225 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
226 data = '\n'.join(data)
228 data = str(torrent[name])
230 torrent_error('Unknown ' + name)
233 if torrent_filename in ('.META/private', '.META/piece length'):
234 if 'info' not in torrent:
235 torrent_error('Info absent')
236 info = torrent['info']
237 if torrent_filename == '.META/private':
238 if 'private' not in info:
239 torrent_error('Info absent')
240 if torrent_filename == '.META/piece length':
241 if 'piece length' not in info:
242 torrent_error('Info absent')
243 data = str(info[torrent_filename[len('.META/'):]])
245 if not torrent_filename.startswith('.META/'):
249 torrent_error('Unknown file name')
251 outfile = open(real_filename, 'w')
256 def mctorrent_copyin():
257 """Put a file to the VFS"""
258 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
261 """Remove a file from the VFS"""
262 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
264 mctorrent_rmdir = mctorrent_rm
266 def mctorrent_mkdir():
267 """Create a directory in the VFS"""
268 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
271 def torrent_error(error_str):
272 logger.critical("Error parsing the torrent metafile: %s", error_str)
275 def decode_torrent():
277 torrent_file = open(sys.argv[2], 'r')
278 data = torrent_file.read()
281 except IOError, error_str:
282 torrent_error(error_str)
285 def decode_datetime_asc(dt):
286 return asctime(localtime(float(dt)))
288 def decode_datetime(dt):
289 Y, m, d, H, M = localtime(float(dt))[0:5]
290 return "%02d-%02d-%d %02d:%02d" % (m, d, Y, H, M)
292 def decode_announce_list(announce):
293 return '\n'.join(l[0] for l in announce)
296 command = sys.argv[1]
297 procname = "mctorrent_" + command
300 if not g.has_key(procname):
301 logger.critical("Unknown command %s", command)
304 torrent = decode_torrent()
311 logger.exception("Error during run")