Merge branch 'master' into 4823-python-sdk-writable-collection-api
[arvados.git] / sdk / python / arvados / ranges.py
1 class Range(object):
2     def __init__(self, locator, range_start, range_size, segment_offset=0):
3         self.locator = locator
4         self.range_start = range_start
5         self.range_size = range_size
6         self.segment_offset = segment_offset
7
8     def __repr__(self):
9         return "Range(\"%s\", %i, %i, %i)" % (self.locator, self.range_start, self.range_size, self.segment_offset)
10
11 def first_block(data_locators, range_start, range_size, debug=False):
12     block_start = 0L
13
14     # range_start/block_start is the inclusive lower bound
15     # range_end/block_end is the exclusive upper bound
16
17     hi = len(data_locators)
18     lo = 0
19     i = int((hi + lo) / 2)
20     block_size = data_locators[i].range_size
21     block_start = data_locators[i].range_start
22     block_end = block_start + block_size
23     if debug: print '---'
24
25     # perform a binary search for the first block
26     # assumes that all of the blocks are contigious, so range_start is guaranteed
27     # to either fall into the range of a block or be outside the block range entirely
28     while not (range_start >= block_start and range_start < block_end):
29         if lo == i:
30             # must be out of range, fail
31             return None
32         if range_start > block_start:
33             lo = i
34         else:
35             hi = i
36         i = int((hi + lo) / 2)
37         if debug: print lo, i, hi
38         block_size = data_locators[i].range_size
39         block_start = data_locators[i].range_start
40         block_end = block_start + block_size
41
42     return i
43
44 class LocatorAndRange(object):
45     def __init__(self, locator, block_size, segment_offset, segment_size):
46         self.locator = locator
47         self.block_size = block_size
48         self.segment_offset = segment_offset
49         self.segment_size = segment_size
50
51     def __eq__(self, other):
52         return  (self.locator == other.locator and
53                  self.block_size == other.block_size and
54                  self.segment_offset == other.segment_offset and
55                  self.segment_size == other.segment_size)
56
57     def __repr__(self):
58         return "LocatorAndRange(\"%s\", %i, %i, %i)" % (self.locator, self.block_size, self.segment_offset, self.segment_size)
59
60 def locators_and_ranges(data_locators, range_start, range_size, debug=False):
61     '''
62     Get blocks that are covered by the range
63     data_locators: list of Range objects, assumes that blocks are in order and contigous
64     range_start: start of range
65     range_size: size of range
66     returns list of LocatorAndRange objects
67     '''
68     if range_size == 0:
69         return []
70     resp = []
71     range_start = long(range_start)
72     range_size = long(range_size)
73     range_end = range_start + range_size
74
75     i = first_block(data_locators, range_start, range_size, debug)
76     if i is None:
77         return []
78
79     while i < len(data_locators):
80         dl = data_locators[i]
81         block_start = dl.range_start
82         block_size = dl.range_size
83         block_end = block_start + block_size
84         if debug:
85             print dl.locator, "range_start", range_start, "block_start", block_start, "range_end", range_end, "block_end", block_end
86         if range_end <= block_start:
87             # range ends before this block starts, so don't look at any more locators
88             break
89
90         #if range_start >= block_end:
91             # range starts after this block ends, so go to next block
92             # we should always start at the first block due to the binary above, so this test is redundant
93             #next
94
95         if range_start >= block_start and range_end <= block_end:
96             # range starts and ends in this block
97             resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset + (range_start - block_start), range_size))
98         elif range_start >= block_start and range_end > block_end:
99             # range starts in this block
100             resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset + (range_start - block_start), block_end - range_start))
101         elif range_start < block_start and range_end > block_end:
102             # range starts in a previous block and extends to further blocks
103             resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset, block_size))
104         elif range_start < block_start and range_end <= block_end:
105             # range starts in a previous block and ends in this block
106             resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset, range_end - block_start))
107         block_start = block_end
108         i += 1
109     return resp
110
111 def replace_range(data_locators, new_range_start, new_range_size, new_locator, new_segment_offset, debug=False):
112     '''
113     Replace a file segment range with a new segment.
114
115     data_locators: list of Range objects, assumes that segments are in order and contigous
116
117     new_range_start: start of range to replace in data_locators
118
119     new_range_size: size of range to replace in data_locators
120
121     new_locator: locator for new segment to be inserted
122
123     new_segment_offset: segment offset within the locator
124
125     debug: print debugging details.
126
127     !!! data_locators will be updated in place !!!
128     '''
129     if new_range_size == 0:
130         return
131
132     new_range_start = long(new_range_start)
133     new_range_size = long(new_range_size)
134     new_range_end = new_range_start + new_range_size
135
136     if len(data_locators) == 0:
137         data_locators.append(Range(new_locator, new_range_start, new_range_size, new_segment_offset))
138         return
139
140     last = data_locators[-1]
141     if (last.range_start+last.range_size) == new_range_start:
142         if last.locator == new_locator:
143             # extend last segment
144             last.range_size += new_range_size
145         else:
146             data_locators.append(Range(new_locator, new_range_start, new_range_size, new_segment_offset))
147         return
148
149     i = first_block(data_locators, new_range_start, new_range_size, debug)
150     if i is None:
151         return
152
153     while i < len(data_locators):
154         dl = data_locators[i]
155         old_segment_start = dl.range_start
156         old_segment_end = old_segment_start + dl.range_size
157         if debug:
158             print locator, "range_start", new_range_start, "segment_start", old_segment_start, "range_end", new_range_end, "segment_end", old_segment_end
159         if new_range_end <= old_segment_start:
160             # range ends before this segment starts, so don't look at any more locators
161             break
162
163         #if range_start >= old_segment_end:
164             # range starts after this segment ends, so go to next segment
165             # we should always start at the first segment due to the binary above, so this test is redundant
166             #next
167
168         if  old_segment_start <= new_range_start and new_range_end <= old_segment_end:
169             # new range starts and ends in old segment
170             # split segment into up to 3 pieces
171             if (new_range_start-old_segment_start) > 0:
172                 data_locators[i] = Range(dl.locator, old_segment_start, (new_range_start-old_segment_start), dl.segment_offset)
173                 data_locators.insert(i+1, Range(new_locator, new_range_start, new_range_size, new_segment_offset))
174             else:
175                 data_locators[i] = Range(new_locator, new_range_start, new_range_size, new_segment_offset)
176                 i -= 1
177             if (old_segment_end-new_range_end) > 0:
178                 data_locators.insert(i+2, Range(dl.locator, new_range_end, (old_segment_end-new_range_end), dl.segment_offset + (new_range_start-old_segment_start) + new_range_size))
179             return
180         elif old_segment_start <= new_range_start and new_range_end > old_segment_end:
181             # range starts in this segment
182             # split segment into 2 pieces
183             data_locators[i] = Range(dl.locator, old_segment_start, (new_range_start-old_segment_start), dl.segment_offset)
184             data_locators.insert(i+1, Range(new_locator, new_range_start, new_range_size, new_segment_offset))
185             i += 1
186         elif new_range_start < old_segment_start and new_range_end >= old_segment_end:
187             # range starts in a previous segment and extends to further segments
188             # delete this segment
189             del data_locators[i]
190             i -= 1
191         elif new_range_start < old_segment_start and new_range_end < old_segment_end:
192             # range starts in a previous segment and ends in this segment
193             # move the starting point of this segment up, and shrink it.
194             data_locators[i] = Range(dl.locator, new_range_end, (old_segment_end-new_range_end), dl.segment_offset + (new_range_end-old_segment_start))
195             return
196         i += 1