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