mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-29 14:02:31 +00:00
Remediation mode prevents the reconciliation scan which readiness depends upon, so the application never becomes ready while the modification-time limit is set. Since the result processor began waiting for readiness, nothing was applied in that mode at all, not even changes older than the limit, which is what the mode exists for. Result application no longer requires readiness while the limit is configured, but it does require a usable local database, because a change applied while a fetch has reset the database cannot gather its chunks and is dropped. The limit itself is still enforced for every change. A scheduled fetch also no longer offers Simple Fetch in this mode. Simple Fetch reconciles the Vault with the local database after fetching, past the check which refuses that scan, so it could store the current files or write changes newer than the limit. The detailed flow states the restriction and offers to clear it first.
119 lines
5.8 KiB
TypeScript
119 lines
5.8 KiB
TypeScript
import { NO_INTERACTION } from "@vrtmrz/livesync-commonlib/replication";
|
|
import { clearHandlers } from "@vrtmrz/livesync-commonlib/compat/replication/SyncParamsHandler";
|
|
import { UnresolvedErrorManager } from "@vrtmrz/livesync-commonlib/compat/services/base/UnresolvedErrorManager";
|
|
import type { ServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
|
import { fireAndForget } from "octagonal-wheels/promises";
|
|
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
|
import { createAutomaticReplicationTriggers } from "./automaticTriggers";
|
|
import { createCentralCompatibilityRecovery } from "./centralCompatibilityRecovery";
|
|
import { createOnlineReplicationPreflight, createSecuritySeedPreflight } from "./preflight";
|
|
import { ReplicateResultProcessor } from "./ReplicateResultProcessor";
|
|
|
|
type LocalApplicationActivityOwner = {
|
|
runBoundedLocalApplicationActivity<T>(task: () => T | PromiseLike<T>, options?: { label?: string }): Promise<T>;
|
|
};
|
|
|
|
function ownsLocalApplicationActivity(value: object): value is LocalApplicationActivityOwner {
|
|
return (
|
|
"runBoundedLocalApplicationActivity" in value && typeof value.runBoundedLocalApplicationActivity === "function"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Compose result application, automatic triggers, preflight, and central
|
|
* compatibility recovery around the existing typed Services.
|
|
*
|
|
* Registration order is observable for equal-priority handlers. The host must
|
|
* call this after host serviceFeatures and add-ons are composed, but before
|
|
* legacy modules are bound, so lifecycle handlers observe the required order.
|
|
*/
|
|
export function useReplicationFeature<TContext extends ServiceContext, TCommands extends IMinimumLiveSyncCommands>(
|
|
core: LiveSyncBaseCore<TContext, TCommands>
|
|
): void {
|
|
const { services } = core;
|
|
// Obsidian adds an application-activity owner to its ReplicatorService.
|
|
// Generic hosts retain the former direct-execution fallback.
|
|
const localApplicationActivityOwner = ownsLocalApplicationActivity(services.replicator)
|
|
? services.replicator
|
|
: undefined;
|
|
const resultProcessor = new ReplicateResultProcessor({
|
|
currentSettings: () => services.setting.currentSettings(),
|
|
getKeyValueDB: () => services.keyValueDB.kvDB,
|
|
getLocalDatabase: () => core.localDatabase,
|
|
requestActiveReplicatorRetirement: () => {
|
|
// Do not await a retirement transition from result application: it
|
|
// may be draining the replication work which delivered this item.
|
|
fireAndForget(() => services.replicator.onCloseActiveReplication());
|
|
},
|
|
runLocalApplicationActivity: async (task, options) =>
|
|
localApplicationActivityOwner
|
|
? await localApplicationActivityOwner.runBoundedLocalApplicationActivity(task, options)
|
|
: await task(),
|
|
services: {
|
|
appLifecycle: services.appLifecycle,
|
|
database: services.database,
|
|
path: services.path,
|
|
replication: services.replication,
|
|
vault: services.vault,
|
|
},
|
|
});
|
|
const unresolvedErrorManager = new UnresolvedErrorManager(services.appLifecycle, services.context.events);
|
|
const initialiseAutomaticReplicationTriggers = createAutomaticReplicationTriggers({
|
|
currentSettings: () => services.setting.currentSettings(),
|
|
isSuspended: () => services.appLifecycle.isSuspended(),
|
|
replicateDatabaseEvent: () =>
|
|
services.replication.replicateUnattendedByEvent({
|
|
trigger: "database-event",
|
|
interaction: NO_INTERACTION,
|
|
}),
|
|
reprocessStoredDocuments: () => resultProcessor.reprocessStoredDocuments(),
|
|
resumeResultApplication: () => resultProcessor.resume(),
|
|
suspendResultApplication: () => resultProcessor.suspend(),
|
|
});
|
|
const preflightContext = {
|
|
services: {
|
|
API: services.API,
|
|
replicator: services.replicator,
|
|
setting: services.setting,
|
|
},
|
|
};
|
|
const onlinePreflight = createOnlineReplicationPreflight(unresolvedErrorManager, preflightContext);
|
|
const securitySeedPreflight = createSecuritySeedPreflight(unresolvedErrorManager, preflightContext);
|
|
const centralCompatibilityRecovery = createCentralCompatibilityRecovery({
|
|
confirm: core.confirm,
|
|
getLocalDatabase: () => core.localDatabase,
|
|
rebuilder: core.rebuilder,
|
|
services: {
|
|
API: services.API,
|
|
appLifecycle: services.appLifecycle,
|
|
replicator: services.replicator,
|
|
setting: services.setting,
|
|
tweakValue: services.tweakValue,
|
|
},
|
|
});
|
|
|
|
services.replicator.onBeforeReplicatorPublication.addHandler(() => {
|
|
// Key-derivation handlers belong to the candidate which is about to
|
|
// become active; discard callbacks retained by the previous owner.
|
|
clearHandlers();
|
|
return Promise.resolve(true);
|
|
});
|
|
services.databaseEvents.onDatabaseInitialised.addHandler(() => {
|
|
fireAndForget(() => resultProcessor.restoreFromSnapshotOnce());
|
|
return Promise.resolve(true);
|
|
});
|
|
services.appLifecycle.onSettingLoaded.addHandler(initialiseAutomaticReplicationTriggers);
|
|
services.replication.parseSynchroniseResult.addHandler((documents) => {
|
|
resultProcessor.enqueueAll(documents);
|
|
return Promise.resolve(true);
|
|
});
|
|
services.replication.onBeforeReplicate.addHandler(onlinePreflight, 10);
|
|
services.replication.onPrepareCentralRemoteReplication.addHandler(securitySeedPreflight);
|
|
services.replication.onBeforeReplicate.addHandler(async () => {
|
|
await resultProcessor.restoreFromSnapshotOnce();
|
|
unresolvedErrorManager.clearErrors();
|
|
return true;
|
|
}, 100);
|
|
services.replication.onReplicationFailed.addHandler(centralCompatibilityRecovery.handleReplicationFailure);
|
|
}
|