Review fixes: the database restore also reads a damaged blob once more; schedule conversion right on days the clocks change; the field follows a changed schedule time zone
This commit is contained in:
@@ -845,7 +845,13 @@ final class Repository {
|
|||||||
try {
|
try {
|
||||||
foreach ($this->backend->getRanges($ranges) as $i => $data) {
|
foreach ($this->backend->getRanges($ranges) as $i => $data) {
|
||||||
foreach ($parts[$i] as [$id, $at, $length]) {
|
foreach ($parts[$i] as [$id, $at, $length]) {
|
||||||
yield $this->decodeBlob($id, substr($data, $at, $length));
|
try {
|
||||||
|
$blob = $this->decodeBlob($id, substr($data, $at, $length));
|
||||||
|
} catch (\Exception) {
|
||||||
|
// a read that went wrong on the way (see loadBlob()): this blob on its own
|
||||||
|
$blob = $this->loadBlob($id);
|
||||||
|
}
|
||||||
|
yield $blob;
|
||||||
$done++;
|
$done++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const browserZone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
|
|||||||
const shown = (s) => (s && props.status.scheduleTimezone && props.status.scheduleTimezone !== browserZone) ? toZone(s, props.status.scheduleTimezone, browserZone) : s
|
const shown = (s) => (s && props.status.scheduleTimezone && props.status.scheduleTimezone !== browserZone) ? toZone(s, props.status.scheduleTimezone, browserZone) : s
|
||||||
const schedule = ref(shown(props.status.schedule))
|
const schedule = ref(shown(props.status.schedule))
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
watch(() => props.status.schedule, (s) => { schedule.value = shown(s) })
|
watch(() => [props.status.schedule, props.status.scheduleTimezone], ([s]) => { schedule.value = shown(s) })
|
||||||
|
|
||||||
const trash = reactive({})
|
const trash = reactive({})
|
||||||
async function loadTrash() {
|
async function loadTrash() {
|
||||||
|
|||||||
+12
-6
@@ -33,17 +33,23 @@ export function formatSize(bytes) {
|
|||||||
* @param {string} hhmm the time
|
* @param {string} hhmm the time
|
||||||
* @param {string} from IANA zone it is in
|
* @param {string} from IANA zone it is in
|
||||||
* @param {string} to IANA zone to show it in
|
* @param {string} to IANA zone to show it in
|
||||||
|
* @param {Date} now the day to convert on (today)
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export function toZone(hhmm, from, to) {
|
export function toZone(hhmm, from, to, now = new Date()) {
|
||||||
const [h, m] = hhmm.split(':').map(Number)
|
const [h, m] = hhmm.split(':').map(Number)
|
||||||
const parts = (zone, date) => Object.fromEntries(new Intl.DateTimeFormat('en-GB', {
|
const parts = (zone, date) => Object.fromEntries(new Intl.DateTimeFormat('en-GB', {
|
||||||
timeZone: zone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
timeZone: zone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
||||||
}).formatToParts(date).map((p) => [p.type, p.value]))
|
}).formatToParts(date).map((p) => [p.type, p.value]))
|
||||||
const today = parts(from, new Date())
|
const offsetAt = (at) => {
|
||||||
const guess = Date.UTC(+today.year, +today.month - 1, +today.day, h, m)
|
const seen = parts(from, new Date(at))
|
||||||
const seen = parts(from, new Date(guess))
|
return Date.UTC(+seen.year, +seen.month - 1, +seen.day, +seen.hour, +seen.minute) - at
|
||||||
const offset = Date.UTC(+seen.year, +seen.month - 1, +seen.day, +seen.hour, +seen.minute) - guess
|
}
|
||||||
const there = parts(to, new Date(guess - offset))
|
const today = parts(from, now)
|
||||||
|
const wall = Date.UTC(+today.year, +today.month - 1, +today.day, h, m)
|
||||||
|
// the offset at the wall time itself: on a day the clocks change it differs from the one at
|
||||||
|
// midnight, so it is looked up a second time at the moment found first
|
||||||
|
const at = wall - offsetAt(wall - offsetAt(wall))
|
||||||
|
const there = parts(to, new Date(at))
|
||||||
return `${there.hour}:${there.minute}`
|
return `${there.hour}:${there.minute}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,23 @@ class DamagedReadTest extends TestCase {
|
|||||||
$this->assertSame(0, $backend->streams, 'the damaged read happened');
|
$this->assertSame(0, $backend->streams, 'the damaged read happened');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testATableDumpThatCameInDamagedOnceIsReadAgain(): void {
|
||||||
|
$repo = Repository::openWithKey(new LocalBackend($this->root), $this->keys);
|
||||||
|
$stats = [];
|
||||||
|
$writer = $repo->blobWriter($stats);
|
||||||
|
$lines = [];
|
||||||
|
for ($i = 0; $i < 20000; $i++) {
|
||||||
|
$lines[] = json_encode(['i' => $i, 'pad' => str_repeat('x', $i % 400)]);
|
||||||
|
}
|
||||||
|
$writer->write(implode("\n", $lines) . "\n");
|
||||||
|
$blobs = $writer->finish();
|
||||||
|
$repo->flushPacks();
|
||||||
|
$backend = new GarblingBackend(new LocalBackend($this->root), ranges: 1);
|
||||||
|
|
||||||
|
$this->assertSame($lines, iterator_to_array(Repository::openWithKey($backend, $this->keys)->readLines($blobs), false));
|
||||||
|
$this->assertSame(0, $backend->ranges, 'the damaged read happened');
|
||||||
|
}
|
||||||
|
|
||||||
public function testDataDamagedOnTheLocationStillStopsTheRestore(): void {
|
public function testDataDamagedOnTheLocationStillStopsTheRestore(): void {
|
||||||
$backend = new GarblingBackend(new LocalBackend($this->root), streams: 1000, ranges: 1000);
|
$backend = new GarblingBackend(new LocalBackend($this->root), streams: 1000, ranges: 1000);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user