Subversion Repositories svnkaklik

Rev

Details | Last modification | View Log

Rev Author Line No. Line
36 kaklik 1
# Written by John Hoffman
2
# see LICENSE.txt for license information
3
 
4
from random import shuffle
5
from traceback import print_exc
6
try:
7
    True
8
except:
9
    True = 1
10
    False = 0
11
 
12
 
13
class FileSelector:
14
    def __init__(self, files, piece_length, bufferdir,
15
                 storage, storagewrapper, sched, failfunc):
16
        self.files = files
17
        self.storage = storage
18
        self.storagewrapper = storagewrapper
19
        self.sched = sched
20
        self.failfunc = failfunc
21
        self.downloader = None
22
        self.picker = None
23
 
24
        storage.set_bufferdir(bufferdir)
25
 
26
        self.numfiles = len(files)
27
        self.priority = [1] * self.numfiles
28
        self.new_priority = None
29
        self.new_partials = None
30
        self.filepieces = []
31
        total = 0L
32
        for file, length in files:
33
            if not length:
34
                self.filepieces.append(())
35
            else:
36
                pieces = range( int(total/piece_length),
37
                                int((total+length-1)/piece_length)+1 )
38
                self.filepieces.append(tuple(pieces))
39
                total += length
40
        self.numpieces = int((total+piece_length-1)/piece_length)
41
        self.piece_priority = [1] * self.numpieces
42
 
43
 
44
 
45
    def init_priority(self, new_priority):
46
        try:
47
            assert len(new_priority) == self.numfiles
48
            for v in new_priority:
49
                assert type(v) in (type(0),type(0L))
50
                assert v >= -1
51
                assert v <= 2
52
        except:
53
#           print_exc()            
54
            return False
55
        try:
56
            files_updated = False
57
            for f in xrange(self.numfiles):
58
                if new_priority[f] < 0:
59
                    self.storage.disable_file(f)
60
                    files_updated = True
61
            if files_updated:
62
                self.storage.reset_file_status()
63
            self.new_priority = new_priority
64
        except (IOError, OSError), e:
65
            self.failfunc("can't open partial file for "
66
                          + self.files[f][0] + ': ' + str(e))
67
            return False
68
        return True
69
 
70
    '''
71
    d['priority'] = [file #1 priority [,file #2 priority...] ]
72
                    a list of download priorities for each file.
73
                    Priority may be -1, 0, 1, 2.  -1 = download disabled,
74
 
75
    Also see Storage.pickle and StorageWrapper.pickle for additional keys.
76
    '''
77
    def unpickle(self, d):
78
        if d.has_key('priority'):
79
            if not self.init_priority(d['priority']):
80
                return
81
        pieces = self.storage.unpickle(d)
82
        if not pieces:  # don't bother, nothing restoreable
83
            return
84
        new_piece_priority = self._get_piece_priority_list(self.new_priority)
85
        self.storagewrapper.reblock([i == -1 for i in new_piece_priority])
86
        self.new_partials = self.storagewrapper.unpickle(d, pieces)
87
 
88
 
89
    def tie_in(self, picker, cancelfunc, requestmorefunc, rerequestfunc):
90
        self.picker = picker
91
        self.cancelfunc = cancelfunc
92
        self.requestmorefunc = requestmorefunc
93
        self.rerequestfunc = rerequestfunc
94
 
95
        if self.new_priority:
96
            self.priority = self.new_priority
97
            self.new_priority = None
98
            self.new_piece_priority = self._set_piece_priority(self.priority)
99
 
100
        if self.new_partials:
101
            shuffle(self.new_partials)
102
            for p in self.new_partials:
103
                self.picker.requested(p)
104
        self.new_partials = None
105
 
106
 
107
    def _set_files_disabled(self, old_priority, new_priority):
108
        old_disabled = [p == -1 for p in old_priority]
109
        new_disabled = [p == -1 for p in new_priority]
110
        data_to_update = []
111
        for f in xrange(self.numfiles):
112
            if new_disabled[f] != old_disabled[f]:
113
                data_to_update.extend(self.storage.get_piece_update_list(f))
114
        buffer = []
115
        for piece, start, length in data_to_update:
116
            if self.storagewrapper.has_data(piece):
117
                data = self.storagewrapper.read_raw(piece, start, length)
118
                if data is None:
119
                    return False
120
                buffer.append((piece, start, data))
121
 
122
        files_updated = False        
123
        try:
124
            for f in xrange(self.numfiles):
125
                if new_disabled[f] and not old_disabled[f]:
126
                    self.storage.disable_file(f)
127
                    files_updated = True
128
                if old_disabled[f] and not new_disabled[f]:
129
                    self.storage.enable_file(f)
130
                    files_updated = True
131
        except (IOError, OSError), e:
132
            if new_disabled[f]:
133
                msg = "can't open partial file for "
134
            else:
135
                msg = 'unable to open '
136
            self.failfunc(msg + self.files[f][0] + ': ' + str(e))
137
            return False
138
        if files_updated:
139
            self.storage.reset_file_status()
140
 
141
        changed_pieces = {}
142
        for piece, start, data in buffer:
143
            if not self.storagewrapper.write_raw(piece, start, data):
144
                return False
145
            data.release()
146
            changed_pieces[piece] = 1
147
        if not self.storagewrapper.doublecheck_data(changed_pieces):
148
            return False
149
 
150
        return True        
151
 
152
 
153
    def _get_piece_priority_list(self, file_priority_list):
154
        l = [-1] * self.numpieces
155
        for f in xrange(self.numfiles):
156
            if file_priority_list[f] == -1:
157
                continue
158
            for i in self.filepieces[f]:
159
                if l[i] == -1:
160
                    l[i] = file_priority_list[f]
161
                    continue
162
                l[i] = min(l[i],file_priority_list[f])
163
        return l
164
 
165
 
166
    def _set_piece_priority(self, new_priority):
167
        was_complete = self.storagewrapper.am_I_complete()
168
        new_piece_priority = self._get_piece_priority_list(new_priority)
169
        pieces = range(self.numpieces)
170
        shuffle(pieces)
171
        new_blocked = []
172
        new_unblocked = []
173
        for piece in pieces:
174
            self.picker.set_priority(piece,new_piece_priority[piece])
175
            o = self.piece_priority[piece] == -1
176
            n = new_piece_priority[piece] == -1
177
            if n and not o:
178
                new_blocked.append(piece)
179
            if o and not n:
180
                new_unblocked.append(piece)
181
        if new_blocked:
182
            self.cancelfunc(new_blocked)
183
        self.storagewrapper.reblock([i == -1 for i in new_piece_priority])
184
        if new_unblocked:
185
            self.requestmorefunc(new_unblocked)
186
        if was_complete and not self.storagewrapper.am_I_complete():
187
            self.rerequestfunc()
188
 
189
        return new_piece_priority        
190
 
191
 
192
    def set_priorities_now(self, new_priority = None):
193
        if not new_priority:
194
            new_priority = self.new_priority
195
            self.new_priority = None    # potential race condition
196
            if not new_priority:
197
                return
198
        old_priority = self.priority
199
        self.priority = new_priority
200
        if not self._set_files_disabled(old_priority, new_priority):
201
            return
202
        self.piece_priority = self._set_piece_priority(new_priority)
203
 
204
    def set_priorities(self, new_priority):
205
        self.new_priority = new_priority
206
        self.sched(self.set_priorities_now)
207
 
208
    def set_priority(self, f, p):
209
        new_priority = self.get_priorities()
210
        new_priority[f] = p
211
        self.set_priorities(new_priority)
212
 
213
    def get_priorities(self):
214
        priority = self.new_priority
215
        if not priority:
216
            priority = self.priority    # potential race condition
217
        return [i for i in priority]
218
 
219
    def __setitem__(self, index, val):
220
        self.set_priority(index, val)
221
 
222
    def __getitem__(self, index):
223
        try:
224
            return self.new_priority[index]
225
        except:
226
            return self.priority[index]
227
 
228
 
229
    def finish(self):
230
        for f in xrange(self.numfiles):
231
            if self.priority[f] == -1:
232
                self.storage.delete_file(f)
233
 
234
    def pickle(self):
235
        d = {'priority': self.priority}
236
        try:
237
            s = self.storage.pickle()
238
            sw = self.storagewrapper.pickle()
239
            for k in s.keys():
240
                d[k] = s[k]
241
            for k in sw.keys():
242
                d[k] = sw[k]
243
        except (IOError, OSError):
244
            pass
245
        return d