Ticket #3100: ext_embedded_torrent.patch

File ext_embedded_torrent.patch, 11.4 KB (added by eugenesan, 7 years ago)

Embed torrent viewer/vfs

  • new file src/vfs/extfs/helpers/torrent

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

    a b shell/i/.lyx 
    668668 
    669669# torrent 
    670670shell/i/.torrent 
     671        Open=%cd %p/torrent:// 
    671672        View=%view{ascii} @EXTHELPERSDIR@/misc.sh view torrent 
    672673 
    673674### Plain compressed files ### 
  • misc/ext.d/misc.sh.in

    a b do_view_action() { 
    4545        lyxcat "${MC_EXT_FILENAME}" 
    4646        ;; 
    4747    torrent) 
    48         ctorrent -x "${MC_EXT_FILENAME}" 2>/dev/null 
     48        @EXTHELPERSDIR@/../extfs.d/torrent view "${MC_EXT_FILENAME}" 2>/dev/null 
    4949        ;; 
    5050    javaclass) 
    5151        jad -p "${MC_EXT_FILENAME}" 2>/dev/null