Пример #1
0
class UnixControlSocket(_ControlSocket):
    def create_socket(self):
        filename = self.socket_filename
        if os.path.exists(filename):
            # If the file already exists, then either the last shutdown was
            # not clean, or there is another Anomos client running.
            try:
                # Check if another client is listening on the socket by
                # trying to send it a command.
                self.send_command('no-op')
            except BTFailure:
                pass
            else:
                raise BTFailure("Could not create control socket: already in use")

            try:
                os.unlink(filename)
            except OSError, e:
                raise BTFailure("Could not remove old control socket filename:"
                                + str(e))
        try:
            self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.socket.setblocking(0)
            self.bind(filename)
        except socket.error, e:
            raise BTFailure("Could not create control socket: "+str(e))
Пример #2
0
 def _restore_state(self):
     def decode_line(line):
         hashtext = line[:40]
         try:
             infohash = hashtext.decode('hex')
         except:
             raise BTFailure("Invalid state file contents")
         if len(infohash) != 20:
             raise BTFailure("Invalid state file contents")
         try:
             path = os.path.join(self.config['data_dir'], 'metainfo',
                                 hashtext)
             f = file(path, 'rb')
             data = f.read()
             f.close()
         except Exception, e:
             try:
                 f.close()
             except:
                 pass
             self.global_error(
                 "ERROR", "Error reading file %s (%s), \
                         cannot restore state completely" % (path, str(e)))
             return None
         if infohash in self.torrents:
             raise BTFailure("Invalid state file (duplicate entry)")
         t = Torrent(infohash)
         self.torrents[infohash] = t
         try:
             t.metainfo = ConvertedMetainfo(bdecode(data))
         except Exception, e:
             self.global_error(
                 "ERROR", "Corrupt data in " + path +
                 " , cannot restore torrent (" + str(e) + ")")
             return None
Пример #3
0
    def run(self, scrwin):
        def reread():
            self.multitorrent.schedule(0, self.reread_config)

        self.d = CursesDisplayer(scrwin, self.errlist, self.doneflag, reread)
        try:
            self.multitorrent = Multitorrent(self.config, self.doneflag)
            # raises BTFailure if bad
            metainfo = ConvertedMetainfo(bdecode(self.metainfo))
            torrent_name = metainfo.name_fs
            if config['save_as']:
                if config['save_in']:
                    raise BTFailure('You cannot specify both --save_as and '
                                    '--save_in')
                saveas = config['save_as']
            elif config['save_in']:
                saveas = os.path.join(config['save_in'], torrent_name)
            else:
                saveas = torrent_name

            self.d.set_torrent_values(metainfo.name, os.path.abspath(saveas),
                                      metainfo.file_size, len(metainfo.hashes))
            self.torrent = self.multitorrent.start_torrent(
                metainfo, self.config, self, saveas)
        except BTFailure, e:
            errlist.append(str(e))
            return
Пример #4
0
 def check_fastresume(self,
                      resumefile,
                      return_filelist=False,
                      piece_size=None,
                      numpieces=None,
                      allfiles=None):
     filenames = [name for _, _, name in self.ranges]
     if resumefile is not None:
         version = resumefile.readline()
         if version != 'Anomos resume state file, version 1\n':
             raise BTFailure('Unsupported fastresume file format, '
                             'maybe from another client version')
         amount_done = int(resumefile.readline())
     else:
         amount_done = size = mtime = 0
     for filename in filenames:
         if resumefile is not None:
             line = resumefile.readline()
             size, mtime = line.split()[:2]  # allow adding extra fields
             size = int(size)
         if os.path.exists(filename):
             fsize = os.path.getsize(filename)
         else:
             fsize = 0
         # Bram cast this to float, then to int for some reason, but that's
         # why this didn't work - leaving them as str makes this actually
         # work. Ass burgers.
         if fsize > 0 and str(mtime) != str(os.path.getmtime(filename)):
             raise BTFailure("Fastresume info doesn't match file "
                             "modification time")
         if size != fsize:
             raise BTFailure("Fastresume data doesn't match actual "
                             "filesize")
     if not return_filelist:
         return amount_done
     if resumefile is None:
         return None
     if numpieces < 32768:
         typecode = 'h'
     else:
         typecode = 'l'
     try:
         r = array(typecode)
         r.fromfile(resumefile, numpieces)
     except Exception, e:
         raise BTFailure("Couldn't read fastresume data: " + str(e))
Пример #5
0
def parse_options(defaults, newvalues):
    for key, value in newvalues.iteritems():
        if not defaults.has_key(key):
            raise BTFailure('unknown key ' + format_key(key))
        try:
            t = type(defaults[key])
            if t is NoneType or t is StringType:
                newvalues[key] = value
            elif t in (IntType, LongType):
                newvalues[key] = int(value)
            elif t is FloatType:
                newvalues[key] = float(value)
            else:
                assert False
        except ValueError, e:
            raise BTFailure('wrong format of %s - %s' %
                            (format_key(key), str(e)))
Пример #6
0
 def read(self, pos, amount):
     r = []
     for filename, pos, end in self._intervals(pos, amount):
         h = self._get_file_handle(filename, False)
         h.seek(pos)
         r.append(h.read(end - pos))
     r = ''.join(r)
     if len(r) != amount:
         raise BTFailure('Short read - something truncated files?')
     return r
Пример #7
0
 def add_files(self, files, torrent):
     for filename in files:
         if filename in self.allfiles:
             raise BTFailure('File ' + filename +
                             ' belongs to another running '
                             'torrent')
     for filename in files:
         self.allfiles[filename] = torrent
     if self.handlebuffer is None and \
            len(self.allfiles) > self.max_files_open:
         self.handlebuffer = self.handles.keys()
Пример #8
0
 def send_command(self, action, data=''):
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     try:
         s.connect(('127.0.0.1', 56881))
         s.send(tobinary(len(action)))
         s.send(action)
         s.send(tobinary(len(data)))
         s.send(data)
         s.close()
     except socket.error, e:
         s.close()
         raise BTFailure('Could not send command: ' + str(e))
Пример #9
0
 def send_command(self, action, data=''):
     s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
     filename = self.socket_filename
     try:
         s.connect(filename)
         s.send(tobinary(len(action)))
         s.send(action)
         s.send(tobinary(len(data)))
         s.send(data)
         s.close()
     except socket.error, e:
         s.close()
         raise BTFailure('Could not send command: ' + str(e))
Пример #10
0
 def decode_line(line):
     hashtext = line[:40]
     try:
         infohash = hashtext.decode('hex')
     except:
         raise BTFailure("Invalid state file contents")
     if len(infohash) != 20:
         raise BTFailure("Invalid state file contents")
     try:
         path = os.path.join(self.config['data_dir'], 'metainfo',
                             hashtext)
         f = file(path, 'rb')
         data = f.read()
         f.close()
     except Exception, e:
         try:
             f.close()
         except:
             pass
         self.global_error(
             "ERROR", "Error reading file %s (%s), \
                     cannot restore state completely" % (path, str(e)))
         return None
Пример #11
0
 def create_socket(self):
    try:
        reuse = True
        tos = 0
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        if reuse and os.name != 'nt':
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.setblocking(0)
        if tos != 0:
           try:
               self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, tos)
           except:
               pass
        self.bind(('127.0.0.1', 56881))
        self.listen(5)
    except socket.error, e:
        raise BTFailure("Could not create control socket: "+str(e))
Пример #12
0
def parseargs(argv, options, minargs=None, maxargs=None, presets=None):
    config = {}
    for option in options:
        longname, default, doc = option
        config[longname] = default
    args = []
    pos = 0
    if presets is None:
        presets = {}
    else:
        presets = presets.copy()
    while pos < len(argv):
        if argv[pos][:1] != '-':  # not a cmdline option
            args.append(argv[pos])
            pos += 1
        else:
            key, value = None, None
            if argv[pos][:2] == '--':  # --aaa 1
                if pos == len(argv) - 1:
                    usage('parameter passed in at end with no value')
                key, value = argv[pos][2:], argv[pos + 1]
                pos += 2
            elif argv[pos][:1] == '-':
                key = argv[pos][1:2]
                if len(argv[pos]) > 2:  # -a1
                    value = argv[pos][2:]
                    pos += 1
                else:  # -a 1
                    if pos == len(argv) - 1:
                        usage('parameter passed in at end with no value')
                    value = argv[pos + 1]
                    pos += 2
            else:
                raise BTFailure('command line parsing failed at ' + argv[pos])

            presets[key] = value
    parse_options(config, presets)
    config.update(presets)
    for key, value in config.items():
        if value is None:
            usage("Option %s is required." % format_key(key))
    if minargs is not None and len(args) < minargs:
        usage("Must supply at least %d args." % minargs)
    if maxargs is not None and len(args) > maxargs:
        usage("Too many args - %d max." % maxargs)
    return (config, args)
Пример #13
0
 def _move_piece(self, oldpos, newpos):
     assert self.rplaces[newpos] < 0
     assert self.rplaces[oldpos] >= 0
     data = self.storage.read(self.piece_size * oldpos,
                              self._piecelen(newpos))
     self.storage.write(self.piece_size * newpos, data)
     if self.rplaces[newpos] == UNALLOCATED:
         self.storage.allocated(self.piece_size * newpos, len(data))
     piece = self.rplaces[oldpos]
     self.places[piece] = newpos
     self.rplaces[oldpos] = ALLOCATED
     self.rplaces[newpos] = piece
     if not self.have[piece]:
         return
     data = data[:self._piecelen(piece)]
     if hashlib.sha1(data).digest() != self.hashes[piece]:
         raise BTFailure('data corrupted on disk - '
                         'maybe you have two copies running?')
Пример #14
0
    def __init__(self, config, filepool, files, check_only=False):
        self.filepool = filepool
        self.config = config
        self.ranges = []
        self.myfiles = {}
        self.tops = {}
        self.undownloaded = {}
        self.unallocated = {}
        total = 0
        for filename, length in files:
            self.unallocated[filename] = length
            self.undownloaded[filename] = length
            if length > 0:
                self.ranges.append((total, total + length, filename))
                self.myfiles[filename] = None
            total += length
            if os.path.exists(filename):
                if not os.path.isfile(filename):
                    raise BTFailure('File ' + filename +
                                    ' already exists, but '
                                    'is not a regular file')
                l = os.path.getsize(filename)
                if l > length and not check_only:
                    h = file(filename, 'rb+')
                    h.truncate(length)
                    h.close()
                    l = length
                self.tops[filename] = l
            elif not check_only:
                f = os.path.split(filename)[0]
                if f != '' and not os.path.exists(f):
                    os.makedirs(f)
                file(filename, 'wb').close()
        self.begins = [i[0] for i in self.ranges]
        self.total_length = total
        if check_only:
            return
        self.handles = filepool.handles
        self.whandles = filepool.whandles

        # Rather implement this as an ugly hack here than change all the
        # individual calls. Affects all torrent instances using this module.
        if config['enable_bad_libc_workaround']:
            bad_libc_workaround()
Пример #15
0
 def handle_accept(self):
     assert self.callback is not None
     try:
         sock, addr = self.socket.accept()
     except socket.error, e:
         raise BTFailure("Could not create control socket: "+str(e))
Пример #16
0
def check_info(info, check_paths=True):
    if type(info) != dict:
        raise BTFailure('bad metainfo - not a dictionary')
    pieces = info.get('pieces')
    if type(pieces) != str or len(pieces) % 20 != 0:
        raise BTFailure('bad metainfo - bad pieces key')
    piecelength = info.get('piece length')
    if type(piecelength) not in ints or piecelength <= 0:
        raise BTFailure('bad metainfo - illegal piece length')
    name = info.get('name')
    if type(name) != str:
        raise BTFailure('bad metainfo - bad name')
    if not allowed_path_re.match(name):
        raise BTFailure('name %s disallowed for security reasons' % name)
    if info.has_key('files') == info.has_key('length'):
        raise BTFailure('single/multiple file mix')
    if info.has_key('length'):
        length = info.get('length')
        if type(length) not in ints or length < 0:
            raise BTFailure('bad metainfo - bad length')
    else:
        files = info.get('files')
        if type(files) != list:
            raise BTFailure('bad metainfo - "files" is not a list of files')
        for f in files:
            if type(f) != dict:
                raise BTFailure('bad metainfo - bad file value')
            length = f.get('length')
            if type(length) not in ints or length < 0:
                raise BTFailure('bad metainfo - bad length')
            path = f.get('path')
            if type(path) != list or path == []:
                raise BTFailure('bad metainfo - bad path')
            for p in path:
                if type(p) != str:
                    raise BTFailure('bad metainfo - bad path dir')
                if check_paths and not allowed_path_re.match(p):
                    raise BTFailure('path %s disallowed for security reasons' %
                                    p)
        f = ['/'.join(x['path']) for x in files]
        f.sort()
        i = iter(f)
        try:
            name2 = i.next()
            while True:
                name1 = name2
                name2 = i.next()
                if name2.startswith(name1):
                    if name1 == name2:
                        raise BTFailure('bad metainfo - duplicate path')
                    elif name2[len(name1)] == '/':
                        raise BTFailure('bad metainfo - name used as both '
                                        'file and subdirectory name')
        except StopIteration:
            pass
Пример #17
0
        if args:
            if config['responsefile']:
                raise BTFailure, 'must have responsefile as arg or ' \
                      'parameter, not both'
            config['responsefile'] = args[0]
        try:
            if config['responsefile']:
                h = file(config['responsefile'], 'rb')
                metainfo = h.read()
                h.close()
            elif config['url']:
                h = urlopen(config['url'])
                metainfo = h.read()
                h.close()
            else:
                raise BTFailure('you need to specify a .torrent file')
        except IOError, e:
            raise BTFailure('Error reading .torrent file: ', str(e))
    except BTFailure, e:
        print str(e)
        sys.exit(1)

    errlist = []
    dl = DL(metainfo, config, errlist)
    curses_wrapper(dl.run)

    if errlist:
        print "These messages were logged during execution:"
        for error in errlist:
            print error
Пример #18
0
    def message(self, s):
        print "### " + s

    def exception(self, s):
        exceptions.append(s)
        self.message('SYSTEM ERROR - EXCEPTION GENERATED')


if __name__ == '__main__':
    uiname = 'anonlaunchmany'
    defaults = get_defaults(uiname)
    try:
        if len(sys.argv) < 2:
            printHelp(uiname, defaults)
            sys.exit(1)
        config, args = configfile.parse_configuration_and_args(
            defaults, uiname, sys.argv[1:], 0, 1)
        if args:
            config['torrent_dir'] = args[0]
        if not os.path.isdir(config['torrent_dir']):
            raise BTFailure("Warning: " + args[0] + " is not a directory")
    except BTFailure, e:
        print 'error: ' + str(
            e) + '\nrun with no args for parameter explanations'
        sys.exit(1)

    LaunchMany(config, HeadlessDisplayer(), 'anonlaunchmany')
    if exceptions:
        print '\nEXCEPTION:'
        print exceptions[0]
Пример #19
0
def check_message(message, check_paths=True):
    if type(message) != dict:
        raise BTFailure('bad metainfo - wrong object type')
    check_info(message.get('info'), check_paths)
    if type(message.get('announce')) != str:
        raise BTFailure('bad metainfo - no announce URL string')
Пример #20
0
 def add_torrent(self, infohash, torrent):
     if infohash in self.torrents:
         raise BTFailure("Can't start two separate instances of the same "
                         "torrent")
     self.torrents[infohash] = torrent
Пример #21
0
                    "ERROR", "Corrupt data in " + path +
                    " , cannot restore torrent (" + str(e) + ")")
                return None
            if len(line) == 41:
                t.dlpath = None
                return infohash, t
            try:
                if version < 2:
                    t.dlpath = line[41:-1].decode('string_escape')
                else:
                    up, down, dlpath = line[41:-1].split(' ', 2)
                    t.uptotal = t.uptotal_old = int(up)
                    t.downtotal = t.downtotal_old = int(down)
                    t.dlpath = dlpath.decode('string_escape')
            except ValueError:  # unpack, int(), decode()
                raise BTFailure('Invalid state file (bad entry)')
            return infohash, t

        filename = os.path.join(self.config['data_dir'], 'ui_state')
        if not os.path.exists(filename):
            return
        f = None
        try:
            f = file(filename, 'rb')
            lines = f.readlines()
            f.close()
        except Exception, e:
            if f is not None:
                f.close()
            raise BTFailure(str(e))
        i = iter(lines)
Пример #22
0
def check_peers(message):
    if type(message) != dict:
        raise BTFailure
    if message.has_key('failure reason'):
        if type(message['failure reason']) != str:
            raise BTFailure('non-text failure reason')
        return
    if message.has_key('warning message'):
        if type(message['warning message']) != str:
            raise BTFailure('non-text warning message')
    peers = message.get('peers', [])
    if type(peers) != list:
        raise BTFailure('invalid peer list')
    for p in peers:
        if type(p) != dict:
            raise BTFailure('invalid entry in peer list')
        if type(p.get('ip')) != str:
            raise BTFailure('invalid entry in peer list')
        port = p.get('port')
        if type(port) not in ints or p <= 0:
            raise BTFailure('invalid entry in peer list')
        if p.has_key('nid'):
            nid = p.get('nid')
            if type(nid) != str or len(nid) != 1:
                raise BTFailure('invalid entry in peer list')
        #PeerID only used in BitTorrent
        #if p.has_key('peer id'):
        #    peerid = p.get('peer id')
        #    if type(peerid) != str or len(peerid) != 20:
        #        raise BTFailure('invalid entry in peer list')
    interval = message.get('interval', 1)
    if type(interval) not in ints or interval <= 0:
        raise BTFailure('invalid announce interval')
    minint = message.get('min interval', 1)
    if type(minint) not in ints or minint <= 0:
        raise BTFailure('invalid min announce interval')
    tcodes = message.get('tracking codes', [])
    if type(tcodes) != list:
        raise BTFailure('invalid tracking code list')
    for t in tcodes:
        if type(t) != list:
            raise BTFailure('invalid entry in tracking code list')
        if len(t) != 2:
            raise BTFailure('invalid tracking code entry length')
        if type(t[0]) != str or type(t[1]) != str:
            raise BTFailure('invalid format for tracking code components')
Пример #23
0
def usage(str):
    raise BTFailure(str)
Пример #24
0
    def __init__(self, storage, config, hashes, piece_size, finished,
            statusfunc, flag, data_flunked, infohash, resumefile):
        self.numpieces = len(hashes)
        self.storage = storage
        self.config = config
        check_hashes = config['check_hashes']
        self.hashes = hashes
        self.piece_size = piece_size
        self.data_flunked = data_flunked
        self.total_length = storage.get_total_length()
        self.amount_left = self.total_length
        self.partial_mark = "Anomos - this part has not been "+\
                            "downloaded yet."+infohash+\
                            tobinary(config['download_slice_size'])
        if self.total_length <= piece_size * (self.numpieces - 1):
            raise BTFailure, 'bad data in responsefile - total too small'
        if self.total_length > piece_size * self.numpieces:
            raise BTFailure, 'bad data in responsefile - total too big'
        self.finished = finished
        self.numactive = array('H', [0] * self.numpieces)
        self.inactive_requests = [1] * self.numpieces
        self.amount_inactive = self.total_length
        self.endgame = False
        self.have = Bitfield(self.numpieces)
        self.waschecked = Bitfield(self.numpieces)
        if self.numpieces < 32768:
            typecode = 'h'
        else:
            typecode = 'l'
        self.places = array(typecode, [NO_PLACE] * self.numpieces)
        if not check_hashes:
            self.rplaces = array(typecode, range(self.numpieces))
            fastresume = True
        else:
            self.rplaces = self._load_fastresume(resumefile, typecode)
            if self.rplaces is not None:
                fastresume = True
            else:
                self.rplaces = array(typecode, [UNALLOCATED] * self.numpieces)
                fastresume = False
        self.holepos = 0
        self.stat_numfound = 0
        self.stat_numflunked = 0
        self.stat_numdownloaded = 0
        self.stat_active = {}
        self.stat_new = {}
        self.stat_dirty = {}
        self.download_history = {}
        self.failed_pieces = {}

        if self.numpieces == 0:
            return
        targets = {}
        total = 0
        if not fastresume:
            for i in xrange(self.numpieces):
                if self._waspre(i):
                    self.rplaces[i] = ALLOCATED
                    total += 1
                else:
                    targets[hashes[i]] = i
        if total and check_hashes:
            statusfunc('checking existing file', 0)
        def markgot(piece, pos):
            if self.have[piece]:
                if piece != pos:
                    return
                self.rplaces[self.places[pos]] = ALLOCATED
                self.places[pos] = self.rplaces[pos] = pos
                return
            self.places[piece] = pos
            self.rplaces[pos] = piece
            self.have[piece] = True
            self.amount_left -= self._piecelen(piece)
            self.amount_inactive -= self._piecelen(piece)
            self.inactive_requests[piece] = None
            if not fastresume:
                self.waschecked[piece] = True
            self.stat_numfound += 1
        lastlen = self._piecelen(self.numpieces - 1)
        partials = {}
        for i in xrange(self.numpieces):
            if not self._waspre(i):
                if self.rplaces[i] != UNALLOCATED:
                    raise BTFailure("--check_hashes 0 or fastresume info "
                                    "doesn't match file state (missing data)")
                continue
            elif fastresume:
                t = self.rplaces[i]
                if t >= 0:
                    markgot(t, i)
                    continue
                if t == UNALLOCATED:
                    raise BTFailure("Bad fastresume info (files contain more "
                                    "data)")
                if t == ALLOCATED:
                    continue
                if t!= FASTRESUME_PARTIAL:
                    raise BTFailure("Bad fastresume info (illegal value)")
                data = self.storage.read(self.piece_size * i,
                                         self._piecelen(i))
                self._check_partial(i, partials, data)
                self.rplaces[i] = ALLOCATED
            else:
                data = self.storage.read(piece_size * i, self._piecelen(i))
                sh = hashlib.sha1(buffer(data, 0, lastlen))
                sp = sh.digest()
                sh.update(buffer(data, lastlen))
                s = sh.digest()
                if s == hashes[i]:
                    markgot(i, i)
                elif s in targets and self._piecelen(i) == self._piecelen(targets[s]):
                    markgot(targets[s], i)
                elif not self.have[self.numpieces - 1] and sp == hashes[-1] and (i == self.numpieces - 1 or not self._waspre(self.numpieces - 1)):
                    markgot(self.numpieces - 1, i)
                else:
                    self._check_partial(i, partials, data)
                statusfunc(fractionDone = 1 - float(self.amount_left) /
                           self.total_length)
            if flag.isSet():
                return
        self.amount_left_with_partials = self.amount_left
        for piece in partials:
            if self.places[piece] < 0:
                pos = partials[piece][0]
                self.places[piece] = pos
                self.rplaces[pos] = piece
                self._make_partial(piece, partials[piece][1])
        for i in xrange(self.numpieces):
            if self.rplaces[i] != UNALLOCATED:
                self.storage.allocated(piece_size * i, self._piecelen(i))
            if self.have[i]:
                self.storage.downloaded(piece_size * i, self._piecelen(i))
Пример #25
0
    def __init__(self, metainfo):
        self.bad_torrent_wrongfield = False
        self.bad_torrent_unsolvable = False
        self.bad_torrent_noncharacter = False
        self.bad_conversion = False
        self.bad_windows = False
        self.bad_path = False
        self.reported_errors = False
        self.is_batch = False
        self.orig_files = None
        self.files_fs = None
        self.file_size = 0
        self.sizes = []
        self.anon = None

        if metainfo.has_key('anon'):
            self.anon = bool(metainfo['anon'])

        btformats.check_message(metainfo, check_paths=False)
        info = metainfo['info']
        if info.has_key('length'):
            self.file_size = info['length']
            self.sizes.append(self.file_size)
        else:
            self.is_batch = True
            r = []
            self.orig_files = []
            self.sizes = []
            i = 0
            for f in info['files']:
                l = f['length']
                self.file_size += l
                self.sizes.append(l)
                path = self._get_attr_utf8(f, 'path')
                for x in path:
                    if not btformats.allowed_path_re.match(x):
                        if l > 0:
                            raise BTFailure("Bad file path component: "+x)
                        # BitComet makes bad .torrent files with empty
                        # filename part
                        self.bad_path = True
                        break
                else:
                    path = [(self._enforce_utf8(x), x) for x in path]
                    self.orig_files.append('/'.join([x[0] for x in path]))
                    r.append(([(self._to_fs_2(u), u, o) for u, o in path], i))
                    i += 1
            # If two or more file/subdirectory names in the same directory
            # would map to the same name after encoding conversions + Windows
            # workarounds, change them. Files are changed as
            # 'a.b.c'->'a.b.0.c', 'a.b.1.c' etc, directories or files without
            # '.' as 'a'->'a.0', 'a.1' etc. If one of the multiple original
            # names was a "clean" conversion, that one is always unchanged
            # and the rest are adjusted.
            r.sort()
            self.files_fs = [None] * len(r)
            prev = [None]
            res = []
            stack = [{}]
            for x in r:
                j = 0
                x, i = x
                while x[j] == prev[j]:
                    j += 1
                del res[j:]
                del stack[j+1:]
                name = x[j][0][1]
                if name in stack[-1]:
                    for name in generate_names(x[j][1], j != len(x) - 1):
                        name = self._to_fs(name)
                        if name not in stack[-1]:
                            break
                stack[-1][name] = None
                res.append(name)
                for j in range(j + 1, len(x)):
                    name = x[j][0][1]
                    stack.append({name: None})
                    res.append(name)
                self.files_fs[i] = os.path.join(*res)
                prev = x

        self.name = self._get_field_utf8(info, 'name')
        self.name_fs = self._to_fs(self.name)
        self.piece_length = info['piece length']
        self.announce = metainfo['announce']
        self.hashes = [info['pieces'][x:x+20] for x in xrange(0,
            len(info['pieces']), 20)]
        self.infohash = hashlib.sha1(bencode(info)).digest()