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