text: import random
  
  
  class FileSelector:
      def __init__(self, files, piece_length, bufferdir,
                   storage, storagewrapper, sched, failfunc):
          self.files = files
          self.storage = storage
          self.storagewrapper = storagewrapper
          self.sched = sched
          self.failfunc = failfunc
          self.downloader = None
          self.picker = None
  
          storage.set_bufferdir(bufferdir)
  
          self.numfiles = len(files)
          self.priority = [1] * self.numfiles
          self.new_priority = None
          self.new_partials = None
          self.filepieces = []
          total = 0
          for _, length in files:
              if not length:
                  self.filepieces.append(())
              else:
                  pieces = range(int(total / piece_length),
                                 int((total + length - 1) / piece_length) + 1)
                  self.filepieces.append(tuple(pieces))
                  total += length
          self.numpieces = int((total + piece_length - 1) / piece_length)
          self.piece_priority = [1] * self.numpieces
  
      def init_priority(self, new_priority):
          try:
              assert len(new_priority) == self.numfiles
              for v in new_priority:
                  assert isinstance(v, int)
                  assert v >= -1
                  assert v <= 2
          except AssertionError:
              return False
          try:
              files_updated = False
              for f in range(self.numfiles):
                  if new_priority[f] < 0:
                      self.storage.disable_file(f)
                      files_updated = True
              if files_updated:
                  self.storage.reset_file_status()
              self.new_priority = new_priority
          except (IOError, OSError) as e:
              self.failfunc("can't open partial file for {:s}: {:s}".format(
                            self.files[f][0], e))
              return False
          return True
  
      '''
      d['priority'] = [file #1 priority [,file #2 priority...] ]
                      a list of download priorities for each file.
                      Priority may be -1, 0, 1, 2.  -1 = download disabled,
                      0 = highest, 1 = normal, 2 = lowest.
      Also see Storage.pickle and StorageWrapper.pickle for additional keys.
      '''
      def unpickle(self, d):
          if 'priority' in d and not self.init_priority(d['priority']):
              return
          pieces = self.storage.unpickle(d)
          if not pieces:  # don't bother, nothing restoreable
              return
          new_piece_priority = self._get_piece_priority_list(self.new_priority)
          self.storagewrapper.reblock([i == -1 for i in new_piece_priority])
          self.new_partials = self.storagewrapper.unpickle(d, pieces)
  
      def tie_in(self, picker, cancelfunc, requestmorefunc, rerequestfunc):
          self.picker = picker
          self.cancelfunc = cancelfunc
          self.requestmorefunc = requestmorefunc
          self.rerequestfunc = rerequestfunc
  
          if self.new_priority:
              self.priority = self.new_priority
              self.new_priority = None
              self.new_piece_priority = self._set_piece_priority(self.priority)
  
          if self.new_partials:
              random.shuffle(self.new_partials)
              for p in self.new_partials:
                  self.picker.requested(p)
          self.new_partials = None
  
      def _set_files_disabled(self, old_priority, new_priority):
          old_disabled = [p == -1 for p in old_priority]
          new_disabled = [p == -1 for p in new_priority]
          data_to_update = []
          for f in range(self.numfiles):
              if new_disabled[f] != old_disabled[f]:
                  data_to_update.extend(self.storage.get_piece_update_list(f))
          buffer = []
          for piece, start, length in data_to_update:
              if self.storagewrapper.has_data(piece):
                  data = self.storagewrapper.read_raw(piece, start, length)
                  if data is None:
                      return False
                  buffer.append((piece, start, data))
  
          files_updated = False
          try:
              for f in range(self.numfiles):
                  if new_disabled[f] and not old_disabled[f]:
                      self.storage.disable_file(f)
                      files_updated = True
                  if old_disabled[f] and not new_disabled[f]:
                      self.storage.enable_file(f)
                      files_updated = True
          except (IOError, OSError) as e:
              if new_disabled[f]:
                  msg = "can't open partial file for "
              else:
                  msg = 'unable to open '
              self.failfunc(msg + self.files[f][0] + ': ' + str(e))
              return False
          if files_updated:
              self.storage.reset_file_status()
  
          changed_pieces = set()
          for piece, start, data in buffer:
              if not self.storagewrapper.write_raw(piece, start, data):
                  return False
              data.release()
              changed_pieces.add(piece)
          if not self.storagewrapper.doublecheck_data(changed_pieces):
              return False
  
          return True
  
      def _get_piece_priority_list(self, file_priority_list):
          l = [-1] * self.numpieces
          for f in range(self.numfiles):
              if file_priority_list[f] == -1:
                  continue
              for i in self.filepieces[f]:
                  if l[i] == -1:
                      l[i] = file_priority_list[f]
                      continue
                  l[i] = min(l[i], file_priority_list[f])
          return l
  
      def _set_piece_priority(self, new_priority):
          was_complete = self.storagewrapper.am_I_complete()
          new_piece_priority = self._get_piece_priority_list(new_priority)
          pieces = list(range(self.numpieces))
          random.shuffle(pieces)
          new_blocked = []
          new_unblocked = []
          for piece in pieces:
              self.picker.set_priority(piece, new_piece_priority[piece])
              o = self.piece_priority[piece] == -1
              n = new_piece_priority[piece] == -1
              if n and not o:
                  new_blocked.append(piece)
              if o and not n:
                  new_unblocked.append(piece)
          if new_blocked:
              self.cancelfunc(new_blocked)
          self.storagewrapper.reblock([i == -1 for i in new_piece_priority])
          if new_unblocked:
              self.requestmorefunc(new_unblocked)
          if was_complete and not self.storagewrapper.am_I_complete():
              self.rerequestfunc()
  
          return new_piece_priority
  
      def set_priorities_now(self, new_priority=None):
          if not new_priority:
              new_priority = self.new_priority
              self.new_priority = None    # potential race condition
              if not new_priority:
                  return
          old_priority = self.priority
          self.priority = new_priority
          if not self._set_files_disabled(old_priority, new_priority):
              return
          self.piece_priority = self._set_piece_priority(new_priority)
  
      def set_priorities(self, new_priority):
          self.new_priority = new_priority
          self.sched(self.set_priorities_now)
  
      def set_priority(self, f, p):
          new_priority = self.get_priorities()
          new_priority[f] = p
          self.set_priorities(new_priority)
  
      def get_priorities(self):
          priority = self.new_priority
          if not priority:
              priority = self.priority    # potential race condition
          return [i for i in priority]
  
      def __setitem__(self, index, val):
          self.set_priority(index, val)
  
      def __getitem__(self, index):
          try:
              return self.new_priority[index]
          except TypeError:
              return self.priority[index]
  
      def finish(self):
          for f in range(self.numfiles):
              if self.priority[f] == -1:
                  self.storage.delete_file(f)
  
      def pickle(self):
          d = {'priority': self.priority}
          try:
              s = self.storage.pickle()
              sw = self.storagewrapper.pickle()
              d.update(s)
              d.update(sw)
          except (IOError, OSError):
              pass
          return d
  