Documentation update.
[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 read-only, of course.
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     for name, size in paths + meta:
129         print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
130
131
132 def mctorrent_copyout():
133     """Extract a file from the VFS"""
134
135     torrent_filename = sys.argv[3]
136     real_filename = sys.argv[4]
137     data = None
138
139     for name in 'comment', 'encoding', 'creation date', 'announce-list', \
140             'created by', 'announce':
141         if torrent_filename == '.META/' + name:
142             if name in torrent:
143                 if name == 'creation date':
144                     data = decode_datetime(torrent[name])
145                 elif name == 'announce-list':
146                     data = decode_list(torrent[name])
147                 else:
148                     data = str(torrent[name])
149             else:
150                 torrent_error('Unknown ' + name)
151             break
152
153     if torrent_filename == '.META/private':
154         if 'info' not in torrent:
155             torrent_error('Info absent')
156         info = torrent['info']
157         if 'private' not in info:
158             torrent_error('Info absent')
159         data = str(info['private'])
160
161     if not torrent_filename.startswith('.META/'):
162         data = ''
163
164     if data is None:
165         torrent_error('Unknown file name')
166     else:
167         outfile = open(real_filename, 'w')
168         outfile.write(data)
169         outfile.close()
170
171
172 def mctorrent_copyin():
173     """Put a file to the VFS"""
174     sys.exit("Torrent VFS doesn't support adding files")
175
176 def mctorrent_rm():
177     """Remove a file from the VFS"""
178     sys.exit("Torrent VFS doesn't support removing files/directories")
179
180 mctorrent_rmdir = mctorrent_rm
181
182 def mctorrent_mkdir():
183     """Create a directory in the VFS"""
184     sys.exit("Torrent VFS doesn't support creating directories")
185
186
187 def torrent_error(error_str):
188     logger.critical("Error parsing the torrent metafile: %s", error_str)
189     sys.exit(1)
190
191 def decode_torrent():
192     try:
193         torrent_file = open(sys.argv[2], 'r')
194         data = torrent_file.read()
195         torrent_file.close()
196         return decode(data)
197     except IOError, error_str:
198         torrent_error(error_str)
199
200
201 def decode_datetime(dt):
202     from time import localtime, asctime
203     the_time = float(dt)
204     l_now = localtime(the_time)
205     return asctime(l_now)
206
207 def decode_list(announce):
208     return '\n'.join(l[0] for l in announce)
209
210
211 command = sys.argv[1]
212 procname = "mctorrent_" + command
213
214 g = globals()
215 if not g.has_key(procname):
216     logger.critical("Unknown command %s", command)
217     sys.exit(1)
218
219 torrent = decode_torrent()
220
221 try:
222     g[procname]()
223 except SystemExit:
224     raise
225 except:
226     logger.exception("Error during run")