Filenames are reencoded from the metafile's encoding to the current locale.
[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     if 'files' in info:
75         files = info['files']
76         paths = []
77         encoding = torrent.get('encoding', None)
78         for file in files:
79             if 'path' not in file:
80                 torrent_error('Unknown path')
81             if 'length' not in file:
82                 torrent_error('Unknown length')
83             path = '/'.join([name] + file['path'])
84             if charset and encoding and (charset != encoding):
85                 path = path.decode(encoding, 'replace').encode(charset, 'replace')
86             length = file['length']
87             paths.append((path, length))
88     else: # One-file torrent
89         if 'length' not in info:
90             torrent_error('Unknown length')
91         length = info['length']
92         paths = [(name, length)]
93
94     meta = []
95     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
96             'created by', 'announce':
97         if name in torrent:
98             if name == 'creation date':
99                 data = decode_datetime(torrent[name])
100             elif name == 'announce-list':
101                 data = decode_list(torrent[name])
102             else:
103                 data = torrent[name]
104             meta.append(('.META/' + name, len(data)))
105
106     if 'private' in info:
107         meta.append(('.META/private', 1))
108
109     if 'piece length' in info:
110         meta.append(('.META/piece length', len(str(info['piece length']))))
111
112     for name, size in paths + meta:
113         print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
114
115
116 def mctorrent_copyout():
117     """Extract a file from the VFS"""
118
119     torrent_filename = sys.argv[3]
120     real_filename = sys.argv[4]
121     data = None
122
123     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
124             'created by', 'announce':
125         if torrent_filename == '.META/' + name:
126             if name in torrent:
127                 if name == 'creation date':
128                     data = decode_datetime(torrent[name])
129                 elif name == 'announce-list':
130                     data = decode_list(torrent[name])
131                 else:
132                     data = str(torrent[name])
133             else:
134                 torrent_error('Unknown ' + name)
135             break
136
137     if torrent_filename in ('.META/private', '.META/piece length'):
138         if 'info' not in torrent:
139             torrent_error('Info absent')
140         info = torrent['info']
141         if torrent_filename == '.META/private':
142             if 'private' not in info:
143                 torrent_error('Info absent')
144         if torrent_filename == '.META/piece length':
145             if 'piece length' not in info:
146                 torrent_error('Info absent')
147         data = str(info[torrent_filename[len('.META/'):]])
148
149     if not torrent_filename.startswith('.META/'):
150         data = ''
151
152     if data is None:
153         torrent_error('Unknown file name')
154     else:
155         outfile = open(real_filename, 'w')
156         outfile.write(data)
157         outfile.close()
158
159
160 def mctorrent_copyin():
161     """Put a file to the VFS"""
162     sys.exit("Torrent VFS doesn't support adding files")
163
164 def mctorrent_rm():
165     """Remove a file from the VFS"""
166     sys.exit("Torrent VFS doesn't support removing files/directories")
167
168 mctorrent_rmdir = mctorrent_rm
169
170 def mctorrent_mkdir():
171     """Create a directory in the VFS"""
172     sys.exit("Torrent VFS doesn't support creating directories")
173
174
175 def torrent_error(error_str):
176     logger.critical("Error parsing the torrent metafile: %s", error_str)
177     sys.exit(1)
178
179 def decode_torrent():
180     try:
181         torrent_file = open(sys.argv[2], 'r')
182         data = torrent_file.read()
183         torrent_file.close()
184         return decode(data)
185     except IOError, error_str:
186         torrent_error(error_str)
187
188
189 def decode_datetime(dt):
190     from time import localtime, asctime
191     the_time = float(dt)
192     l_now = localtime(the_time)
193     return asctime(l_now)
194
195 def decode_list(announce):
196     return '\n'.join(l[0] for l in announce)
197
198
199 command = sys.argv[1]
200 procname = "mctorrent_" + command
201
202 g = globals()
203 if not g.has_key(procname):
204     logger.critical("Unknown command %s", command)
205     sys.exit(1)
206
207 torrent = decode_torrent()
208
209 try:
210     g[procname]()
211 except SystemExit:
212     raise
213 except:
214     logger.exception("Error during run")