1 | import { PageRange } from "../generated/artifacts/models";
|
2 | import {
|
3 | PersistencyPageRange,
|
4 | ZERO_EXTENT_ID
|
5 | } from "../persistence/IBlobMetadataStore";
|
6 | import IPageBlobRangesManager from "./IPageBlobRangesManager";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | export default class PageBlobRangesManager implements IPageBlobRangesManager {
|
51 | public mergeRange(
|
52 | ranges: PersistencyPageRange[],
|
53 | range: PersistencyPageRange
|
54 | ): void {
|
55 | const start = range.start;
|
56 | const end = range.end;
|
57 | const persistency = range.persistency;
|
58 |
|
59 | const impactedScope = this.selectImpactedRanges(ranges, start, end);
|
60 |
|
61 |
|
62 | const impactedStartIndex = impactedScope[0];
|
63 | const impactedEndIndex = impactedScope[1];
|
64 | const impactedRangesCount =
|
65 | impactedStartIndex > impactedEndIndex
|
66 | ? 0
|
67 | : impactedEndIndex - impactedStartIndex + 1;
|
68 |
|
69 |
|
70 | const newRange: PersistencyPageRange = { start, end, persistency };
|
71 |
|
72 | if (impactedRangesCount === 0) {
|
73 |
|
74 | ranges.splice(impactedStartIndex, 0, newRange);
|
75 | } else {
|
76 |
|
77 | const firstImpactedRange = ranges[impactedStartIndex];
|
78 | const lastImpactedRange = ranges[impactedEndIndex];
|
79 |
|
80 |
|
81 | const newRanges = [];
|
82 |
|
83 |
|
84 | if (firstImpactedRange.end >= start && firstImpactedRange.start < start) {
|
85 | newRanges.push({
|
86 | start: firstImpactedRange.start,
|
87 | end: start - 1,
|
88 | persistency: {
|
89 | id: firstImpactedRange.persistency.id,
|
90 | offset: firstImpactedRange.persistency.offset,
|
91 | count: start - firstImpactedRange.start
|
92 | }
|
93 | });
|
94 | }
|
95 |
|
96 | newRanges.push(newRange);
|
97 |
|
98 |
|
99 | if (end >= lastImpactedRange.start && end < lastImpactedRange.end) {
|
100 | newRanges.push({
|
101 | start: end + 1,
|
102 | end: lastImpactedRange.end,
|
103 | persistency: {
|
104 | id: lastImpactedRange.persistency.id,
|
105 | offset:
|
106 | lastImpactedRange.persistency.offset +
|
107 | (end + 1 - lastImpactedRange.start),
|
108 | count: lastImpactedRange.end - end
|
109 | }
|
110 | });
|
111 | }
|
112 |
|
113 | ranges.splice(impactedStartIndex, impactedRangesCount, ...newRanges);
|
114 | }
|
115 | }
|
116 |
|
117 | public clearRange(ranges: PersistencyPageRange[], range: PageRange): void {
|
118 | const start = range.start;
|
119 | const end = range.end;
|
120 |
|
121 |
|
122 | const impactedScope = this.selectImpactedRanges(ranges, start, end);
|
123 |
|
124 |
|
125 | const impactedStartIndex = impactedScope[0];
|
126 | const impactedEndIndex = impactedScope[1];
|
127 | const impactedRangesCount =
|
128 | impactedStartIndex > impactedEndIndex
|
129 | ? 0
|
130 | : impactedEndIndex - impactedStartIndex + 1;
|
131 |
|
132 | if (impactedRangesCount > 0) {
|
133 |
|
134 | const firstImpactedRange = ranges[impactedStartIndex];
|
135 | const lastImpactedRange = ranges[impactedEndIndex];
|
136 |
|
137 |
|
138 | const newRanges = [];
|
139 |
|
140 |
|
141 | if (firstImpactedRange.end >= start && firstImpactedRange.start < start) {
|
142 | newRanges.push({
|
143 | start: firstImpactedRange.start,
|
144 | end: start - 1,
|
145 | persistency: {
|
146 | id: firstImpactedRange.persistency.id,
|
147 | offset: firstImpactedRange.persistency.offset,
|
148 | count: start - firstImpactedRange.start
|
149 | }
|
150 | });
|
151 | }
|
152 |
|
153 |
|
154 | if (end >= lastImpactedRange.start && end < lastImpactedRange.end) {
|
155 | newRanges.push({
|
156 | start: end + 1,
|
157 | end: lastImpactedRange.end,
|
158 | persistency: {
|
159 | id: lastImpactedRange.persistency.id,
|
160 | offset:
|
161 | lastImpactedRange.persistency.offset +
|
162 | (end + 1 - lastImpactedRange.start),
|
163 | count: lastImpactedRange.end - end
|
164 | }
|
165 | });
|
166 | }
|
167 |
|
168 | ranges.splice(impactedStartIndex, impactedRangesCount, ...newRanges);
|
169 | }
|
170 | }
|
171 |
|
172 | |
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | public cutRanges(
|
181 | ranges: PersistencyPageRange[],
|
182 | range: PageRange
|
183 | ): PersistencyPageRange[] {
|
184 | const start = range.start;
|
185 | const end = range.end;
|
186 |
|
187 | const impactedScope = this.selectImpactedRanges(ranges, start, end);
|
188 |
|
189 |
|
190 | const impactedStartIndex = impactedScope[0];
|
191 | const impactedEndIndex = impactedScope[1];
|
192 | const impactedRangesCount =
|
193 | impactedStartIndex > impactedEndIndex
|
194 | ? 0
|
195 | : impactedEndIndex - impactedStartIndex + 1;
|
196 |
|
197 | let impactedRanges: PersistencyPageRange[] = [];
|
198 |
|
199 | if (impactedRangesCount > 0) {
|
200 | impactedRanges = ranges.slice(impactedStartIndex, impactedEndIndex + 1);
|
201 |
|
202 |
|
203 | const firstImpactedRange = impactedRanges[0];
|
204 | if (firstImpactedRange.end >= start && firstImpactedRange.start < start) {
|
205 | impactedRanges[0] = {
|
206 | start,
|
207 | end: firstImpactedRange.end,
|
208 | persistency: {
|
209 | offset:
|
210 | start -
|
211 | firstImpactedRange.start +
|
212 | firstImpactedRange.persistency.offset,
|
213 | count:
|
214 | firstImpactedRange.persistency.count -
|
215 | (start - firstImpactedRange.start),
|
216 | id: firstImpactedRange.persistency.id
|
217 | }
|
218 | };
|
219 | }
|
220 |
|
221 | const lastImpactedRange = impactedRanges[impactedRanges.length - 1];
|
222 |
|
223 | if (end >= lastImpactedRange.start && end < lastImpactedRange.end) {
|
224 | impactedRanges[impactedRanges.length - 1] = {
|
225 | start: lastImpactedRange.start,
|
226 | end,
|
227 | persistency: {
|
228 | offset: lastImpactedRange.persistency.offset,
|
229 | count:
|
230 | lastImpactedRange.persistency.count -
|
231 | (lastImpactedRange.end - end),
|
232 | id: lastImpactedRange.persistency.id
|
233 | }
|
234 | };
|
235 | }
|
236 | }
|
237 |
|
238 | return impactedRanges;
|
239 | }
|
240 |
|
241 | public fillZeroRanges(
|
242 | ranges: PersistencyPageRange[],
|
243 | range: PageRange
|
244 | ): PersistencyPageRange[] {
|
245 | ranges = this.cutRanges(ranges, range);
|
246 | const filledRanges: PersistencyPageRange[] = [];
|
247 |
|
248 | if (ranges.length === 0) {
|
249 | return [
|
250 | {
|
251 | start: range.start,
|
252 | end: range.end,
|
253 | persistency: {
|
254 | offset: 0,
|
255 | count: range.end + 1 - range.start,
|
256 | id: ZERO_EXTENT_ID
|
257 | }
|
258 | }
|
259 | ];
|
260 | }
|
261 |
|
262 | const firstRange = ranges[0];
|
263 | const lastRange = ranges[ranges.length - 1];
|
264 |
|
265 | if (range.start < firstRange.start) {
|
266 | filledRanges.push({
|
267 | start: range.start,
|
268 | end: firstRange.start - 1,
|
269 | persistency: {
|
270 | offset: 0,
|
271 | count: firstRange.start - range.start,
|
272 | id: ZERO_EXTENT_ID
|
273 | }
|
274 | });
|
275 | }
|
276 |
|
277 |
|
278 | for (let i = 0; i < ranges.length - 1; i++) {
|
279 | const nextRange = ranges[i + 1];
|
280 | const currentRange = ranges[i];
|
281 |
|
282 | filledRanges.push(currentRange);
|
283 |
|
284 | const gap = nextRange.start - 1 - currentRange.end;
|
285 | if (gap > 0) {
|
286 | filledRanges.push({
|
287 | start: currentRange.end + 1,
|
288 | end: nextRange.start - 1,
|
289 | persistency: {
|
290 | offset: 0,
|
291 | count: gap,
|
292 | id: ZERO_EXTENT_ID
|
293 | }
|
294 | });
|
295 | }
|
296 | }
|
297 |
|
298 | filledRanges.push(ranges[ranges.length - 1]);
|
299 |
|
300 | if (lastRange.end < range.end) {
|
301 | filledRanges.push({
|
302 | start: lastRange.end + 1,
|
303 | end: range.end,
|
304 | persistency: {
|
305 | offset: 0,
|
306 | count: range.end - lastRange.end,
|
307 | id: ZERO_EXTENT_ID
|
308 | }
|
309 | });
|
310 | }
|
311 |
|
312 | return filledRanges;
|
313 | }
|
314 |
|
315 | |
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 | public selectImpactedRanges(
|
329 | ranges: PersistencyPageRange[],
|
330 | start: number,
|
331 | end: number
|
332 | ): [number, number] {
|
333 | if (ranges.length === 0) {
|
334 | return [Infinity, -1];
|
335 | }
|
336 |
|
337 | if (start > end || start < 0) {
|
338 | throw new RangeError(
|
339 |
|
340 | "PageBlobRangesManager:selectImpactedRanges() start must less equal than end parameter, start must larger equal than 0."
|
341 | );
|
342 | }
|
343 |
|
344 | const impactedRangeStartIndex = this.locateFirstImpactedRange(
|
345 | ranges,
|
346 | 0,
|
347 | ranges.length,
|
348 | start
|
349 | );
|
350 |
|
351 | const impactedRangesEndIndex = this.locateLastImpactedRange(
|
352 | ranges,
|
353 | 0,
|
354 | ranges.length,
|
355 | end
|
356 | );
|
357 |
|
358 | return [impactedRangeStartIndex, impactedRangesEndIndex];
|
359 | }
|
360 |
|
361 | |
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | public locateFirstImpactedRange(
|
373 | ranges: PersistencyPageRange[],
|
374 | searchStart: number,
|
375 | searchEnd: number,
|
376 | position: number
|
377 | ): number {
|
378 | searchStart = searchStart < 0 ? 0 : searchStart;
|
379 | searchEnd = searchEnd > ranges.length ? searchEnd : searchEnd;
|
380 | if (ranges.length === 0 || searchStart >= searchEnd) {
|
381 | return Infinity;
|
382 | }
|
383 |
|
384 |
|
385 | if (searchStart === searchEnd - 1) {
|
386 | return this.positionInRange(ranges[searchStart], position) ||
|
387 | position < ranges[searchStart].start
|
388 | ? searchStart
|
389 | : Infinity;
|
390 | }
|
391 |
|
392 |
|
393 | const searchMid = Math.floor((searchStart + searchEnd) / 2);
|
394 | const indexInLeft = this.locateFirstImpactedRange(
|
395 | ranges,
|
396 | searchStart,
|
397 | searchMid,
|
398 | position
|
399 | );
|
400 | if (indexInLeft !== Infinity) {
|
401 | return indexInLeft;
|
402 | }
|
403 | if (
|
404 | this.positionInRange(ranges[searchMid], position) ||
|
405 | position < ranges[searchMid].start
|
406 | ) {
|
407 | return searchMid;
|
408 | } else {
|
409 | return this.locateFirstImpactedRange(
|
410 | ranges,
|
411 | searchMid + 1,
|
412 | searchEnd,
|
413 | position
|
414 | );
|
415 | }
|
416 | }
|
417 |
|
418 | |
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 | public locateLastImpactedRange(
|
430 | ranges: PersistencyPageRange[],
|
431 | searchStart: number,
|
432 | searchEnd: number,
|
433 | position: number
|
434 | ): number {
|
435 | searchStart = searchStart < 0 ? 0 : searchStart;
|
436 | searchEnd = searchEnd > ranges.length ? searchEnd : searchEnd;
|
437 | if (ranges.length === 0 || searchStart >= searchEnd) {
|
438 | return -1;
|
439 | }
|
440 |
|
441 |
|
442 | if (searchStart === searchEnd - 1) {
|
443 | return this.positionInRange(ranges[searchStart], position) ||
|
444 | position > ranges[searchStart].end
|
445 | ? searchStart
|
446 | : -1;
|
447 | }
|
448 |
|
449 |
|
450 | const searchMid = Math.floor((searchStart + searchEnd) / 2);
|
451 | const indexInRight = this.locateLastImpactedRange(
|
452 | ranges,
|
453 | searchMid + 1,
|
454 | searchEnd,
|
455 | position
|
456 | );
|
457 | if (indexInRight > -1) {
|
458 | return indexInRight;
|
459 | }
|
460 | if (
|
461 | this.positionInRange(ranges[searchMid], position) ||
|
462 | position > ranges[searchMid].end
|
463 | ) {
|
464 | return searchMid;
|
465 | } else {
|
466 | return this.locateLastImpactedRange(
|
467 | ranges,
|
468 | searchStart,
|
469 | searchMid,
|
470 | position
|
471 | );
|
472 | }
|
473 | }
|
474 |
|
475 | public positionInRange(
|
476 | range: PersistencyPageRange,
|
477 | position: number
|
478 | ): boolean {
|
479 | return position >= range.start && position <= range.end;
|
480 | }
|
481 | }
|