Added metafile 'piece length'.
[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.
17
18 Along with the files/directories in the torrent metafile the VFS also presents
19 meta information - in the form of files in .META directory. The size and
20 contents of these files are taken from the corresponding fields in the torrent
21 metafile. The VFS doesn't check if the torrent contains .META file or directory
22 (quite unlikely).
23
24 Date/time for all files is set at midnight of the 1st January of the current
25 year. The filesystem is, naturally, read-only.
26
27 """
28
29 __version__ = "1.0.0"
30 __revision__ = "$Id$"
31 __date__ = "$Date$"
32 __author__ = "Oleg Broytman <phd@phd.pp.ru>"
33 __copyright__ = "Copyright (C) 2010 PhiloSoft Design"
34 __license__ = "GPL"
35
36 import locale, sys, os
37 from tempfile import _candidate_tempdir_list
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 tempdirlist = _candidate_tempdir_list()
60 tempdirlist.insert(0, os.path.abspath(os.path.dirname(sys.argv[2])))
61
62 found = False
63 for tempdir in tempdirlist:
64     try:
65         logfile_name = os.path.join(tempdir, 'torrent-mcextfs.log')
66         logfile = open(logfile_name, 'w')
67     except IOError:
68         pass
69     else:
70         found = True
71         logfile.close()
72         break
73
74 if not found:
75     logger.critical("Cannot initialize error log file in directories %s" % str(tempdirlist))
76     sys.exit(1)
77
78 logger.removeHandler(log_err_handler)
79 logger.addHandler(logging.FileHandler(logfile_name))
80
81 locale.setlocale(locale.LC_ALL, '')
82 charset = locale.getpreferredencoding()
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:
93         torrent_error('Unknown name')
94
95     name = info['name']
96     if 'files' in info:
97         files = info['files']
98         paths = []
99         for file in files:
100             if 'path' not in file:
101                 torrent_error('Unknown path')
102             if 'length' not in file:
103                 torrent_error('Unknown length')
104             path = file['path']
105             length = file['length']
106             paths.append(('/'.join([name] + path), length))
107     else: # One-file torrent
108         if 'length' not in info:
109             torrent_error('Unknown length')
110         length = info['length']
111         paths = [(name, length)]
112
113     meta = []
114     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
115             'created by', 'announce':
116         if name in torrent:
117             if name == 'creation date':
118                 data = decode_datetime(torrent[name])
119             elif name == 'announce-list':
120                 data = decode_list(torrent[name])
121             else:
122                 data = torrent[name]
123             meta.append(('.META/' + name, len(data)))
124
125     if 'private' in info:
126         meta.append(('.META/private', 1))
127
128     if 'piece length' in info:
129         meta.append(('.META/piece length', len(str(info['piece length']))))
130
131     for name, size in paths + meta:
132         print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
133
134
135 def mctorrent_copyout():
136     """Extract a file from the VFS"""
137
138     torrent_filename = sys.argv[3]
139     real_filename = sys.argv[4]
140     data = None
141
142     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
143             'created by', 'announce':
144         if torrent_filename == '.META/' + name:
145             if name in torrent:
146                 if name == 'creation date':
147                     data = decode_datetime(torrent[name])
148                 elif name == 'announce-list':
149                     data = decode_list(torrent[name])
150                 else:
151                     data = str(torrent[name])
152             else:
153                 torrent_error('Unknown ' + name)
154             break
155
156     if torrent_filename in ('.META/private', '.META/piece length'):
157         if 'info' not in torrent:
158             torrent_error('Info absent')
159         info = torrent['info']
160         if torrent_filename == '.META/private':
161             if 'private' not in info:
162                 torrent_error('Info absent')
163         if torrent_filename == '.META/piece length':
164             if 'piece length' not in info:
165                 torrent_error('Info absent')
166         data = str(info[torrent_filename[len('.META/'):]])
167
168     if not torrent_filename.startswith('.META/'):
169         data = ''
170
171     if data is None:
172         torrent_error('Unknown file name')
173     else:
174         outfile = open(real_filename, 'w')
175         outfile.write(data)
176         outfile.close()
177
178
179 def mctorrent_copyin():
180     """Put a file to the VFS"""
181     sys.exit("Torrent VFS doesn't support adding files")
182
183 def mctorrent_rm():
184     """Remove a file from the VFS"""
185     sys.exit("Torrent VFS doesn't support removing files/directories")
186
187 mctorrent_rmdir = mctorrent_rm
188
189 def mctorrent_mkdir():
190     """Create a directory in the VFS"""
191     sys.exit("Torrent VFS doesn't support creating directories")
192
193
194 def torrent_error(error_str):
195     logger.critical("Error parsing the torrent metafile: %s", error_str)
196     sys.exit(1)
197
198 def decode_torrent():
199     try:
200         torrent_file = open(sys.argv[2], 'r')
201         data = torrent_file.read()
202         torrent_file.close()
203         return decode(data)
204     except IOError, error_str:
205         torrent_error(error_str)
206
207
208 def decode_datetime(dt):
209     from time import localtime, asctime
210     the_time = float(dt)
211     l_now = localtime(the_time)
212     return asctime(l_now)
213
214 def decode_list(announce):
215     return '\n'.join(l[0] for l in announce)
216
217
218 command = sys.argv[1]
219 procname = "mctorrent_" + command
220
221 g = globals()
222 if not g.has_key(procname):
223     logger.critical("Unknown command %s", command)
224     sys.exit(1)
225
226 torrent = decode_torrent()
227
228 try:
229     g[procname]()
230 except SystemExit:
231     raise
232 except:
233     logger.exception("Error during run")