mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-23 14:41:28 +00:00
## 0.24.21
### Fixed
- No longer conflicted files are handled in the boot-up process. No more unexpected overwriting.
- It ignores `Always overwrite with a newer file`, and always be prevented for the safety. Please pick it manually or open the file.
- Some log messages on conflict resolution has been corrected.
- Automatic merge notifications, displayed on the grounds of `same`, have been degraded to logs.
### Improved
- Now we can fetch the remote database with keeping local files completely intact.
- In new option, all files are stored into the local database before the fetching, and will be merged automatically or detected as conflicts.
- The dialogue presenting options when performing `Fetch` are now more informative.
### Refactored
- Some class methods have been fixed its arguments to be more consistent.
- Types have been defined for some conditional results.
This commit is contained in:
@@ -1306,7 +1306,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, false, false);
|
||||||
if (docXDoc == false) {
|
if (docXDoc == false) {
|
||||||
throw "Could not load the document";
|
throw "Could not load the document";
|
||||||
}
|
}
|
||||||
@@ -1440,7 +1440,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
// this._log(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same time)`, LOG_LEVEL_VERBOSE);
|
// this._log(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same time)`, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
const oldC = await this.localDatabase.getDBEntryFromMeta(old, false, false);
|
||||||
if (oldC) {
|
if (oldC) {
|
||||||
const d = (await deserialize(getDocDataAsArray(oldC.data), {})) as PluginDataEx;
|
const d = (await deserialize(getDocDataAsArray(oldC.data), {})) as PluginDataEx;
|
||||||
if (d.files.length == dt.files.length) {
|
if (d.files.length == dt.files.length) {
|
||||||
|
|||||||
@@ -1490,7 +1490,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const fileOnDB = await this.localDatabase.getDBEntryFromMeta(metaOnDB, {}, false, true, true);
|
const fileOnDB = await this.localDatabase.getDBEntryFromMeta(metaOnDB, false, true);
|
||||||
if (fileOnDB === false) {
|
if (fileOnDB === false) {
|
||||||
throw new Error(`Failed to read file from database:${storageFilePath}`);
|
throw new Error(`Failed to read file from database:${storageFilePath}`);
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: a5d21afb61...1e24d2af19
@@ -313,13 +313,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
if (skipCheck && !(await this.checkIsTargetFile(meta.path))) {
|
if (skipCheck && !(await this.checkIsTargetFile(meta.path))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const doc = await this.localDatabase.getDBEntryFromMeta(
|
const doc = await this.localDatabase.getDBEntryFromMeta(meta as LoadedEntry, false, waitForReady);
|
||||||
meta as LoadedEntry,
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
waitForReady,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
if (doc === false) {
|
if (doc === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fetchLocal(makeLocalChunkBeforeSync?: boolean) {
|
async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.core.$allSuspendExtraSync();
|
||||||
await this.askUseNewAdapter();
|
await this.askUseNewAdapter();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
@@ -189,6 +189,10 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this.core.$$markIsReady();
|
this.core.$$markIsReady();
|
||||||
if (makeLocalChunkBeforeSync) {
|
if (makeLocalChunkBeforeSync) {
|
||||||
await this.core.fileHandler.createAllChunks(true);
|
await this.core.fileHandler.createAllChunks(true);
|
||||||
|
} else if (!preventMakeLocalFilesBeforeSync) {
|
||||||
|
await this.core.$$initializeDatabase(true);
|
||||||
|
} else {
|
||||||
|
// Do not create local file entries before sync (Means use remote information)
|
||||||
}
|
}
|
||||||
await this.core.$$markRemoteResolved();
|
await this.core.$$markRemoteResolved();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
|
|
||||||
// If `Read chunks online` is disabled, chunks should be transferred before here.
|
// If `Read chunks online` is disabled, chunks should be transferred before here.
|
||||||
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
|
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
|
||||||
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, {}, false, true, true);
|
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, false, true);
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
Logger(
|
Logger(
|
||||||
`Something went wrong while gathering content of ${path} (${dbDoc._id.substring(0, 8)}, ${dbDoc._rev?.substring(0, 10)}) `,
|
`Something went wrong while gathering content of ${path} (${dbDoc._id.substring(0, 8)}, ${dbDoc._rev?.substring(0, 10)}) `,
|
||||||
|
|||||||
@@ -44,13 +44,16 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
eventHub.emitEvent("conflict-cancelled", path);
|
eventHub.emitEvent("conflict-cancelled", path);
|
||||||
this._log(`${title} Conflicted revision deleted ${displayRev(deleteRevision)} ${path}`, LOG_LEVEL_INFO);
|
this._log(
|
||||||
|
`${title} Conflicted revision has been deleted ${displayRev(deleteRevision)} ${path}`,
|
||||||
|
LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
if ((await this.core.databaseFileAccess.getConflictedRevs(path)).length != 0) {
|
if ((await this.core.databaseFileAccess.getConflictedRevs(path)).length != 0) {
|
||||||
this._log(`${title} some conflicts are left in ${path}`, LOG_LEVEL_INFO);
|
this._log(`${title} some conflicts are left in ${path}`, LOG_LEVEL_INFO);
|
||||||
return AUTO_MERGED;
|
return AUTO_MERGED;
|
||||||
}
|
}
|
||||||
this._log(`${title} ${path} is a plugin metadata file, no need to write to storage`, LOG_LEVEL_INFO);
|
|
||||||
if (isPluginMetadata(path) || isCustomisationSyncMetadata(path)) {
|
if (isPluginMetadata(path) || isCustomisationSyncMetadata(path)) {
|
||||||
|
this._log(`${title} ${path} is a plugin metadata file, no need to write to storage`, LOG_LEVEL_INFO);
|
||||||
return AUTO_MERGED;
|
return AUTO_MERGED;
|
||||||
}
|
}
|
||||||
// If no conflicts were found, write the resolved content to the storage.
|
// If no conflicts were found, write the resolved content to the storage.
|
||||||
@@ -58,7 +61,8 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE);
|
this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE);
|
||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
this._log(`${path} Has been merged automatically`, LOG_LEVEL_NOTICE);
|
const level = subTitle.indexOf("same") !== -1 ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE;
|
||||||
|
this._log(`${path} has been merged automatically`, level);
|
||||||
return AUTO_MERGED;
|
return AUTO_MERGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +112,9 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
`${isSame ? "same" : ""}`,
|
`${isSame ? "same" : ""}`,
|
||||||
`${isBinary ? "binary" : ""}`,
|
`${isBinary ? "binary" : ""}`,
|
||||||
`${alwaysNewer ? "alwaysNewer" : ""}`,
|
`${alwaysNewer ? "alwaysNewer" : ""}`,
|
||||||
].join(",");
|
]
|
||||||
|
.filter((e) => e.trim())
|
||||||
|
.join(",");
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
|
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
|
||||||
}
|
}
|
||||||
// make diff.
|
// make diff.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
|
|
||||||
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
||||||
async isFlagFileExist(path: string) {
|
async isFlagFileExist(path: string) {
|
||||||
@@ -105,16 +106,35 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
`${FLAGMD_REDFLAG3} or ${FLAGMD_REDFLAG3_HR} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`,
|
`${FLAGMD_REDFLAG3} or ${FLAGMD_REDFLAG3_HR} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
);
|
);
|
||||||
const makeLocalChunkBeforeSync =
|
const method1 = $msg("RedFlag.Fetch.Method.FetchSafer");
|
||||||
(await this.core.confirm.askYesNoDialog(
|
const method2 = $msg("RedFlag.Fetch.Method.FetchSmoother");
|
||||||
`Do you want to create local chunks before fetching?
|
const method3 = $msg("RedFlag.Fetch.Method.FetchTraditional");
|
||||||
> [!MORE]-
|
|
||||||
> If creating local chunks before fetching, only the difference between the local and remote will be fetched.
|
const methods = [method1, method2, method3] as const;
|
||||||
|
const chunkMode = await this.core.confirm.askSelectStringDialogue(
|
||||||
|
$msg("RedFlag.Fetch.Method.Desc"),
|
||||||
|
methods,
|
||||||
|
{
|
||||||
|
defaultAction: method1,
|
||||||
|
timeout: 0,
|
||||||
|
title: $msg("RedFlag.Fetch.Method.Title"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let makeLocalChunkBeforeSync = false;
|
||||||
|
let preventMakeLocalFilesBeforeSync = false;
|
||||||
|
if (chunkMode === method1) {
|
||||||
|
preventMakeLocalFilesBeforeSync = true;
|
||||||
|
} else if (chunkMode === method2) {
|
||||||
|
makeLocalChunkBeforeSync = true;
|
||||||
|
} else if (chunkMode === method3) {
|
||||||
|
// Do nothing.
|
||||||
|
} else {
|
||||||
|
this._log("Cancelled the fetch operation", LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.core.rebuilder.$fetchLocal(makeLocalChunkBeforeSync, preventMakeLocalFilesBeforeSync);
|
||||||
|
|
||||||
`,
|
|
||||||
{ defaultOption: "Yes", title: "Trick to transfer efficiently" }
|
|
||||||
)) == "yes";
|
|
||||||
await this.core.rebuilder.$fetchLocal(makeLocalChunkBeforeSync);
|
|
||||||
await this.deleteRedFlag3();
|
await this.deleteRedFlag3();
|
||||||
if (this.settings.suspendFileWatching) {
|
if (this.settings.suspendFileWatching) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
};
|
};
|
||||||
initProcess.push(
|
initProcess.push(
|
||||||
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
||||||
// console.warn("UPDATE DATABASE", e);
|
// Exists in storage but not in database.
|
||||||
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
||||||
const path = file.path;
|
const path = file.path;
|
||||||
@@ -195,9 +195,16 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
initProcess.push(
|
initProcess.push(
|
||||||
runAll("UPDATE STORAGE", filesExistOnlyInDatabase, async (e) => {
|
runAll("UPDATE STORAGE", filesExistOnlyInDatabase, async (e) => {
|
||||||
const w = databaseFileNameMap[databaseFileNameCI2CS[e]];
|
const w = databaseFileNameMap[databaseFileNameCI2CS[e]];
|
||||||
|
// Exists in database but not in storage.
|
||||||
const path = getPath(w) ?? e;
|
const path = getPath(w) ?? e;
|
||||||
if (w && !(w.deleted || w._deleted)) {
|
if (w && !(w.deleted || w._deleted)) {
|
||||||
if (!this.core.$$isFileSizeExceeded(w.size)) {
|
if (!this.core.$$isFileSizeExceeded(w.size)) {
|
||||||
|
// Prevent applying the conflicted state to the storage.
|
||||||
|
const conflicted = await this.core.databaseFileAccess.getConflictedRevs(path);
|
||||||
|
if (conflicted.length > 0) {
|
||||||
|
this._log(`UPDATE STORAGE: ${path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// await this.pullFile(path, undefined, false, undefined, false);
|
// await this.pullFile(path, undefined, false, undefined, false);
|
||||||
// Memo: No need to force
|
// Memo: No need to force
|
||||||
await this.core.fileHandler.dbToStorage(path, null, true);
|
await this.core.fileHandler.dbToStorage(path, null, true);
|
||||||
@@ -229,6 +236,12 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
initProcess.push(
|
initProcess.push(
|
||||||
runAll("SYNC DATABASE AND STORAGE", fileMap, async (e) => {
|
runAll("SYNC DATABASE AND STORAGE", fileMap, async (e) => {
|
||||||
const { file, doc } = e;
|
const { file, doc } = e;
|
||||||
|
// Prevent applying the conflicted state to the storage.
|
||||||
|
const conflicted = await this.core.databaseFileAccess.getConflictedRevs(file.path);
|
||||||
|
if (conflicted.length > 0) {
|
||||||
|
this._log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
||||||
await this.syncFileBetweenDBandStorage(file, doc);
|
await this.syncFileBetweenDBandStorage(file, doc);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export interface Rebuilder {
|
|||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
$rebuildRemote(): Promise<void>;
|
$rebuildRemote(): Promise<void>;
|
||||||
$rebuildEverything(): Promise<void>;
|
$rebuildEverything(): Promise<void>;
|
||||||
$fetchLocal(makeLocalChunkBeforeSync?: boolean): Promise<void>;
|
$fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean): Promise<void>;
|
||||||
|
|
||||||
scheduleRebuild(): Promise<void>;
|
scheduleRebuild(): Promise<void>;
|
||||||
scheduleFetch(): Promise<void>;
|
scheduleFetch(): Promise<void>;
|
||||||
|
|||||||
Reference in New Issue
Block a user