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/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-2013 PhiloSoft Design"
42 from eff_bdecode import decode
51 # Get the default charset.
53 lcAll = locale.getdefaultlocale()
54 except locale.Error, err:
55 print >>sys.stderr, "WARNING:", err
59 default_encoding = lcAll[1]
62 default_encoding = locale.getpreferredencoding()
63 except locale.Error, err:
64 print >>sys.stderr, "WARNING:", err
65 default_encoding = sys.getdefaultencoding()
67 default_encoding = sys.getdefaultencoding()
70 logger = logging.getLogger('torrent-mcextfs')
71 log_err_handler = logging.StreamHandler(sys.stderr)
72 logger.addHandler(log_err_handler)
73 logger.setLevel(logging.INFO)
77 Torrent Virtual FileSystem for Midnight Commander version %s
81 This is not a program. Put the script in $HOME/[.local/share/].mc/extfs.d or
82 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
83 __version__, __author__, __copyright__
87 locale.setlocale(locale.LC_ALL, '')
91 """List the entire VFS"""
93 if 'info' not in torrent:
94 torrent_error('Info absent')
96 info = torrent['info']
97 if 'name' not in info and 'name.utf-8' not in info:
98 torrent_error('Unknown name')
100 codepage = torrent.get('codepage', None)
101 encoding = torrent.get('encoding', None)
102 if not encoding and codepage:
103 encoding = str(codepage)
106 name_utf8 = info.get('name.utf-8', None)
109 files = info['files']
112 if 'path' not in file and 'path.utf-8' not in file:
113 torrent_error('Unknown path')
114 if 'length' not in file:
115 torrent_error('Unknown length')
116 if 'path.utf-8' in file:
118 path = '/'.join([name_utf8] + file['path.utf-8'])
119 if default_encoding != 'utf-8':
120 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
123 if encoding and (encoding != 'utf-8'):
124 _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
125 path = '/'.join([_name_utf8] + file['path.utf-8'])
126 if default_encoding != 'utf-8':
127 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
131 if encoding and (encoding != 'utf-8'):
132 path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
133 path = '/'.join([name_utf8] + path)
134 if default_encoding != 'utf-8':
135 path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
137 path = '/'.join([name] + file['path'])
138 if encoding and (default_encoding != encoding):
139 path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
140 length = file['length']
141 paths.append((path, length))
142 else: # One-file torrent
143 if 'length' not in info:
144 torrent_error('Unknown length')
145 length = info['length']
147 if default_encoding != 'utf-8':
148 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
149 elif encoding and (default_encoding != encoding):
150 name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
151 paths = [(name, length)]
154 for name in 'announce', 'announce-list', 'codepage', 'comment', \
155 'created by', 'creation date', 'encoding', \
156 'nodes', 'publisher', 'publisher-url':
157 if name == 'comment' and 'comment.utf-8' in torrent:
158 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
159 meta.append(('.META/' + name, len(data)))
160 elif name in torrent:
161 if name == 'announce-list':
162 data = decode_announce_list(torrent[name])
163 elif name == 'codepage':
164 data = str(torrent[name])
165 elif name == 'creation date':
166 data = decode_datetime(torrent[name])
167 elif name == 'nodes':
168 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
169 data = '\n'.join(data)
172 meta.append(('.META/' + name, len(data)))
174 if 'private' in info:
175 meta.append(('.META/private', 1))
177 if 'piece length' in info:
178 meta.append(('.META/piece length', len(str(info['piece length']))))
180 for name, size in paths + meta:
181 print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
184 def mctorrent_copyout():
185 """Extract a file from the VFS"""
187 torrent_filename = sys.argv[3]
188 real_filename = sys.argv[4]
191 for name in 'announce', 'announce-list', 'codepage', 'comment', \
192 'created by', 'creation date', 'encoding', \
193 'nodes', 'publisher', 'publisher-url':
194 if name == 'comment' and 'comment.utf-8' in torrent:
195 data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
196 elif torrent_filename == '.META/' + name:
198 if name == 'announce-list':
199 data = decode_announce_list(torrent[name])
200 elif name == 'codepage':
201 data = str(torrent[name])
202 elif name == 'creation date':
203 data = decode_datetime(torrent[name])
204 elif name == 'nodes':
205 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
206 data = '\n'.join(data)
208 data = str(torrent[name])
210 torrent_error('Unknown ' + name)
213 if torrent_filename in ('.META/private', '.META/piece length'):
214 if 'info' not in torrent:
215 torrent_error('Info absent')
216 info = torrent['info']
217 if torrent_filename == '.META/private':
218 if 'private' not in info:
219 torrent_error('Info absent')
220 if torrent_filename == '.META/piece length':
221 if 'piece length' not in info:
222 torrent_error('Info absent')
223 data = str(info[torrent_filename[len('.META/'):]])
225 if not torrent_filename.startswith('.META/'):
229 torrent_error('Unknown file name')
231 outfile = open(real_filename, 'w')
236 def mctorrent_copyin():
237 """Put a file to the VFS"""
238 sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
241 """Remove a file from the VFS"""
242 sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
244 mctorrent_rmdir = mctorrent_rm
246 def mctorrent_mkdir():
247 """Create a directory in the VFS"""
248 sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
251 def torrent_error(error_str):
252 logger.critical("Error parsing the torrent metafile: %s", error_str)
255 def decode_torrent():
257 torrent_file = open(sys.argv[2], 'r')
258 data = torrent_file.read()
261 except IOError, error_str:
262 torrent_error(error_str)
265 def decode_datetime(dt):
266 from time import localtime, asctime
268 l_now = localtime(the_time)
269 return asctime(l_now)
271 def decode_announce_list(announce):
272 return '\n'.join(l[0] for l in announce)
275 command = sys.argv[1]
276 procname = "mctorrent_" + command
279 if not g.has_key(procname):
280 logger.critical("Unknown command %s", command)
283 torrent = decode_torrent()
290 logger.exception("Error during run")