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 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. The VFS
16 lists all files and directories from the torrent metafile; all files appear
17 empty, of course, but the sizes are shown. Filenames are reencoded from the
18 metafile's encoding/codepage to the current locale.
20 Along with the files/directories in the torrent metafile the VFS also presents
21 meta information - in the form of files in .META directory. The size and
22 contents of these files are taken from the corresponding fields in the torrent
23 metafile. The script doesn't check if the torrent consists of a .META file or
24 directory (quite unlikely).
26 Date/time for all files is set to midnight of the 1st January of the current
27 year. The filesystem is, naturally, read-only.
32 __author__ = "Oleg Broytman <phd@phdru.name>"
33 __copyright__ = "Copyright (C) 2010-2013 PhiloSoft Design"
38 from eff_bdecode import decode
47 # Get the default charset.
49 lcAll = locale.getdefaultlocale()
50 except locale.Error, err:
51 print >>sys.stderr, "WARNING:", err
55 default_encoding = lcAll[1]
58 default_encoding = locale.getpreferredencoding()
59 except locale.Error, err:
60 print >>sys.stderr, "WARNING:", err
61 default_encoding = sys.getdefaultencoding()
63 default_encoding = sys.getdefaultencoding()
66 logger = logging.getLogger('torrent-mcextfs')
67 log_err_handler = logging.StreamHandler(sys.stderr)
68 logger.addHandler(log_err_handler)
69 logger.setLevel(logging.INFO)
73 Torrent Virtual FileSystem for Midnight Commander version %s
77 This is not a program. Put the script in $HOME/.mc/extfs.d or
78 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
79 __version__, __author__, __copyright__
83 locale.setlocale(locale.LC_ALL, '')
87 """List the entire VFS"""
89 if 'info' not in torrent:
90 torrent_error('Info absent')
92 info = torrent['info']
93 if 'name' not in info and 'name.utf-8' not in info:
94 torrent_error('Unknown name')
96 codepage = torrent.get('codepage', None)
97 encoding = torrent.get('encoding', None)
98 if not encoding and codepage:
99 encoding = str(codepage)
102 name_utf8 = info.get('name.utf-8', None)
105 files = info['files']
108 if 'path' not in file and 'path.utf-8' not in file:
109 torrent_error('Unknown path')
110 if 'length' not in file:
111 torrent_error('Unknown length')
112 if 'path.utf-8' in file:
114 path = '/'.join([name_utf8] + file['path.utf-8'])
115 if default_encoding != 'utf-8':
116 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
119 if encoding and (encoding != 'utf-8'):
120 _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
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')
127 if encoding and (encoding != 'utf-8'):
128 path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
129 path = '/'.join([name_utf8] + path)
130 if default_encoding != 'utf-8':
131 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
133 path = '/'.join([name] + file['path'])
134 if encoding and (default_encoding != encoding):
135 path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
136 length = file['length']
137 paths.append((path, length))
138 else: # One-file torrent
139 if 'length' not in info:
140 torrent_error('Unknown length')
141 length = info['length']
143 if default_encoding != 'utf-8':
144 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
145 elif encoding and (default_encoding != encoding):
146 name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
147 paths = [(name, length)]
150 for name in 'announce', 'announce-list', 'codepage', 'comment', \
151 'created by', 'creation date', 'encoding', \
152 'nodes', 'publisher', 'publisher-url':
153 if name == 'comment' and 'comment.utf-8' in torrent:
154 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
155 meta.append(('.META/' + name, len(data)))
156 elif name in torrent:
157 if name == 'announce-list':
158 data = decode_announce_list(torrent[name])
159 elif name == 'codepage':
160 data = str(torrent[name])
161 elif name == 'creation date':
162 data = decode_datetime(torrent[name])
163 elif name == 'nodes':
164 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
165 data = '\n'.join(data)
168 meta.append(('.META/' + name, len(data)))
170 if 'private' in info:
171 meta.append(('.META/private', 1))
173 if 'piece length' in info:
174 meta.append(('.META/piece length', len(str(info['piece length']))))
176 for name, size in paths + meta:
177 print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
180 def mctorrent_copyout():
181 """Extract a file from the VFS"""
183 torrent_filename = sys.argv[3]
184 real_filename = sys.argv[4]
187 for name in 'announce', 'announce-list', 'codepage', 'comment', \
188 'created by', 'creation date', 'encoding', \
189 'nodes', 'publisher', 'publisher-url':
190 if name == 'comment' and 'comment.utf-8' in torrent:
191 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
192 meta.append(('.META/' + name, len(data)))
193 elif torrent_filename == '.META/' + name:
195 if name == 'announce-list':
196 data = decode_announce_list(torrent[name])
197 elif name == 'codepage':
198 data = str(torrent[name])
199 elif name == 'creation date':
200 data = decode_datetime(torrent[name])
201 elif name == 'nodes':
202 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
203 data = '\n'.join(data)
205 data = str(torrent[name])
207 torrent_error('Unknown ' + name)
210 if torrent_filename in ('.META/private', '.META/piece length'):
211 if 'info' not in torrent:
212 torrent_error('Info absent')
213 info = torrent['info']
214 if torrent_filename == '.META/private':
215 if 'private' not in info:
216 torrent_error('Info absent')
217 if torrent_filename == '.META/piece length':
218 if 'piece length' not in info:
219 torrent_error('Info absent')
220 data = str(info[torrent_filename[len('.META/'):]])
222 if not torrent_filename.startswith('.META/'):
226 torrent_error('Unknown file name')
228 outfile = open(real_filename, 'w')
233 def mctorrent_copyin():
234 """Put a file to the VFS"""
235 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
238 """Remove a file from the VFS"""
239 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
241 mctorrent_rmdir = mctorrent_rm
243 def mctorrent_mkdir():
244 """Create a directory in the VFS"""
245 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
248 def torrent_error(error_str):
249 logger.critical("Error parsing the torrent metafile: %s", error_str)
252 def decode_torrent():
254 torrent_file = open(sys.argv[2], 'r')
255 data = torrent_file.read()
258 except IOError, error_str:
259 torrent_error(error_str)
262 def decode_datetime(dt):
263 from time import localtime, asctime
265 l_now = localtime(the_time)
266 return asctime(l_now)
268 def decode_announce_list(announce):
269 return '\n'.join(l[0] for l in announce)
272 command = sys.argv[1]
273 procname = "mctorrent_" + command
276 if not g.has_key(procname):
277 logger.critical("Unknown command %s", command)
280 torrent = decode_torrent()
287 logger.exception("Error during run")