Blame | Last modification | View Log | Download
# Written by John Hoffman
# see LICENSE.txt for license information
from random import shuffle
from traceback import print_exc
try:
True
except:
True = 1
False = 0
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 = 0L
for file, 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 type(v) in (type(0),type(0L))
assert v >= -1
assert v <= 2
except:
# print_exc()
return False
try:
files_updated = False
for f in xrange(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), e:
self.failfunc("can't open partial file for "
+ self.files[f][0] + ': ' + str(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 d.has_key('priority'):
if 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:
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 xrange(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 xrange(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), 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 = {}
for piece, start, data in buffer:
if not self.storagewrapper.write_raw(piece, start, data):
return False
data.release()
changed_pieces[piece] = 1
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 xrange(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 = range(self.numpieces)
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:
return self.priority[index]
def finish(self):
for f in xrange(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()
for k in s.keys():
d[k] = s[k]
for k in sw.keys():
d[k] = sw[k]
except (IOError, OSError):
pass
return d