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