Ticket #3100: 0001-extfs-Add-support-for-BitTorrent-file-browse.patch

File 0001-extfs-Add-support-for-BitTorrent-file-browse.patch, 8.7 KB (added by artemsen, 19 months ago)
  • misc/mc.ext.in

    From 8b5baaf585f82d44005187b36211b2f75d3a2c02 Mon Sep 17 00:00:00 2001
    From: Artem Senichev <artemsen@gmail.com>
    Date: Thu, 14 Mar 2019 12:28:22 +0300
    Subject: [PATCH] extfs: Add support for BitTorrent file browse
    
    Signed-off-by: Artem Senichev <artemsen@gmail.com>
    ---
     misc/mc.ext.in                    |   1 +
     src/vfs/extfs/helpers/Makefile.am |   2 +-
     src/vfs/extfs/helpers/torrent     | 216 ++++++++++++++++++++++++++++++
     3 files changed, 218 insertions(+), 1 deletion(-)
     create mode 100755 src/vfs/extfs/helpers/torrent
    
    diff --git a/misc/mc.ext.in b/misc/mc.ext.in
    index 71cc003e6..05290cea7 100644
    a b shell/i/.lyx 
    718718 
    719719# torrent 
    720720shell/i/.torrent 
     721        Open=%cd %p/torrent:// 
    721722        View=%view{ascii} @EXTHELPERSDIR@/misc.sh view torrent 
    722723 
    723724 
  • src/vfs/extfs/helpers/Makefile.am

    diff --git a/src/vfs/extfs/helpers/Makefile.am b/src/vfs/extfs/helpers/Makefile.am
    index 06ea7898d..5bd5e5c60 100644
    a b EXTFSCONFFILES = sfs.ini 
    66EXTFS_MISC  = README README.extfs 
    77 
    88# Scripts hat don't need adaptation to the local system 
    9 EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm u7z 
     9EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm torrent trpm u7z 
    1010 
    1111# Scripts that need adaptation to the local system - source files 
    1212EXTFS_IN    =                   \ 
  • new file src/vfs/extfs/helpers/torrent

    diff --git a/src/vfs/extfs/helpers/torrent b/src/vfs/extfs/helpers/torrent
    new file mode 100755
    index 000000000..94554c539
    - +  
     1#!/usr/bin/env python3 
     2 
     3# Midnight Commander - extFS extension to explore BitTorrent files 
     4# 
     5# Copyright (C) 2019 
     6# The Free Software Foundation, Inc. 
     7# 
     8# Written by: 
     9#  Artem Senichev <artemsen@gmail.com>, 2019 
     10# 
     11# This file is part of the Midnight Commander. 
     12# 
     13# The Midnight Commander is free software: you can redistribute it 
     14# and/or modify it under the terms of the GNU General Public License as 
     15# published by the Free Software Foundation, either version 3 of the License, 
     16# or (at your option) any later version. 
     17# 
     18# The Midnight Commander is distributed in the hope that it will be useful, 
     19# but WITHOUT ANY WARRANTY; without even the implied warranty of 
     20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     21# GNU General Public License for more details. 
     22# 
     23# You should have received a copy of the GNU General Public License 
     24# along with this program.  If not, see <http://www.gnu.org/licenses/>. 
     25 
     26import os 
     27import sys 
     28import time 
     29import json 
     30 
     31 
     32class BtDecoder(object): 
     33    """ 
     34    Decoder for BitTorrent files. 
     35    """ 
     36 
     37    # Predefined node names 
     38    INFO_DICT = 'info' 
     39    FILES_DICT = 'files' 
     40    FILE_PATH = 'path' 
     41    FILE_SIZE = 'length' 
     42    TORR_NAME = 'name' 
     43    PIECES_NODE = 'pieces' 
     44    CREATION_DATE = 'creation date' 
     45 
     46    class FormatError(Exception): 
     47        """ 
     48        BitTorrent file format exception. 
     49        """ 
     50        def __init__(self, msg, offset=-1, dump=None): 
     51            self.msg = msg 
     52            self.offset = offset 
     53            self.dump = dump 
     54 
     55    def __init__(self, file): 
     56        """ 
     57        Constructor. 
     58        :param file: BitTorrent file to open (*.torrent) 
     59        """ 
     60        with open(file, 'rb') as f: 
     61            self._data = f.read() 
     62        self._offset = 0 
     63        self._root = self._decode() 
     64 
     65    def file_list(self): 
     66        """ 
     67        Print file list in format compatible with Midnight Commander. 
     68        """ 
     69        if BtDecoder.INFO_DICT not in self._root: 
     70            raise BtDecoder.FormatError('Info block not found') 
     71        info_dict = self._root[BtDecoder.INFO_DICT] 
     72        # Build the file list 
     73        file_list = [] 
     74        if BtDecoder.FILES_DICT in info_dict: 
     75            files_dict = info_dict[BtDecoder.FILES_DICT] 
     76            for file in files_dict: 
     77                if BtDecoder.FILE_PATH in file: 
     78                    path = '/'.join(file[BtDecoder.FILE_PATH]) 
     79                    file_list.append((path, file.get(BtDecoder.FILE_SIZE, 0))) 
     80        elif BtDecoder.TORR_NAME in info_dict: 
     81            file_list.append((info_dict[BtDecoder.TORR_NAME], info_dict.get(BtDecoder.FILE_SIZE, 0))) 
     82 
     83        # Use torrent creation date as file time 
     84        if BtDecoder.CREATION_DATE in self._root: 
     85            ts = time.localtime(self._root[BtDecoder.CREATION_DATE]) 
     86        else: 
     87            ts = time.localtime() 
     88        ft = '{:02}-{:02}-{:04} {:02}:{:02}:{:02}'.format(ts.tm_mon, ts.tm_mday, ts.tm_year, 
     89                                                          ts.tm_hour, ts.tm_min, ts.tm_sec) 
     90        # Use current UID/GID as file owner 
     91        uid = os.getuid() 
     92        gid = os.getgid() 
     93 
     94        for path, size in file_list: 
     95            print('-rw-r--r-- 1 {:3} {:3} {} {} {}'.format(uid, gid, size, ft, path)) 
     96 
     97    def dump(self): 
     98        """ 
     99        Print json dump of torrent file content. 
     100        """ 
     101        print(json.dumps(self._root, indent=2)) 
     102 
     103    def _decode(self): 
     104        """ 
     105        Decode next node from buffer. 
     106        :return: node instance 
     107        """ 
     108        type_id = self._data[self._offset] 
     109        if ord('0') <= type_id <= ord('9'): 
     110            node = self._decode_string() 
     111        elif type_id == ord('i'): 
     112            node = self._decode_integer() 
     113        elif type_id == ord('l'): 
     114            node = self._decode_list() 
     115        elif type_id == ord('d'): 
     116            node = self._decode_dict() 
     117        else: 
     118            raise BtDecoder.FormatError('Invalid node type', self._offset, 
     119                                        self._data[self._offset:self._offset + 8]) 
     120        return node 
     121 
     122    def _decode_string(self): 
     123        """ 
     124        Node decoder - text string. 
     125        """ 
     126        try: 
     127            delimiter = self._data.index(ord(':'), self._offset) 
     128            length = int(self._data[self._offset:delimiter].decode('utf8', 'ignore')) 
     129            node = self._data[delimiter + 1:delimiter + 1 + length].decode('utf8', 'ignore') 
     130            self._offset = delimiter + length + 1 
     131            return node 
     132        except Exception: 
     133            raise BtDecoder.FormatError('Unable to decode string node', self._offset, 
     134                                        self._data[self._offset:self._offset + 8]) 
     135 
     136    def _decode_integer(self): 
     137        """ 
     138        Node decoder - integer value. 
     139        """ 
     140        try: 
     141            self._offset += 1 
     142            delimiter = self._data.index(ord('e'), self._offset) 
     143            node = int(self._data[self._offset:delimiter].decode('utf8', 'ignore')) 
     144            self._offset = delimiter + 1 
     145            return node 
     146        except Exception: 
     147            raise BtDecoder.FormatError('Unable to decode integer node', self._offset, 
     148                                        self._data[self._offset:self._offset + 8]) 
     149 
     150    def _decode_list(self): 
     151        """ 
     152        Node decoder - list. 
     153        """ 
     154        self._offset += 1 
     155        node = [] 
     156        try: 
     157            while self._offset < len(self._data) and self._data[self._offset] != ord('e'): 
     158                node.append(self._decode()) 
     159            self._offset += 1 
     160        except Exception: 
     161            # Stop further processing 
     162            self._offset = len(self._data) 
     163        return node 
     164 
     165    def _decode_dict(self): 
     166        """ 
     167        Node decoder - dictionary. 
     168        """ 
     169        self._offset += 1 
     170        node = {} 
     171        try: 
     172            while self._offset < len(self._data) and self._data[self._offset] != ord('e'): 
     173                key = self._decode_string() 
     174                val = self._decode() 
     175                if key == BtDecoder.PIECES_NODE: 
     176                    val = '<Binary data>' 
     177                node[key] = val 
     178            self._offset += 1 
     179        except Exception: 
     180            # Stop further processing 
     181            self._offset = len(self._data) 
     182        return node 
     183 
     184 
     185def main(): 
     186    """ 
     187    Main entry point. 
     188    :return: exit code, 0 if operation was completed successfully 
     189    """ 
     190    if len(sys.argv) < 3 or '-h' in sys.argv or '--help' in sys.argv: 
     191        print('Use: {} list|view FILE'.format(sys.argv[0])) 
     192        return 1 
     193    try: 
     194        if sys.argv[1] == 'list': 
     195            BtDecoder(sys.argv[2]).file_list() 
     196        elif sys.argv[1] == 'view': 
     197            BtDecoder(sys.argv[2]).dump() 
     198        else: 
     199            return 1 
     200        return 0 
     201    except BtDecoder.FormatError as e: 
     202        print('Invalid torrent file format!', file=sys.stderr) 
     203        print(e.msg, file=sys.stderr) 
     204        if e.offset >= 0: 
     205            msg = 'Offset: 0x{:02x}'.format(e.offset) 
     206            if e.dump: 
     207                msg += ' ' + ''.join(' {:02x}'.format(x) for x in e.dump) 
     208            print(msg, file=sys.stderr) 
     209        return 1 
     210    except Exception as e: 
     211        print(str(e), file=sys.stderr) 
     212        return 1 
     213 
     214 
     215if __name__ == '__main__': 
     216    exit(main())