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