lunes, 17 de septiembre de 2012

Lendo un arquivo .torrent [python]

Os arquivos .torrent gardan toda a información necesaria para describir un ou mais arquivos compartidos. A codificación utilizada chámase bencode, e é bastante fácil de ler, imos ver como facelo.


A codificación permite representar catro tipos de datos, números enteiros, cadeas de caracteres (non necesariamente lexibles), listas e diccionarios.

Para saber o tipo de dato que é temos que ler o primer carácter, se é un 'i', e enteiro; 'l', lista; 'd', diccionario; e un número, cadea de caracteres.
def decode_torrent(f):
    c = f.read(1)
    if   c == 'i': return decode_int(f)
    elif c == 'l': return decode_list(f)
    elif c == 'd': return decode_dict(f)
    elif c == 'e': return None # Xa veremos para que funciona :)
    elif c in map(str, range(10)): return decode_string(c, f)
    else: raise Exception('Invalid character ', c)

No caso de ser un enteiro o que atopamos é o número escrito en texto, acabado cunha 'e', por exemplo:
i1234e
Entón, a rutina para decodificalo é simple:
def decode_int(f):
    nums = []
    c = f.read(1)
    while c != 'e':
        nums.append(c)
        c = f.read(1)

    # Xuntamos tódo-los caracteres e convertímolos nun número
    return int(''.join(nums))


No caso dunha lista igual, lemos todos os elementos ata atopar unha 'e', se tiramos uso da función 'decode_torrent' queda ate máis sinxelo:
def decode_list(f):
    l = []
    item = decode_torrent(f)
    while item != None:
        l.append(item)
        item = decode_torrent(f)

    return l


Se o tema a tratar son os diccionarios á complicación tampouco é excesiva, só temos que ler os elementos por pares, o primeiro será o índice e o segundo o valor, se o índice é unha 'e' pois xa non seguimos:
def decode_dict(f):
    d = {}
    key = decode_torrent(f)
    while key != None:
        value = decode_torrent(f)
        d[key] = value

        key = decode_torrent(f)
    return d


Por último, ó ler as cadeas atopamonos primeiro coa súa lonxutide, de novo expresada coma un número en texto e acabado cun ':', so temos que tomar ese número (chamemoslle 'lonx') e ler os seguintes 'lonx' caracteres:
def decode_string(n, f):
    # Lemos o tamanho
    l = [n]
    c = f.read(1)
    while c != ':':
        l.append(c)
        c = f.read(1)

    # Feito iso só queda ler 
    lonxitude = int(''.join(l))
    return f.read(lonxitude)

E xa está, un arquivo .torrent decodificado en menos de 50 liñas de código (formateado a man ;) ):
>>> decode_torrent(open('trisquel_5.5_i686.iso.torrent'))
{'comment': 'Trisquel GNU/Linux 5.5 brigantia. i686 Installable Live CD',
 'info': {'length': 729808896,
          'piece length': 262144, 
          'name': 'trisquel_5.5_i686.iso', 
          'pieces': '(eliminado por brevedade, son os hashes das pezas do arquivo)'}, 
 'creation date': 1334183204,
 'created by': 'mktorrent 1.0',
 'announce': 'http://trisquel.info:6969/announce',
 'url-list': ['http://cdimage.trisquel.info/trisquel-images/trisquel_5.5_i686.iso',
              'http://gdsol.uta.cl/trisquel/iso/trisquel_5.5_i686.iso',
              'http://us.archive.trisquel.info/iso/trisquel_5.5_i686.iso',
              'http://es.gnu.org/~ruben/trisquel/trisquel_5.5_i686.iso',
              'http://ftp.linux.org.tr/trisquel/iso/trisquel_5.5_i686.iso',
              'http://ftp.rediris.es/mirror/Trisquel/iso/trisquel_5.5_i686.iso',
              'http://nl.cdimage.trisquel.info/trisquel_5.5_i686.iso',
              'ftp://in.archive.trisquel.info/trisquel-iso/trisquel_5.5_i686.iso',
              'http://ibelin.mx.gnu.org/trisquel_5.5_i686.iso',
              'http://ftp.linux.org.tr/trisquel/iso/trisquel_5.5_i686.iso']}
>>> 

No hay comentarios:

Publicar un comentario