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