]> git.phdru.name Git - extfs.d.git/blob - torrent
514edf344efc8528ec4b982dc90724d25b5d0ebb
[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 For mc 4.7+ run this "cd" command in the Midnight Commander (in the "bindings"
14 file the command is "%cd"): cd file/torrent://; In older versions it is
15 cd file#torrent, where "file" is the name of your torrent metafile. The VFS
16 lists all files and directories from the torrent metafile; all files appear
17 empty, of course, but the sizes are shown. Filenames are reencoded from the
18 metafile's encoding/codepage to the current locale.
19
20 Along with the files/directories in the torrent metafile the VFS also presents
21 meta information - in the form of files in .META directory. The size and
22 contents of these files are taken from the corresponding fields in the torrent
23 metafile. The script doesn't check if the torrent consists of a .META file or
24 directory (quite unlikely).
25
26 Date/time for all files is set to midnight of the 1st January of the current
27 year. The filesystem is, naturally, read-only.
28
29 """
30
31 __version__ = "1.2.0"
32 __author__ = "Oleg Broytman <phd@phdru.name>"
33 __copyright__ = "Copyright (C) 2010-2013 PhiloSoft Design"
34 __license__ = "GPL"
35
36
37 import sys, os
38 from eff_bdecode import decode
39
40 try:
41    import locale
42    use_locale = True
43 except ImportError:
44    use_locale = False
45
46 if use_locale:
47    # Get the default charset.
48    try:
49       lcAll = locale.getdefaultlocale()
50    except locale.Error, err:
51       print >>sys.stderr, "WARNING:", err
52       lcAll = []
53
54    if len(lcAll) == 2:
55       default_encoding = lcAll[1]
56    else:
57       try:
58          default_encoding = locale.getpreferredencoding()
59       except locale.Error, err:
60          print >>sys.stderr, "WARNING:", err
61          default_encoding = sys.getdefaultencoding()
62 else:
63    default_encoding = sys.getdefaultencoding()
64
65 import logging
66 logger = logging.getLogger('torrent-mcextfs')
67 log_err_handler = logging.StreamHandler(sys.stderr)
68 logger.addHandler(log_err_handler)
69 logger.setLevel(logging.INFO)
70
71 if len(sys.argv) < 3:
72     logger.critical("""\
73 Torrent Virtual FileSystem for Midnight Commander version %s
74 Author: %s
75 %s
76
77 This is not a program. Put the script in $HOME/.mc/extfs.d or
78 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
79    __version__, __author__, __copyright__
80 )
81     sys.exit(1)
82
83 locale.setlocale(locale.LC_ALL, '')
84
85
86 def mctorrent_list():
87     """List the entire VFS"""
88
89     if 'info' not in torrent:
90         torrent_error('Info absent')
91
92     info = torrent['info']
93     if 'name' not in info and 'name.utf-8' not in info:
94         torrent_error('Unknown name')
95
96     codepage = torrent.get('codepage', None)
97     encoding = torrent.get('encoding', None)
98     if not encoding and codepage:
99         encoding = str(codepage)
100
101     name = info['name']
102     name_utf8 = info.get('name.utf-8', None)
103
104     if 'files' in info:
105         files = info['files']
106         paths = []
107         for file in files:
108             if 'path' not in file and 'path.utf-8' not in file:
109                 torrent_error('Unknown path')
110             if 'length' not in file:
111                 torrent_error('Unknown length')
112             if 'path.utf-8' in file:
113                 if name_utf8:
114                     path = '/'.join([name_utf8] + file['path.utf-8'])
115                     if default_encoding != 'utf-8':
116                         path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
117                 else:
118                     _name_utf8 = name
119                     if encoding and (encoding != 'utf-8'):
120                         _name_utf8 = _name_utf8.decode(encoding, 'replace').encode('utf-8', 'replace')
121                     path = '/'.join([_name_utf8] + file['path.utf-8'])
122                     if default_encoding != 'utf-8':
123                         path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
124             else:
125                 if name_utf8:
126                     path = file['path']
127                     if encoding and (encoding != 'utf-8'):
128                         path = path.decode(encoding, 'replace').encode('utf-8', 'replace')
129                     path = '/'.join([name_utf8] + path)
130                     if default_encoding != 'utf-8':
131                         path = path.decode('utf-8', 'replace').encode(default_encoding, 'replace')
132                 else:
133                     path = '/'.join([name] + file['path'])
134                     if encoding and (default_encoding != encoding):
135                         path = path.decode(encoding, 'replace').encode(default_encoding, 'replace')
136             length = file['length']
137             paths.append((path, length))
138     else: # One-file torrent
139         if 'length' not in info:
140             torrent_error('Unknown length')
141         length = info['length']
142         if name_utf8:
143             if default_encoding != 'utf-8':
144                 name = name_utf8.decode('utf-8', 'replace').encode(default_encoding, 'replace')
145         elif encoding and (default_encoding != encoding):
146             name = name.decode(encoding, 'replace').encode(default_encoding, 'replace')
147         paths = [(name, length)]
148
149     meta = []
150     for name in 'announce', 'announce-list', 'codepage', 'comment', \
151                 'created by', 'creation date', 'encoding', \
152                 'nodes', 'publisher', 'publisher-url':
153         if name == 'comment' and 'comment.utf-8' in torrent:
154             data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
155             meta.append(('.META/' + name, len(data)))
156         elif name in torrent:
157             if name == 'announce-list':
158                 data = decode_announce_list(torrent[name])
159             elif name == 'codepage':
160                 data = str(torrent[name])
161             elif name == 'creation date':
162                 data = decode_datetime(torrent[name])
163             elif name == 'nodes':
164                 data = ['%s:%s' % (host, port) for host, port in torrent[name]]
165                 data = '\n'.join(data)
166             else:
167                 data = torrent[name]
168             meta.append(('.META/' + name, len(data)))
169
170     if 'private' in info:
171         meta.append(('.META/private', 1))
172
173     if 'piece length' in info:
174         meta.append(('.META/piece length', len(str(info['piece length']))))
175
176     for name, size in paths + meta:
177         print "-r--r--r-- 1 user group %d Jan 1 00:00 %s" % (size, name)
178
179
180 def mctorrent_copyout():
181     """Extract a file from the VFS"""
182
183     torrent_filename = sys.argv[3]
184     real_filename = sys.argv[4]
185     data = None
186
187     for name in 'announce', 'announce-list', 'codepage', 'comment', \
188                 'created by', 'creation date', 'encoding', \
189                 'nodes', 'publisher', 'publisher-url':
190         if name == 'comment' and 'comment.utf-8' in torrent:
191             data = torrent['comment.utf-8'].decode('utf-8').encode(default_encoding, 'replace')
192             meta.append(('.META/' + name, len(data)))
193         elif torrent_filename == '.META/' + name:
194             if name in torrent:
195                 if name == 'announce-list':
196                     data = decode_announce_list(torrent[name])
197                 elif name == 'codepage':
198                     data = str(torrent[name])
199                 elif name == 'creation date':
200                     data = decode_datetime(torrent[name])
201                 elif name == 'nodes':
202                     data = ['%s:%s' % (host, port) for host, port in torrent[name]]
203                     data = '\n'.join(data)
204                 else:
205                     data = str(torrent[name])
206             else:
207                 torrent_error('Unknown ' + name)
208             break
209
210     if torrent_filename in ('.META/private', '.META/piece length'):
211         if 'info' not in torrent:
212             torrent_error('Info absent')
213         info = torrent['info']
214         if torrent_filename == '.META/private':
215             if 'private' not in info:
216                 torrent_error('Info absent')
217         if torrent_filename == '.META/piece length':
218             if 'piece length' not in info:
219                 torrent_error('Info absent')
220         data = str(info[torrent_filename[len('.META/'):]])
221
222     if not torrent_filename.startswith('.META/'):
223         data = ''
224
225     if data is None:
226         torrent_error('Unknown file name')
227     else:
228         outfile = open(real_filename, 'w')
229         outfile.write(data)
230         outfile.close()
231
232
233 def mctorrent_copyin():
234     """Put a file to the VFS"""
235     sys.exit("Torrent VFS doesn't support adding files (read-only filesystem)")
236
237 def mctorrent_rm():
238     """Remove a file from the VFS"""
239     sys.exit("Torrent VFS doesn't support removing files/directories (read-only filesystem)")
240
241 mctorrent_rmdir = mctorrent_rm
242
243 def mctorrent_mkdir():
244     """Create a directory in the VFS"""
245     sys.exit("Torrent VFS doesn't support creating directories (read-only filesystem)")
246
247
248 def torrent_error(error_str):
249     logger.critical("Error parsing the torrent metafile: %s", error_str)
250     sys.exit(1)
251
252 def decode_torrent():
253     try:
254         torrent_file = open(sys.argv[2], 'r')
255         data = torrent_file.read()
256         torrent_file.close()
257         return decode(data)
258     except IOError, error_str:
259         torrent_error(error_str)
260
261
262 def decode_datetime(dt):
263     from time import localtime, asctime
264     the_time = float(dt)
265     l_now = localtime(the_time)
266     return asctime(l_now)
267
268 def decode_announce_list(announce):
269     return '\n'.join(l[0] for l in announce)
270
271
272 command = sys.argv[1]
273 procname = "mctorrent_" + command
274
275 g = globals()
276 if not g.has_key(procname):
277     logger.critical("Unknown command %s", command)
278     sys.exit(1)
279
280 torrent = decode_torrent()
281
282 try:
283     g[procname]()
284 except SystemExit:
285     raise
286 except:
287     logger.exception("Error during run")