]> git.phdru.name Git - extfs.d.git/blob - torrent
XML VFS 0.2 - show attributes as a text file
[extfs.d.git] / torrent
1 #! /usr/bin/env python
2 """Torrent Virtual FileSystem for Midnight Commander
3
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).
7
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.
12
13 Run this "cd" command in the Midnight Commander (in the "bindings" file the
14 command is "%cd"): cd file#torrent, where "file" is the name of your torrent
15 metafile. The VFS lists all files and directories from the torrent metafile;
16 all files appear empty, of course, but the sizes are shown. Filenames are
17 reencoded from the metafile's encoding/codepage to the current locale.
18
19 Along with the files/directories in the torrent metafile the VFS also presents
20 meta information - in the form of files in .META directory. The size and
21 contents of these files are taken from the corresponding fields in the torrent
22 metafile. The script doesn't check if the torrent consists of a .META file or
23 directory (quite unlikely).
24
25 Date/time for all files is set to midnight of the 1st January of the current
26 year. The filesystem is, naturally, read-only.
27
28 """
29
30 __version__ = "1.2.0"
31 __author__ = "Oleg Broytman <phd@phdru.name>"
32 __copyright__ = "Copyright (C) 2010-2013 PhiloSoft Design"
33 __license__ = "GPL"
34
35
36 import sys, os
37 from eff_bdecode import decode
38
39 try:
40    import locale
41    use_locale = True
42 except ImportError:
43    use_locale = False
44
45 if use_locale:
46    # Get the default charset.
47    try:
48       lcAll = locale.getdefaultlocale()
49    except locale.Error, err:
50       print >>sys.stderr, "WARNING:", err
51       lcAll = []
52
53    if len(lcAll) == 2:
54       default_encoding = lcAll[1]
55    else:
56       try:
57          default_encoding = locale.getpreferredencoding()
58       except locale.Error, err:
59          print >>sys.stderr, "WARNING:", err
60          default_encoding = sys.getdefaultencoding()
61 else:
62    default_encoding = sys.getdefaultencoding()
63
64 import logging
65 logger = logging.getLogger('torrent-mcextfs')
66 log_err_handler = logging.StreamHandler(sys.stderr)
67 logger.addHandler(log_err_handler)
68 logger.setLevel(logging.INFO)
69
70 if len(sys.argv) < 3:
71     logger.critical("""\
72 Torrent Virtual FileSystem for Midnight Commander version %s
73 Author: %s
74 %s
75
76 This is not a program. Put the script in $HOME/.mc/extfs.d or
77 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
78    __version__, __author__, __copyright__
79 )
80     sys.exit(1)
81
82 locale.setlocale(locale.LC_ALL, '')
83
84
85 def mctorrent_list():
86     """List the entire VFS"""
87
88     if 'info' not in torrent:
89         torrent_error('Info absent')
90
91     info = torrent['info']
92     if 'name' not in info and 'name.utf-8' not in info:
93         torrent_error('Unknown name')
94
95     codepage = torrent.get('codepage', None)
96     encoding = torrent.get('encoding', None)
97     if not encoding and codepage:
98         encoding = str(codepage)
99
100     name = info['name']
101     name_utf8 = info.get('name.utf-8', None)
102
103     if 'files' in info:
104         files = info['files']
105         paths = []
106         for file in files:
107             if 'path' not in file and 'path.utf-8' not in file:
108                 torrent_error('Unknown path')
109             if 'length' not in file:
110                 torrent_error('Unknown length')
111             if 'path.utf-8' in file:
112                 if name_utf8:
113                     path = '/'.join([name_utf8] + file['path.utf-8'])
114                     if default_encoding != 'utf-8':
115                         path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
116                 else:
117                     _name_utf8 = name
118                     if encoding and (encoding != 'utf-8'):
119                         _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
120                     path = '/'.join([_name_utf8] + file['path.utf-8'])
121                     if default_encoding != 'utf-8':
122                         path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
123             else:
124                 if name_utf8:
125                     path = file['path']
126                     if encoding and (encoding != 'utf-8'):
127                         path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
128                     path = '/'.join([name_utf8] + path)
129                     if default_encoding != 'utf-8':
130                         path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
131                 else:
132                     path = '/'.join([name] + file['path'])
133                     if encoding and (default_encoding != encoding):
134                         path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
135             length = file['length']
136             paths.append((path, length))
137     else: # One-file torrent
138         if 'length' not in info:
139             torrent_error('Unknown length')
140         length = info['length']
141         if name_utf8:
142             if default_encoding != 'utf-8':
143                 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
144         elif encoding and (default_encoding != encoding):
145             name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
146         paths = [(name, length)]
147
148     meta = []
149     for name in 'announce', 'announce-list', 'codepage', 'comment', \
150                 'created by', 'creation date', 'encoding', \
151                 'nodes', 'publisher', 'publisher-url':
152         if name == 'comment' and 'comment.utf-8' in torrent:
153             data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
154             meta.append(('.META/' + name, len(data)))
155         elif name in torrent:
156             if name == 'announce-list':
157                 data = decode_announce_list(torrent[name])
158             elif name == 'codepage':
159                 data = str(torrent[name])
160             elif name == 'creation date':
161                 data = decode_datetime(torrent[name])
162             elif name == 'nodes':
163                 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
164                 data = '\n'.join(data)
165             else:
166                 data = torrent[name]
167             meta.append(('.META/' + name, len(data)))
168
169     if 'private' in info:
170         meta.append(('.META/private', 1))
171
172     if 'piece length' in info:
173         meta.append(('.META/piece length', len(str(info['piece length']))))
174
175     for name, size in paths + meta:
176         print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
177
178
179 def mctorrent_copyout():
180     """Extract a file from the VFS"""
181
182     torrent_filename = sys.argv[3]
183     real_filename = sys.argv[4]
184     data = None
185
186     for name in 'announce', 'announce-list', 'codepage', 'comment', \
187                 'created by', 'creation date', 'encoding', \
188                 'nodes', 'publisher', 'publisher-url':
189         if name == 'comment' and 'comment.utf-8' in torrent:
190             data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
191             meta.append(('.META/' + name, len(data)))
192         elif torrent_filename == '.META/' + name:
193             if name in torrent:
194                 if name == 'announce-list':
195                     data = decode_announce_list(torrent[name])
196                 elif name == 'codepage':
197                     data = str(torrent[name])
198                 elif name == 'creation date':
199                     data = decode_datetime(torrent[name])
200                 elif name == 'nodes':
201                     data = ['%s:%s' % (host, port) for host, port in torrent[name]]
202                     data = '\n'.join(data)
203                 else:
204                     data = str(torrent[name])
205             else:
206                 torrent_error('Unknown ' + name)
207             break
208
209     if torrent_filename in ('.META/private', '.META/piece length'):
210         if 'info' not in torrent:
211             torrent_error('Info absent')
212         info = torrent['info']
213         if torrent_filename == '.META/private':
214             if 'private' not in info:
215                 torrent_error('Info absent')
216         if torrent_filename == '.META/piece length':
217             if 'piece length' not in info:
218                 torrent_error('Info absent')
219         data = str(info[torrent_filename[len('.META/'):]])
220
221     if not torrent_filename.startswith('.META/'):
222         data = ''
223
224     if data is None:
225         torrent_error('Unknown file name')
226     else:
227         outfile = open(real_filename, 'w')
228         outfile.write(data)
229         outfile.close()
230
231
232 def mctorrent_copyin():
233     """Put a file to the VFS"""
234     sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
235
236 def mctorrent_rm():
237     """Remove a file from the VFS"""
238     sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
239
240 mctorrent_rmdir = mctorrent_rm
241
242 def mctorrent_mkdir():
243     """Create a directory in the VFS"""
244     sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
245
246
247 def torrent_error(error_str):
248     logger.critical("Error parsing the torrent metafile: %s", error_str)
249     sys.exit(1)
250
251 def decode_torrent():
252     try:
253         torrent_file = open(sys.argv[2], 'r')
254         data = torrent_file.read()
255         torrent_file.close()
256         return decode(data)
257     except IOError, error_str:
258         torrent_error(error_str)
259
260
261 def decode_datetime(dt):
262     from time import localtime, asctime
263     the_time = float(dt)
264     l_now = localtime(the_time)
265     return asctime(l_now)
266
267 def decode_announce_list(announce):
268     return '\n'.join(l[0] for l in announce)
269
270
271 command = sys.argv[1]
272 procname = "mctorrent_" + command
273
274 g = globals()
275 if not g.has_key(procname):
276     logger.critical("Unknown command %s", command)
277     sys.exit(1)
278
279 torrent = decode_torrent()
280
281 try:
282     g[procname]()
283 except SystemExit:
284     raise
285 except:
286     logger.exception("Error during run")