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