Read-only filesystem.
[extfs.d.git] / torrent
1 #! /usr/bin/env python
2
3 """Torrent Virtual FileSystem for Midnight Commander
4
5 The script requires Midnight Commander 3.1+
6 (http://www.midnight-commander.org/), Python 2.4+ (http://www.python.org/),
7 module eff_bdecode.py (http://effbot.org/zone/bencode.htm).
8
9 Put the script in the /usr/[local/][lib|share]/mc/extfs/, and add a line
10 "torrent" to the /usr/[local/][lib|share]/mc/extfs/extfs.ini. Make the script
11 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 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 VFS doesn't check if the torrent contains .META file or directory
23 (quite unlikely).
24
25 Date/time for all files is set at midnight of the 1st January of the current
26 year. The filesystem is, naturally, read-only.
27
28 """
29
30 __version__ = "1.0.0"
31 __revision__ = "$Id$"
32 __date__ = "$Date$"
33 __author__ = "Oleg Broytman <phd@phd.pp.ru>"
34 __copyright__ = "Copyright (C) 2010 PhiloSoft Design"
35 __license__ = "GPL"
36
37 import locale, sys, os
38 from eff_bdecode import decode
39
40 import logging
41 logger = logging.getLogger('torrent-mcextfs')
42 log_err_handler = logging.StreamHandler(sys.stderr)
43 logger.addHandler(log_err_handler)
44 logger.setLevel(logging.INFO)
45
46 if len(sys.argv) < 3:
47     logger.critical("""\
48 Torrent Virtual FileSystem for Midnight Commander version %s
49 Author: %s
50 %s
51
52 This is not a program. Put the script in /usr/[local/][lib|share]/mc/extfs.
53 For more information read the source!""",
54    __version__, __author__, __copyright__
55 )
56     sys.exit(1)
57
58
59 locale.setlocale(locale.LC_ALL, '')
60 charset = locale.getpreferredencoding()
61
62
63 def mctorrent_list():
64     """List the entire VFS"""
65
66     if 'info' not in torrent:
67         torrent_error('Info absent')
68
69     info = torrent['info']
70     if 'name' not in info:
71         torrent_error('Unknown name')
72
73     name = info['name']
74     encoding = torrent.get('encoding', None)
75
76     if 'files' in info:
77         files = info['files']
78         paths = []
79         for file in files:
80             if 'path' not in file:
81                 torrent_error('Unknown path')
82             if 'length' not in file:
83                 torrent_error('Unknown length')
84             path = '/'.join([name] + file['path'])
85             if charset and encoding and (charset != encoding):
86                 path = path.decode(encoding, 'replace').encode(charset, 'replace')
87             length = file['length']
88             paths.append((path, length))
89     else: # One-file torrent
90         if 'length' not in info:
91             torrent_error('Unknown length')
92         length = info['length']
93         if charset and encoding and (charset != encoding):
94             name = name.decode(encoding, 'replace').encode(charset, 'replace')
95         paths = [(name, length)]
96
97     meta = []
98     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
99             'created by', 'announce':
100         if name in torrent:
101             if name == 'creation date':
102                 data = decode_datetime(torrent[name])
103             elif name == 'announce-list':
104                 data = decode_list(torrent[name])
105             else:
106                 data = torrent[name]
107             meta.append(('.META/' + name, len(data)))
108
109     if 'private' in info:
110         meta.append(('.META/private', 1))
111
112     if 'piece length' in info:
113         meta.append(('.META/piece length', len(str(info['piece length']))))
114
115     for name, size in paths + meta:
116         print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
117
118
119 def mctorrent_copyout():
120     """Extract a file from the VFS"""
121
122     torrent_filename = sys.argv[3]
123     real_filename = sys.argv[4]
124     data = None
125
126     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
127             'created by', 'announce':
128         if torrent_filename == '.META/' + name:
129             if name in torrent:
130                 if name == 'creation date':
131                     data = decode_datetime(torrent[name])
132                 elif name == 'announce-list':
133                     data = decode_list(torrent[name])
134                 else:
135                     data = str(torrent[name])
136             else:
137                 torrent_error('Unknown ' + name)
138             break
139
140     if torrent_filename in ('.META/private', '.META/piece length'):
141         if 'info' not in torrent:
142             torrent_error('Info absent')
143         info = torrent['info']
144         if torrent_filename == '.META/private':
145             if 'private' not in info:
146                 torrent_error('Info absent')
147         if torrent_filename == '.META/piece length':
148             if 'piece length' not in info:
149                 torrent_error('Info absent')
150         data = str(info[torrent_filename[len('.META/'):]])
151
152     if not torrent_filename.startswith('.META/'):
153         data = ''
154
155     if data is None:
156         torrent_error('Unknown file name')
157     else:
158         outfile = open(real_filename, 'w')
159         outfile.write(data)
160         outfile.close()
161
162
163 def mctorrent_copyin():
164     """Put a file to the VFS"""
165     sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
166
167 def mctorrent_rm():
168     """Remove a file from the VFS"""
169     sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
170
171 mctorrent_rmdir = mctorrent_rm
172
173 def mctorrent_mkdir():
174     """Create a directory in the VFS"""
175     sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
176
177
178 def torrent_error(error_str):
179     logger.critical("Error parsing the torrent metafile: %s", error_str)
180     sys.exit(1)
181
182 def decode_torrent():
183     try:
184         torrent_file = open(sys.argv[2], 'r')
185         data = torrent_file.read()
186         torrent_file.close()
187         return decode(data)
188     except IOError, error_str:
189         torrent_error(error_str)
190
191
192 def decode_datetime(dt):
193     from time import localtime, asctime
194     the_time = float(dt)
195     l_now = localtime(the_time)
196     return asctime(l_now)
197
198 def decode_list(announce):
199     return '\n'.join(l[0] for l in announce)
200
201
202 command = sys.argv[1]
203 procname = "mctorrent_" + command
204
205 g = globals()
206 if not g.has_key(procname):
207     logger.critical("Unknown command %s", command)
208     sys.exit(1)
209
210 torrent = decode_torrent()
211
212 try:
213     g[procname]()
214 except SystemExit:
215     raise
216 except:
217     logger.exception("Error during run")