mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-26 21:37:05 +00:00
feat: expose adaptive journal options for S3
This commit is contained in:
@@ -20,10 +20,16 @@
|
||||
import { copyTo, pickBucketSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import { TYPE_CANCELLED, type SetupRemoteBucketResultType } from "./setupDialogTypes";
|
||||
import { $msg as translateMessage } from "@/common/translation";
|
||||
import { normaliseS3JournalSettings } from "./s3JournalSettings";
|
||||
|
||||
const default_setting = pickBucketSyncSettings(DEFAULT_SETTINGS);
|
||||
|
||||
let syncSetting = $state<BucketSyncSetting>({ ...default_setting });
|
||||
let syncSetting = $state<BucketSyncSetting>({
|
||||
...default_setting,
|
||||
expectedRepositoryId: default_setting.expectedRepositoryId ?? "",
|
||||
journalFormat: default_setting.journalFormat ?? "opaque-v1",
|
||||
packReadPolicy: default_setting.packReadPolicy ?? "whole-pack",
|
||||
});
|
||||
|
||||
type Props = GuestDialogProps<SetupRemoteBucketResultType, BucketSyncSetting>;
|
||||
|
||||
@@ -58,11 +64,10 @@
|
||||
isEndpointSupplied
|
||||
);
|
||||
});
|
||||
const isAdaptive = $derived(syncSetting.journalFormat === "adaptive-v1");
|
||||
|
||||
function generateSetting() {
|
||||
const connSetting: BucketSyncSetting = {
|
||||
...syncSetting,
|
||||
};
|
||||
const connSetting = normaliseS3JournalSettings(syncSetting);
|
||||
const trialSettings: BucketSyncSetting = {
|
||||
...connSetting,
|
||||
};
|
||||
@@ -115,8 +120,13 @@
|
||||
}
|
||||
}
|
||||
function commit() {
|
||||
const setting = pickBucketSyncSettings(generateSetting());
|
||||
setResult(setting);
|
||||
error = "";
|
||||
try {
|
||||
const setting = pickBucketSyncSettings(generateSetting());
|
||||
setResult(setting);
|
||||
} catch (e) {
|
||||
error = translateMessage("Invalid Object Storage settings: ${reason}", { reason: `${e}` });
|
||||
}
|
||||
}
|
||||
function cancel() {
|
||||
setResult(TYPE_CANCELLED);
|
||||
@@ -220,6 +230,30 @@
|
||||
</InfoNote>
|
||||
|
||||
<ExtraItems title={translateMessage("Advanced Settings")}>
|
||||
<InputRow label={translateMessage("Journal data format")}>
|
||||
<select name="s3-journal-format" bind:value={syncSetting.journalFormat}>
|
||||
<option value="opaque-v1">{translateMessage("Opaque Journal (current format)")}</option>
|
||||
<option value="adaptive-v1">{translateMessage("Adaptive Journal (experimental)")}</option>
|
||||
</select>
|
||||
</InputRow>
|
||||
<InfoNote warning visible={isAdaptive}>
|
||||
{translateMessage(
|
||||
"Adaptive Journal uses immutable objects and a separate remote format. Existing Opaque Journal data is not migrated or read. Rebuild the remote when changing formats."
|
||||
)}
|
||||
</InfoNote>
|
||||
{#if isAdaptive}
|
||||
<InputRow label={translateMessage("Pack retrieval")}>
|
||||
<select name="s3-pack-read-policy" bind:value={syncSetting.packReadPolicy}>
|
||||
<option value="whole-pack">{translateMessage("Download complete Packs")}</option>
|
||||
<option value="range">{translateMessage("Use S3 Range requests")}</option>
|
||||
</select>
|
||||
</InputRow>
|
||||
<InfoNote>
|
||||
{translateMessage(
|
||||
"Complete Pack reads favour throughput. Range reads can reduce transferred bytes. The connection test verifies exact Range support on this endpoint, and synchronisation refuses an unsupported selection before writing."
|
||||
)}
|
||||
</InfoNote>
|
||||
{/if}
|
||||
<InputRow label={translateMessage("Custom Headers")}>
|
||||
<textarea
|
||||
name="bucket-custom-headers"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { DEFAULT_SETTINGS, REMOTE_MINIO, type BucketSyncSetting } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { journalProtocolConfigurationForSettings } from "@vrtmrz/livesync-commonlib/journal-storage";
|
||||
|
||||
export function normaliseS3JournalSettings(settings: BucketSyncSetting): BucketSyncSetting {
|
||||
const journalFormat = settings.journalFormat ?? "opaque-v1";
|
||||
const candidate: BucketSyncSetting = {
|
||||
...settings,
|
||||
bucket: settings.bucket.trim(),
|
||||
bucketPrefix: settings.bucketPrefix.trim(),
|
||||
endpoint: settings.endpoint.trim(),
|
||||
expectedRepositoryId: journalFormat === "adaptive-v1" ? (settings.expectedRepositoryId ?? "").trim() : "",
|
||||
journalFormat,
|
||||
packReadPolicy: journalFormat === "adaptive-v1" ? (settings.packReadPolicy ?? "whole-pack") : "whole-pack",
|
||||
region: settings.region.trim(),
|
||||
};
|
||||
const protocol = journalProtocolConfigurationForSettings({
|
||||
...DEFAULT_SETTINGS,
|
||||
...candidate,
|
||||
remoteType: REMOTE_MINIO,
|
||||
});
|
||||
return {
|
||||
...candidate,
|
||||
...protocol,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_SETTINGS, type BucketSyncSetting } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { pickBucketSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import { normaliseS3JournalSettings } from "./s3JournalSettings";
|
||||
|
||||
function settings(overrides: Partial<BucketSyncSetting> = {}): BucketSyncSetting {
|
||||
return {
|
||||
...pickBucketSyncSettings(DEFAULT_SETTINGS),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("normaliseS3JournalSettings", () => {
|
||||
it("retains and validates Adaptive repository options", () => {
|
||||
const repositoryId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
|
||||
const result = normaliseS3JournalSettings(
|
||||
settings({
|
||||
bucket: " vault ",
|
||||
bucketPrefix: " journals/ ",
|
||||
endpoint: " https://storage.example ",
|
||||
expectedRepositoryId: ` ${repositoryId} `,
|
||||
journalFormat: "adaptive-v1",
|
||||
packReadPolicy: "range",
|
||||
region: " auto ",
|
||||
})
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
bucket: "vault",
|
||||
bucketPrefix: "journals/",
|
||||
endpoint: "https://storage.example",
|
||||
expectedRepositoryId: repositoryId,
|
||||
journalFormat: "adaptive-v1",
|
||||
packReadPolicy: "range",
|
||||
region: "auto",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses conservative Opaque defaults for older profiles", () => {
|
||||
const legacy = settings();
|
||||
delete legacy.expectedRepositoryId;
|
||||
delete legacy.journalFormat;
|
||||
delete legacy.packReadPolicy;
|
||||
|
||||
expect(normaliseS3JournalSettings(legacy)).toMatchObject({
|
||||
expectedRepositoryId: "",
|
||||
journalFormat: "opaque-v1",
|
||||
packReadPolicy: "whole-pack",
|
||||
});
|
||||
});
|
||||
|
||||
it("clears Adaptive-only options when Opaque Journal is selected", () => {
|
||||
expect(
|
||||
normaliseS3JournalSettings(
|
||||
settings({
|
||||
expectedRepositoryId: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
journalFormat: "opaque-v1",
|
||||
packReadPolicy: "range",
|
||||
})
|
||||
)
|
||||
).toMatchObject({
|
||||
expectedRepositoryId: "",
|
||||
journalFormat: "opaque-v1",
|
||||
packReadPolicy: "whole-pack",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a non-canonical expected repository ID", () => {
|
||||
expect(() =>
|
||||
normaliseS3JournalSettings(
|
||||
settings({
|
||||
expectedRepositoryId: "not-a-repository-id",
|
||||
journalFormat: "adaptive-v1",
|
||||
})
|
||||
)
|
||||
).toThrow("expectedRepositoryId must be a canonical base64url-encoded 32-byte value");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user