Format files
This commit is contained in:
parent
02486d671e
commit
438caa96a6
19 changed files with 557 additions and 585 deletions
|
|
@ -16,8 +16,8 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"tabWidth": 2,
|
"tabWidth": 4,
|
||||||
"useTabs": false,
|
"useTabs": true,
|
||||||
"endOfLine": "lf"
|
"endOfLine": "lf"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import type {
|
||||||
DocumentId,
|
DocumentId,
|
||||||
DocumentMetadata,
|
DocumentMetadata,
|
||||||
RelativePath,
|
RelativePath,
|
||||||
VaultUpdateId,
|
VaultUpdateId
|
||||||
} from "./document-metadata";
|
} from "./document-metadata";
|
||||||
import { Logger } from "src/tracing/logger";
|
import { Logger } from "src/tracing/logger";
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ export class Database {
|
||||||
|
|
||||||
this._settings = {
|
this._settings = {
|
||||||
...DEFAULT_SETTINGS,
|
...DEFAULT_SETTINGS,
|
||||||
...(initialState.settings ?? {}),
|
...(initialState.settings ?? {})
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.getInstance().debug(
|
Logger.getInstance().debug(
|
||||||
|
|
@ -128,7 +128,7 @@ export class Database {
|
||||||
documentId,
|
documentId,
|
||||||
relativePath,
|
relativePath,
|
||||||
parentVersionId,
|
parentVersionId,
|
||||||
hash,
|
hash
|
||||||
}: {
|
}: {
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath;
|
||||||
|
|
@ -138,7 +138,7 @@ export class Database {
|
||||||
this._documents.set(relativePath, {
|
this._documents.set(relativePath, {
|
||||||
documentId,
|
documentId,
|
||||||
parentVersionId,
|
parentVersionId,
|
||||||
hash,
|
hash
|
||||||
});
|
});
|
||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +148,7 @@ export class Database {
|
||||||
oldRelativePath,
|
oldRelativePath,
|
||||||
relativePath,
|
relativePath,
|
||||||
parentVersionId,
|
parentVersionId,
|
||||||
hash,
|
hash
|
||||||
}: {
|
}: {
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
oldRelativePath: RelativePath;
|
oldRelativePath: RelativePath;
|
||||||
|
|
@ -160,7 +160,7 @@ export class Database {
|
||||||
this._documents.set(relativePath, {
|
this._documents.set(relativePath, {
|
||||||
documentId,
|
documentId,
|
||||||
parentVersionId,
|
parentVersionId,
|
||||||
hash,
|
hash
|
||||||
});
|
});
|
||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +180,7 @@ export class Database {
|
||||||
await this.saveData({
|
await this.saveData({
|
||||||
documents: Object.fromEntries(this._documents.entries()),
|
documents: Object.fromEntries(this._documents.entries()),
|
||||||
settings: this._settings,
|
settings: this._settings,
|
||||||
lastSeenUpdateId: this._lastSeenUpdateId,
|
lastSeenUpdateId: this._lastSeenUpdateId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,5 @@ export const DEFAULT_SETTINGS: SyncSettings = {
|
||||||
syncConcurrency: 1,
|
syncConcurrency: 1,
|
||||||
isSyncEnabled: false,
|
isSyncEnabled: false,
|
||||||
displayNoopSyncEvents: false,
|
displayNoopSyncEvents: false,
|
||||||
minimumLogLevel: LogLevel.INFO,
|
minimumLogLevel: LogLevel.INFO
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export class ObsidianFileEventHandler implements FileEventHandler {
|
||||||
await this.syncer.syncLocallyUpdatedFile({
|
await this.syncer.syncLocallyUpdatedFile({
|
||||||
oldPath,
|
oldPath,
|
||||||
relativePath: file.path,
|
relativePath: file.path,
|
||||||
updateTime: new Date(file.stat.ctime),
|
updateTime: new Date(file.stat.ctime)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Logger.getInstance().debug(
|
Logger.getInstance().debug(
|
||||||
|
|
@ -54,7 +54,7 @@ export class ObsidianFileEventHandler implements FileEventHandler {
|
||||||
|
|
||||||
await this.syncer.syncLocallyUpdatedFile({
|
await this.syncer.syncLocallyUpdatedFile({
|
||||||
relativePath: file.path,
|
relativePath: file.path,
|
||||||
updateTime: new Date(file.stat.ctime),
|
updateTime: new Date(file.stat.ctime)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Logger.getInstance().debug(
|
Logger.getInstance().debug(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import type { Vault } from "obsidian";
|
import type { Vault } from "obsidian";
|
||||||
import { normalizePath } from "obsidian";
|
import { normalizePath } from "obsidian";
|
||||||
import type { FileOperations } from "./file-operations";
|
import type { FileOperations } from "./file-operations";
|
||||||
import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js";
|
|
||||||
import type { RelativePath } from "src/database/document-metadata";
|
import type { RelativePath } from "src/database/document-metadata";
|
||||||
|
import { isBinary, mergeText } from "sync_lib";
|
||||||
|
|
||||||
export class ObsidianFileOperations implements FileOperations {
|
export class ObsidianFileOperations implements FileOperations {
|
||||||
public constructor(private readonly vault: Vault) {}
|
public constructor(private readonly vault: Vault) {}
|
||||||
|
|
@ -49,7 +49,7 @@ export class ObsidianFileOperations implements FileOperations {
|
||||||
return new Uint8Array(0);
|
return new Uint8Array(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lib.isBinary(expectedContent) || !path.endsWith(".md")) {
|
if (isBinary(expectedContent) || !path.endsWith(".md")) {
|
||||||
await this.vault.adapter.writeBinary(
|
await this.vault.adapter.writeBinary(
|
||||||
normalizePath(path),
|
normalizePath(path),
|
||||||
newContent
|
newContent
|
||||||
|
|
@ -64,7 +64,7 @@ export class ObsidianFileOperations implements FileOperations {
|
||||||
normalizePath(path),
|
normalizePath(path),
|
||||||
(currentText) => {
|
(currentText) => {
|
||||||
if (currentText !== expetedText) {
|
if (currentText !== expetedText) {
|
||||||
return lib.mergeText(expetedText, currentText, newText);
|
return mergeText(expetedText, currentText, newText);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newText;
|
return newText;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js";
|
|
||||||
|
|
||||||
import type { Client } from "openapi-fetch";
|
import type { Client } from "openapi-fetch";
|
||||||
import createClient from "openapi-fetch";
|
import createClient from "openapi-fetch";
|
||||||
import type { components, paths } from "./types.js"; // Generated by openapi-typescript
|
import type { components, paths } from "./types"; // Generated by openapi-typescript
|
||||||
import type { Database } from "src/database/database";
|
import type { Database } from "src/database/database";
|
||||||
import type { SyncSettings } from "src/database/sync-settings";
|
import type { SyncSettings } from "src/database/sync-settings";
|
||||||
import type {
|
import type {
|
||||||
DocumentId,
|
DocumentId,
|
||||||
RelativePath,
|
RelativePath,
|
||||||
VaultUpdateId,
|
VaultUpdateId
|
||||||
} from "src/database/document-metadata";
|
} from "src/database/document-metadata";
|
||||||
import { Logger } from "src/tracing/logger.js";
|
import { Logger } from "src/tracing/logger";
|
||||||
import { retriedFetch } from "src/utils/retried-fetch.js";
|
import { retriedFetch } from "src/utils/retried-fetch";
|
||||||
|
import { bytesToBase64 } from "sync_lib";
|
||||||
|
|
||||||
export interface CheckConnectionResult {
|
export interface CheckConnectionResult {
|
||||||
isSuccessful: boolean;
|
isSuccessful: boolean;
|
||||||
|
|
@ -45,11 +44,9 @@ export class SyncService {
|
||||||
const response = await this.clientWithoutRetries.GET("/ping", {
|
const response = await this.clientWithoutRetries.GET("/ping", {
|
||||||
params: {
|
params: {
|
||||||
header: {
|
header: {
|
||||||
authorization: `Bearer ${
|
authorization: `Bearer ${this.database.getSettings().token}`
|
||||||
this.database.getSettings().token
|
}
|
||||||
}`,
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Logger.getInstance().debug(
|
Logger.getInstance().debug(
|
||||||
|
|
@ -58,9 +55,7 @@ export class SyncService {
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to ping server: ${SyncService.formatError(
|
`Failed to ping server: ${SyncService.formatError(response.error)}`
|
||||||
response.error
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +65,7 @@ export class SyncService {
|
||||||
public async create({
|
public async create({
|
||||||
relativePath,
|
relativePath,
|
||||||
contentBytes,
|
contentBytes,
|
||||||
createdDate,
|
createdDate
|
||||||
}: {
|
}: {
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath;
|
||||||
contentBytes: Uint8Array;
|
contentBytes: Uint8Array;
|
||||||
|
|
@ -81,27 +76,23 @@ export class SyncService {
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
vault_id: this.database.getSettings().vaultName,
|
vault_id: this.database.getSettings().vaultName
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
authorization: `Bearer ${
|
authorization: `Bearer ${this.database.getSettings().token}`
|
||||||
this.database.getSettings().token
|
}
|
||||||
}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
contentBase64: lib.bytesToBase64(contentBytes),
|
contentBase64: bytesToBase64(contentBytes),
|
||||||
createdDate: createdDate.toISOString(),
|
createdDate: createdDate.toISOString(),
|
||||||
relativePath,
|
relativePath
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to create document: ${SyncService.formatError(
|
`Failed to create document: ${SyncService.formatError(response.error)}`
|
||||||
response.error
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +110,7 @@ export class SyncService {
|
||||||
documentId,
|
documentId,
|
||||||
relativePath,
|
relativePath,
|
||||||
contentBytes,
|
contentBytes,
|
||||||
createdDate,
|
createdDate
|
||||||
}: {
|
}: {
|
||||||
parentVersionId: VaultUpdateId;
|
parentVersionId: VaultUpdateId;
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
|
|
@ -133,28 +124,24 @@ export class SyncService {
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
vault_id: this.database.getSettings().vaultName,
|
vault_id: this.database.getSettings().vaultName,
|
||||||
document_id: documentId,
|
document_id: documentId
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
authorization: `Bearer ${
|
authorization: `Bearer ${this.database.getSettings().token}`
|
||||||
this.database.getSettings().token
|
}
|
||||||
}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
parentVersionId,
|
parentVersionId,
|
||||||
contentBase64: lib.bytesToBase64(contentBytes),
|
contentBase64: bytesToBase64(contentBytes),
|
||||||
createdDate: createdDate.toISOString(),
|
createdDate: createdDate.toISOString(),
|
||||||
relativePath,
|
relativePath
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to update document: ${SyncService.formatError(
|
`Failed to update document: ${SyncService.formatError(response.error)}`
|
||||||
response.error
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +155,7 @@ export class SyncService {
|
||||||
public async delete({
|
public async delete({
|
||||||
documentId,
|
documentId,
|
||||||
relativePath,
|
relativePath,
|
||||||
createdDate,
|
createdDate
|
||||||
}: {
|
}: {
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath;
|
||||||
|
|
@ -180,18 +167,16 @@ export class SyncService {
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
vault_id: this.database.getSettings().vaultName,
|
vault_id: this.database.getSettings().vaultName,
|
||||||
document_id: documentId,
|
document_id: documentId
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
authorization: `Bearer ${
|
authorization: `Bearer ${this.database.getSettings().token}`
|
||||||
this.database.getSettings().token
|
}
|
||||||
}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
createdDate: createdDate.toISOString(),
|
createdDate: createdDate.toISOString(),
|
||||||
relativePath,
|
relativePath
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -207,7 +192,7 @@ export class SyncService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get({
|
public async get({
|
||||||
documentId,
|
documentId
|
||||||
}: {
|
}: {
|
||||||
documentId: DocumentId;
|
documentId: DocumentId;
|
||||||
}): Promise<components["schemas"]["DocumentVersion"]> {
|
}): Promise<components["schemas"]["DocumentVersion"]> {
|
||||||
|
|
@ -217,22 +202,18 @@ export class SyncService {
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
vault_id: this.database.getSettings().vaultName,
|
vault_id: this.database.getSettings().vaultName,
|
||||||
document_id: documentId,
|
document_id: documentId
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
authorization: `Bearer ${
|
authorization: `Bearer ${this.database.getSettings().token}`
|
||||||
this.database.getSettings().token
|
}
|
||||||
}`,
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to get document: ${SyncService.formatError(
|
`Failed to get document: ${SyncService.formatError(response.error)}`
|
||||||
response.error
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,25 +230,21 @@ export class SyncService {
|
||||||
const response = await this.client.GET("/vaults/{vault_id}/documents", {
|
const response = await this.client.GET("/vaults/{vault_id}/documents", {
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
vault_id: this.database.getSettings().vaultName,
|
vault_id: this.database.getSettings().vaultName
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
authorization: `Bearer ${
|
authorization: `Bearer ${this.database.getSettings().token}`
|
||||||
this.database.getSettings().token
|
|
||||||
}`,
|
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
since_update_id: since,
|
since_update_id: since
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { error } = response;
|
const { error } = response;
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to get documents: ${SyncService.formatError(
|
`Failed to get documents: ${SyncService.formatError(response.error)}`
|
||||||
response.error
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,18 +261,18 @@ export class SyncService {
|
||||||
if (result.isAuthenticated) {
|
if (result.isAuthenticated) {
|
||||||
return {
|
return {
|
||||||
isSuccessful: true,
|
isSuccessful: true,
|
||||||
message: `Successfully connected to server (version: ${result.serverVersion}) and authenticated.`,
|
message: `Successfully connected to server (version: ${result.serverVersion}) and authenticated.`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSuccessful: false,
|
isSuccessful: false,
|
||||||
message: `Successfully connected to server (version: ${result.serverVersion}) but failed to authenticate.`,
|
message: `Successfully connected to server (version: ${result.serverVersion}) but failed to authenticate.`
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
isSuccessful: false,
|
isSuccessful: false,
|
||||||
message: `Failed to connect to server: ${e}`,
|
message: `Failed to connect to server: ${e}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,11 +280,11 @@ export class SyncService {
|
||||||
private createClient(settings: SyncSettings): void {
|
private createClient(settings: SyncSettings): void {
|
||||||
this.client = createClient<paths>({
|
this.client = createClient<paths>({
|
||||||
baseUrl: settings.remoteUri,
|
baseUrl: settings.remoteUri,
|
||||||
fetch: retriedFetch,
|
fetch: retriedFetch
|
||||||
});
|
});
|
||||||
|
|
||||||
this.clientWithoutRetries = createClient<paths>({
|
this.clientWithoutRetries = createClient<paths>({
|
||||||
baseUrl: settings.remoteUri,
|
baseUrl: settings.remoteUri
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,380 +4,382 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface paths {
|
export interface paths {
|
||||||
"/ping": {
|
"/ping": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get: {
|
get: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: {
|
header?: {
|
||||||
authorization?: string;
|
authorization?: string;
|
||||||
};
|
};
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody?: never;
|
requestBody?: never;
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["PingResponse"];
|
"application/json": components["schemas"]["PingResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["SerializedError"];
|
"application/json": components["schemas"]["SerializedError"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
delete?: never;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/vaults/{vault_id}/documents": {
|
"/vaults/{vault_id}/documents": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get: {
|
get: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
since_update_id?: number | null;
|
since_update_id?: number | null;
|
||||||
};
|
};
|
||||||
header: {
|
header: {
|
||||||
authorization: string;
|
authorization: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
vault_id: string;
|
vault_id: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody?: never;
|
requestBody?: never;
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["FetchLatestDocumentsResponse"];
|
"application/json": components["schemas"]["FetchLatestDocumentsResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["SerializedError"];
|
"application/json": components["schemas"]["SerializedError"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
put?: never;
|
put?: never;
|
||||||
post: {
|
post: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header: {
|
header: {
|
||||||
authorization: string;
|
authorization: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
vault_id: string;
|
vault_id: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["CreateDocumentVersion"];
|
"application/json": components["schemas"]["CreateDocumentVersion"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["DocumentUpdateResponse"];
|
"application/json": components["schemas"]["DocumentUpdateResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["SerializedError"];
|
"application/json": components["schemas"]["SerializedError"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
delete?: never;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/vaults/{vault_id}/documents/{document_id}": {
|
"/vaults/{vault_id}/documents/{document_id}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get: {
|
get: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header: {
|
header: {
|
||||||
authorization: string;
|
authorization: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
document_id: string;
|
document_id: string;
|
||||||
vault_id: string;
|
vault_id: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody?: never;
|
requestBody?: never;
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["DocumentVersion"];
|
"application/json": components["schemas"]["DocumentVersion"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["SerializedError"];
|
"application/json": components["schemas"]["SerializedError"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
put: {
|
put: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header: {
|
header: {
|
||||||
authorization: string;
|
authorization: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
document_id: string;
|
document_id: string;
|
||||||
vault_id: string;
|
vault_id: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["UpdateDocumentVersion"];
|
"application/json": components["schemas"]["UpdateDocumentVersion"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["DocumentUpdateResponse"];
|
"application/json": components["schemas"]["DocumentUpdateResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["SerializedError"];
|
"application/json": components["schemas"]["SerializedError"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
post?: never;
|
post?: never;
|
||||||
delete: {
|
delete: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header: {
|
header: {
|
||||||
authorization: string;
|
authorization: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
document_id: string;
|
document_id: string;
|
||||||
vault_id: string;
|
vault_id: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["DeleteDocumentVersion"];
|
"application/json": components["schemas"]["DeleteDocumentVersion"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description no content */
|
/** @description no content */
|
||||||
200: {
|
200: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content?: never;
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["SerializedError"];
|
"application/json": components["schemas"]["SerializedError"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
schemas: {
|
schemas: {
|
||||||
CreateDocumentVersion: {
|
CreateDocumentVersion: {
|
||||||
contentBase64: string;
|
contentBase64: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
createdDate: string;
|
createdDate: string;
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
};
|
};
|
||||||
DeleteDocumentVersion: {
|
DeleteDocumentVersion: {
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
createdDate: string;
|
createdDate: string;
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
};
|
};
|
||||||
/** @description Response to a create/update document request. */
|
/** @description Response to a create/update document request. */
|
||||||
DocumentUpdateResponse: {
|
DocumentUpdateResponse:
|
||||||
/** Format: date-time */
|
| {
|
||||||
createdDate: string;
|
/** Format: date-time */
|
||||||
/** Format: uuid */
|
createdDate: string;
|
||||||
documentId: string;
|
/** Format: uuid */
|
||||||
isDeleted: boolean;
|
documentId: string;
|
||||||
relativePath: string;
|
isDeleted: boolean;
|
||||||
/** @enum {string} */
|
relativePath: string;
|
||||||
type: "FastForwardUpdate";
|
/** @enum {string} */
|
||||||
/** Format: date-time */
|
type: "FastForwardUpdate";
|
||||||
updatedDate: string;
|
/** Format: date-time */
|
||||||
vaultId: string;
|
updatedDate: string;
|
||||||
/** Format: int64 */
|
vaultId: string;
|
||||||
vaultUpdateId: number;
|
/** Format: int64 */
|
||||||
} | {
|
vaultUpdateId: number;
|
||||||
contentBase64: string;
|
}
|
||||||
/** Format: date-time */
|
| {
|
||||||
createdDate: string;
|
contentBase64: string;
|
||||||
/** Format: uuid */
|
/** Format: date-time */
|
||||||
documentId: string;
|
createdDate: string;
|
||||||
isDeleted: boolean;
|
/** Format: uuid */
|
||||||
relativePath: string;
|
documentId: string;
|
||||||
/** @enum {string} */
|
isDeleted: boolean;
|
||||||
type: "MergingUpdate";
|
relativePath: string;
|
||||||
/** Format: date-time */
|
/** @enum {string} */
|
||||||
updatedDate: string;
|
type: "MergingUpdate";
|
||||||
vaultId: string;
|
/** Format: date-time */
|
||||||
/** Format: int64 */
|
updatedDate: string;
|
||||||
vaultUpdateId: number;
|
vaultId: string;
|
||||||
};
|
/** Format: int64 */
|
||||||
DocumentVersion: {
|
vaultUpdateId: number;
|
||||||
contentBase64: string;
|
};
|
||||||
/** Format: date-time */
|
DocumentVersion: {
|
||||||
createdDate: string;
|
contentBase64: string;
|
||||||
/** Format: uuid */
|
/** Format: date-time */
|
||||||
documentId: string;
|
createdDate: string;
|
||||||
isDeleted: boolean;
|
/** Format: uuid */
|
||||||
relativePath: string;
|
documentId: string;
|
||||||
/** Format: date-time */
|
isDeleted: boolean;
|
||||||
updatedDate: string;
|
relativePath: string;
|
||||||
vaultId: string;
|
/** Format: date-time */
|
||||||
/** Format: int64 */
|
updatedDate: string;
|
||||||
vaultUpdateId: number;
|
vaultId: string;
|
||||||
};
|
/** Format: int64 */
|
||||||
DocumentVersionWithoutContent: {
|
vaultUpdateId: number;
|
||||||
/** Format: date-time */
|
};
|
||||||
createdDate: string;
|
DocumentVersionWithoutContent: {
|
||||||
/** Format: uuid */
|
/** Format: date-time */
|
||||||
documentId: string;
|
createdDate: string;
|
||||||
isDeleted: boolean;
|
/** Format: uuid */
|
||||||
relativePath: string;
|
documentId: string;
|
||||||
/** Format: date-time */
|
isDeleted: boolean;
|
||||||
updatedDate: string;
|
relativePath: string;
|
||||||
vaultId: string;
|
/** Format: date-time */
|
||||||
/** Format: int64 */
|
updatedDate: string;
|
||||||
vaultUpdateId: number;
|
vaultId: string;
|
||||||
};
|
/** Format: int64 */
|
||||||
/** @description Response to a fetch latest documents request. */
|
vaultUpdateId: number;
|
||||||
FetchLatestDocumentsResponse: {
|
};
|
||||||
/**
|
/** @description Response to a fetch latest documents request. */
|
||||||
* Format: int64
|
FetchLatestDocumentsResponse: {
|
||||||
* @description The update ID of the latest document in the response.
|
/**
|
||||||
*/
|
* Format: int64
|
||||||
lastUpdateId: number;
|
* @description The update ID of the latest document in the response.
|
||||||
latestDocuments: components["schemas"]["DocumentVersionWithoutContent"][];
|
*/
|
||||||
};
|
lastUpdateId: number;
|
||||||
PathParams: {
|
latestDocuments: components["schemas"]["DocumentVersionWithoutContent"][];
|
||||||
vault_id: string;
|
};
|
||||||
};
|
PathParams: {
|
||||||
PathParams2: {
|
vault_id: string;
|
||||||
vault_id: string;
|
};
|
||||||
};
|
PathParams2: {
|
||||||
PathParams3: {
|
vault_id: string;
|
||||||
/** Format: uuid */
|
};
|
||||||
document_id: string;
|
PathParams3: {
|
||||||
vault_id: string;
|
/** Format: uuid */
|
||||||
};
|
document_id: string;
|
||||||
PathParams4: {
|
vault_id: string;
|
||||||
/** Format: uuid */
|
};
|
||||||
document_id: string;
|
PathParams4: {
|
||||||
vault_id: string;
|
/** Format: uuid */
|
||||||
};
|
document_id: string;
|
||||||
PathParams5: {
|
vault_id: string;
|
||||||
/** Format: uuid */
|
};
|
||||||
document_id: string;
|
PathParams5: {
|
||||||
vault_id: string;
|
/** Format: uuid */
|
||||||
};
|
document_id: string;
|
||||||
/** @description Response to a ping request. */
|
vault_id: string;
|
||||||
PingResponse: {
|
};
|
||||||
/** @description Whether the client is authenticated based on the sent Authorization header. */
|
/** @description Response to a ping request. */
|
||||||
isAuthenticated: boolean;
|
PingResponse: {
|
||||||
/** @description Semantic version of the server. */
|
/** @description Whether the client is authenticated based on the sent Authorization header. */
|
||||||
serverVersion: string;
|
isAuthenticated: boolean;
|
||||||
};
|
/** @description Semantic version of the server. */
|
||||||
QueryParams: {
|
serverVersion: string;
|
||||||
/** Format: int64 */
|
};
|
||||||
since_update_id?: number | null;
|
QueryParams: {
|
||||||
};
|
/** Format: int64 */
|
||||||
SerializedError: {
|
since_update_id?: number | null;
|
||||||
causes: string[];
|
};
|
||||||
message: string;
|
SerializedError: {
|
||||||
};
|
causes: string[];
|
||||||
UpdateDocumentVersion: {
|
message: string;
|
||||||
contentBase64: string;
|
};
|
||||||
/** Format: date-time */
|
UpdateDocumentVersion: {
|
||||||
createdDate: string;
|
contentBase64: string;
|
||||||
/** Format: int64 */
|
/** Format: date-time */
|
||||||
parentVersionId: number;
|
createdDate: string;
|
||||||
relativePath: string;
|
/** Format: int64 */
|
||||||
};
|
parentVersionId: number;
|
||||||
};
|
relativePath: string;
|
||||||
responses: never;
|
};
|
||||||
parameters: never;
|
};
|
||||||
requestBodies: never;
|
responses: never;
|
||||||
headers: never;
|
parameters: never;
|
||||||
pathItems: never;
|
requestBodies: never;
|
||||||
|
headers: never;
|
||||||
|
pathItems: never;
|
||||||
}
|
}
|
||||||
export type $defs = Record<string, never>;
|
export type $defs = Record<string, never>;
|
||||||
export type operations = Record<string, never>;
|
export type operations = Record<string, never>;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ let isRunning = false;
|
||||||
export async function applyRemoteChangesLocally({
|
export async function applyRemoteChangesLocally({
|
||||||
database,
|
database,
|
||||||
syncService,
|
syncService,
|
||||||
syncer,
|
syncer
|
||||||
}: {
|
}: {
|
||||||
database: Database;
|
database: Database;
|
||||||
syncService: SyncService;
|
syncService: SyncService;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
tryLockDocument,
|
tryLockDocument,
|
||||||
waitForDocumentLock,
|
waitForDocumentLock,
|
||||||
unlockDocument,
|
unlockDocument
|
||||||
} from "./document-lock";
|
} from "./document-lock";
|
||||||
import type { RelativePath } from "src/database/document-metadata";
|
import type { RelativePath } from "src/database/document-metadata";
|
||||||
|
|
||||||
|
|
@ -34,9 +34,9 @@ describe("Document Lock Operations", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should throw an error when unlocking a document that is not locked", () => {
|
test("should throw an error when unlocking a document that is not locked", () => {
|
||||||
expect(() => { unlockDocument(testPath); }).toThrow(
|
expect(() => {
|
||||||
`Document ${testPath} is not locked, cannot unlock`
|
unlockDocument(testPath);
|
||||||
);
|
}).toThrow(`Document ${testPath} is not locked, cannot unlock`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should wait for a document lock and resolve when unlocked", async () => {
|
test("should wait for a document lock and resolve when unlocked", async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Database } from "src/database/database";
|
import type { Database } from "src/database/database";
|
||||||
import type {
|
import type {
|
||||||
DocumentMetadata,
|
DocumentMetadata,
|
||||||
RelativePath,
|
RelativePath
|
||||||
} from "src/database/document-metadata";
|
} from "src/database/document-metadata";
|
||||||
import type { FileOperations } from "src/file-operations/file-operations";
|
import type { FileOperations } from "src/file-operations/file-operations";
|
||||||
import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js";
|
|
||||||
import type { SyncService } from "src/services/sync-service";
|
import type { SyncService } from "src/services/sync-service";
|
||||||
import { Logger } from "src/tracing/logger";
|
import { Logger } from "src/tracing/logger";
|
||||||
import type { SyncHistory } from "src/tracing/sync-history";
|
import type { SyncHistory } from "src/tracing/sync-history";
|
||||||
|
|
@ -12,7 +11,8 @@ import { SyncSource, SyncStatus, SyncType } from "src/tracing/sync-history";
|
||||||
import { unlockDocument, waitForDocumentLock } from "./document-lock";
|
import { unlockDocument, waitForDocumentLock } from "./document-lock";
|
||||||
import PQueue from "p-queue";
|
import PQueue from "p-queue";
|
||||||
import { EMPTY_HASH, hash } from "src/utils/hash";
|
import { EMPTY_HASH, hash } from "src/utils/hash";
|
||||||
import type { components } from "src/services/types.js";
|
import type { components } from "src/services/types";
|
||||||
|
import { base64ToBytes } from "sync_lib";
|
||||||
|
|
||||||
export class Syncer {
|
export class Syncer {
|
||||||
private readonly remainingOperationsListeners: ((
|
private readonly remainingOperationsListeners: ((
|
||||||
|
|
@ -30,7 +30,7 @@ export class Syncer {
|
||||||
private readonly history: SyncHistory
|
private readonly history: SyncHistory
|
||||||
) {
|
) {
|
||||||
this.syncQueue = new PQueue({
|
this.syncQueue = new PQueue({
|
||||||
concurrency: database.getSettings().syncConcurrency,
|
concurrency: database.getSettings().syncConcurrency
|
||||||
});
|
});
|
||||||
|
|
||||||
database.addOnSettingsChangeHandlers((settings) => {
|
database.addOnSettingsChangeHandlers((settings) => {
|
||||||
|
|
@ -78,9 +78,8 @@ export class Syncer {
|
||||||
public async syncRemotelyUpdatedFile(
|
public async syncRemotelyUpdatedFile(
|
||||||
remoteVersion: components["schemas"]["DocumentVersionWithoutContent"]
|
remoteVersion: components["schemas"]["DocumentVersionWithoutContent"]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.syncQueue.add(
|
await this.syncQueue.add(async () =>
|
||||||
async () =>
|
this.internalSyncRemotelyUpdatedFile(remoteVersion)
|
||||||
this.internalSyncRemotelyUpdatedFile(remoteVersion)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +103,7 @@ export class Syncer {
|
||||||
try {
|
try {
|
||||||
const allLocalFiles = await this.operations.listAllFiles();
|
const allLocalFiles = await this.operations.listAllFiles();
|
||||||
const locallyDeletedFiles = [
|
const locallyDeletedFiles = [
|
||||||
...this.database.getDocuments().entries(),
|
...this.database.getDocuments().entries()
|
||||||
].filter(([path, _]) => !allLocalFiles.includes(path));
|
].filter(([path, _]) => !allLocalFiles.includes(path));
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|
@ -134,7 +133,7 @@ export class Syncer {
|
||||||
updateTime:
|
updateTime:
|
||||||
await this.operations.getModificationTime(
|
await this.operations.getModificationTime(
|
||||||
relativePath
|
relativePath
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +156,7 @@ export class Syncer {
|
||||||
updateTime:
|
updateTime:
|
||||||
await this.operations.getModificationTime(
|
await this.operations.getModificationTime(
|
||||||
relativePath
|
relativePath
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
@ -218,7 +217,7 @@ export class Syncer {
|
||||||
status: SyncStatus.NO_OP,
|
status: SyncStatus.NO_OP,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `File hash matches with last synced version, no need to sync`,
|
message: `File hash matches with last synced version, no need to sync`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -227,7 +226,7 @@ export class Syncer {
|
||||||
const response = await this.syncService.create({
|
const response = await this.syncService.create({
|
||||||
relativePath,
|
relativePath,
|
||||||
contentBytes,
|
contentBytes,
|
||||||
createdDate: updateTime,
|
createdDate: updateTime
|
||||||
});
|
});
|
||||||
|
|
||||||
this.history.addHistoryEntry({
|
this.history.addHistoryEntry({
|
||||||
|
|
@ -235,13 +234,11 @@ export class Syncer {
|
||||||
source: SyncSource.PUSH,
|
source: SyncSource.PUSH,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `Successfully uploaded locally created file`,
|
message: `Successfully uploaded locally created file`,
|
||||||
type: SyncType.CREATE,
|
type: SyncType.CREATE
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.type === "MergingUpdate") {
|
if (response.type === "MergingUpdate") {
|
||||||
const responseBytes = lib.base64ToBytes(
|
const responseBytes = base64ToBytes(response.contentBase64);
|
||||||
response.contentBase64
|
|
||||||
);
|
|
||||||
contentHash = hash(responseBytes);
|
contentHash = hash(responseBytes);
|
||||||
|
|
||||||
await this.operations.write(
|
await this.operations.write(
|
||||||
|
|
@ -254,7 +251,7 @@ export class Syncer {
|
||||||
source: SyncSource.PULL,
|
source: SyncSource.PULL,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `The file we created locally has already existed remotely, so we have merged them`,
|
message: `The file we created locally has already existed remotely, so we have merged them`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +259,7 @@ export class Syncer {
|
||||||
documentId: response.documentId,
|
documentId: response.documentId,
|
||||||
relativePath: response.relativePath,
|
relativePath: response.relativePath,
|
||||||
parentVersionId: response.vaultUpdateId,
|
parentVersionId: response.vaultUpdateId,
|
||||||
hash: contentHash,
|
hash: contentHash
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.tryIncrementVaultUpdateId(response.vaultUpdateId);
|
await this.tryIncrementVaultUpdateId(response.vaultUpdateId);
|
||||||
|
|
@ -273,7 +270,7 @@ export class Syncer {
|
||||||
private async internalSyncLocallyUpdatedFile({
|
private async internalSyncLocallyUpdatedFile({
|
||||||
oldPath,
|
oldPath,
|
||||||
relativePath,
|
relativePath,
|
||||||
updateTime,
|
updateTime
|
||||||
}: {
|
}: {
|
||||||
oldPath?: RelativePath;
|
oldPath?: RelativePath;
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath;
|
||||||
|
|
@ -293,7 +290,7 @@ export class Syncer {
|
||||||
status: SyncStatus.NO_OP,
|
status: SyncStatus.NO_OP,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `The renaming doesn't require a sync because it must have been pulled from remote`,
|
message: `The renaming doesn't require a sync because it must have been pulled from remote`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -314,7 +311,7 @@ export class Syncer {
|
||||||
status: SyncStatus.NO_OP,
|
status: SyncStatus.NO_OP,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `File hash matches with last synced version, no need to sync`,
|
message: `File hash matches with last synced version, no need to sync`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +321,7 @@ export class Syncer {
|
||||||
parentVersionId: localMetadata.parentVersionId,
|
parentVersionId: localMetadata.parentVersionId,
|
||||||
relativePath,
|
relativePath,
|
||||||
contentBytes,
|
contentBytes,
|
||||||
createdDate: updateTime,
|
createdDate: updateTime
|
||||||
});
|
});
|
||||||
|
|
||||||
this.history.addHistoryEntry({
|
this.history.addHistoryEntry({
|
||||||
|
|
@ -332,7 +329,7 @@ export class Syncer {
|
||||||
source: SyncSource.PUSH,
|
source: SyncSource.PUSH,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `Successfully uploaded locally updated file to the remote server`,
|
message: `Successfully uploaded locally updated file to the remote server`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.isDeleted) {
|
if (response.isDeleted) {
|
||||||
|
|
@ -348,7 +345,7 @@ export class Syncer {
|
||||||
relativePath,
|
relativePath,
|
||||||
message:
|
message:
|
||||||
"The file we tried to update had been deleted remotely, therefore, we have deleted it locally",
|
"The file we tried to update had been deleted remotely, therefore, we have deleted it locally",
|
||||||
type: SyncType.DELETE,
|
type: SyncType.DELETE
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -367,7 +364,7 @@ export class Syncer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.type === "MergingUpdate") {
|
if (response.type === "MergingUpdate") {
|
||||||
const responseBytes = lib.base64ToBytes(
|
const responseBytes = base64ToBytes(
|
||||||
response.contentBase64
|
response.contentBase64
|
||||||
);
|
);
|
||||||
contentHash = hash(responseBytes);
|
contentHash = hash(responseBytes);
|
||||||
|
|
@ -383,7 +380,7 @@ export class Syncer {
|
||||||
source: SyncSource.PULL,
|
source: SyncSource.PULL,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `The file we updated had been updated remotely, so we downloaded the merged version`,
|
message: `The file we updated had been updated remotely, so we downloaded the merged version`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.database.moveDocument({
|
await this.database.moveDocument({
|
||||||
|
|
@ -391,7 +388,7 @@ export class Syncer {
|
||||||
oldRelativePath: oldPath ?? relativePath,
|
oldRelativePath: oldPath ?? relativePath,
|
||||||
relativePath: response.relativePath,
|
relativePath: response.relativePath,
|
||||||
parentVersionId: response.vaultUpdateId,
|
parentVersionId: response.vaultUpdateId,
|
||||||
hash: contentHash,
|
hash: contentHash
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.tryIncrementVaultUpdateId(
|
await this.tryIncrementVaultUpdateId(
|
||||||
|
|
@ -420,7 +417,7 @@ export class Syncer {
|
||||||
status: SyncStatus.NO_OP,
|
status: SyncStatus.NO_OP,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `Locally deleted file hasn't been uploaded yet, so there's no need to delete it on the remote server`,
|
message: `Locally deleted file hasn't been uploaded yet, so there's no need to delete it on the remote server`,
|
||||||
type: SyncType.DELETE,
|
type: SyncType.DELETE
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -428,7 +425,7 @@ export class Syncer {
|
||||||
await this.syncService.delete({
|
await this.syncService.delete({
|
||||||
documentId: localMetadata.documentId,
|
documentId: localMetadata.documentId,
|
||||||
relativePath,
|
relativePath,
|
||||||
createdDate: new Date(), // We got the event now, so it must have been deleted just now
|
createdDate: new Date() // We got the event now, so it must have been deleted just now
|
||||||
});
|
});
|
||||||
|
|
||||||
this.history.addHistoryEntry({
|
this.history.addHistoryEntry({
|
||||||
|
|
@ -436,7 +433,7 @@ export class Syncer {
|
||||||
source: SyncSource.PUSH,
|
source: SyncSource.PUSH,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `Successfully deleted locally deleted file on the remote server`,
|
message: `Successfully deleted locally deleted file on the remote server`,
|
||||||
type: SyncType.DELETE,
|
type: SyncType.DELETE
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.database.removeDocument(relativePath);
|
await this.database.removeDocument(relativePath);
|
||||||
|
|
@ -463,17 +460,17 @@ export class Syncer {
|
||||||
source: SyncSource.PULL,
|
source: SyncSource.PULL,
|
||||||
relativePath: remoteVersion.relativePath,
|
relativePath: remoteVersion.relativePath,
|
||||||
message: `Remotely deleted file hasn't been synced yet, so there's no need to delete it locally`,
|
message: `Remotely deleted file hasn't been synced yet, so there's no need to delete it locally`,
|
||||||
type: SyncType.DELETE,
|
type: SyncType.DELETE
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
await this.syncService.get({
|
await this.syncService.get({
|
||||||
documentId: remoteVersion.documentId,
|
documentId: remoteVersion.documentId
|
||||||
})
|
})
|
||||||
).contentBase64;
|
).contentBase64;
|
||||||
const contentBytes = lib.base64ToBytes(content);
|
const contentBytes = base64ToBytes(content);
|
||||||
|
|
||||||
await this.operations.create(
|
await this.operations.create(
|
||||||
remoteVersion.relativePath,
|
remoteVersion.relativePath,
|
||||||
|
|
@ -483,14 +480,14 @@ export class Syncer {
|
||||||
documentId: remoteVersion.documentId,
|
documentId: remoteVersion.documentId,
|
||||||
relativePath: remoteVersion.relativePath,
|
relativePath: remoteVersion.relativePath,
|
||||||
parentVersionId: remoteVersion.vaultUpdateId,
|
parentVersionId: remoteVersion.vaultUpdateId,
|
||||||
hash: hash(contentBytes),
|
hash: hash(contentBytes)
|
||||||
});
|
});
|
||||||
this.history.addHistoryEntry({
|
this.history.addHistoryEntry({
|
||||||
status: SyncStatus.SUCCESS,
|
status: SyncStatus.SUCCESS,
|
||||||
source: SyncSource.PULL,
|
source: SyncSource.PULL,
|
||||||
relativePath: remoteVersion.relativePath,
|
relativePath: remoteVersion.relativePath,
|
||||||
message: `Successfully downloaded remote file which hasn't existed locally`,
|
message: `Successfully downloaded remote file which hasn't existed locally`,
|
||||||
type: SyncType.CREATE,
|
type: SyncType.CREATE
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -516,12 +513,11 @@ export class Syncer {
|
||||||
source: SyncSource.PULL,
|
source: SyncSource.PULL,
|
||||||
relativePath: remoteVersion.relativePath,
|
relativePath: remoteVersion.relativePath,
|
||||||
message: `Successfully deleted remotely deleted file locally`,
|
message: `Successfully deleted remotely deleted file locally`,
|
||||||
type: SyncType.DELETE,
|
type: SyncType.DELETE
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const currentContent = await this.operations.read(
|
const currentContent =
|
||||||
relativePath
|
await this.operations.read(relativePath);
|
||||||
);
|
|
||||||
const currentHash = hash(currentContent);
|
const currentHash = hash(currentContent);
|
||||||
|
|
||||||
if (currentHash !== metadata.hash) {
|
if (currentHash !== metadata.hash) {
|
||||||
|
|
@ -533,10 +529,10 @@ export class Syncer {
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
await this.syncService.get({
|
await this.syncService.get({
|
||||||
documentId: remoteVersion.documentId,
|
documentId: remoteVersion.documentId
|
||||||
})
|
})
|
||||||
).contentBase64;
|
).contentBase64;
|
||||||
const contentBytes = lib.base64ToBytes(content);
|
const contentBytes = base64ToBytes(content);
|
||||||
const contentHash = hash(contentBytes);
|
const contentHash = hash(contentBytes);
|
||||||
|
|
||||||
if (relativePath !== remoteVersion.relativePath) {
|
if (relativePath !== remoteVersion.relativePath) {
|
||||||
|
|
@ -556,7 +552,7 @@ export class Syncer {
|
||||||
oldRelativePath: relativePath,
|
oldRelativePath: relativePath,
|
||||||
relativePath: remoteVersion.relativePath,
|
relativePath: remoteVersion.relativePath,
|
||||||
parentVersionId: remoteVersion.vaultUpdateId,
|
parentVersionId: remoteVersion.vaultUpdateId,
|
||||||
hash: contentHash,
|
hash: contentHash
|
||||||
});
|
});
|
||||||
|
|
||||||
this.history.addHistoryEntry({
|
this.history.addHistoryEntry({
|
||||||
|
|
@ -564,7 +560,7 @@ export class Syncer {
|
||||||
source: SyncSource.PULL,
|
source: SyncSource.PULL,
|
||||||
relativePath: remoteVersion.relativePath,
|
relativePath: remoteVersion.relativePath,
|
||||||
message: `Successfully updated remotely updated file locally`,
|
message: `Successfully updated remotely updated file locally`,
|
||||||
type: SyncType.UPDATE,
|
type: SyncType.UPDATE
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -599,7 +595,7 @@ export class Syncer {
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `Failed to ${syncSource.toLocaleLowerCase()} file ${e} when trying to ${syncType.toLocaleLowerCase()} it`,
|
message: `Failed to ${syncSource.toLocaleLowerCase()} file ${e} when trying to ${syncType.toLocaleLowerCase()} it`,
|
||||||
type: syncType,
|
type: syncType,
|
||||||
source: syncSource,
|
source: syncSource
|
||||||
});
|
});
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,22 @@ export enum LogLevel {
|
||||||
DEBUG = "DEBUG",
|
DEBUG = "DEBUG",
|
||||||
INFO = "INFO",
|
INFO = "INFO",
|
||||||
WARNING = "WARNING",
|
WARNING = "WARNING",
|
||||||
ERROR = "ERROR",
|
ERROR = "ERROR"
|
||||||
}
|
}
|
||||||
|
|
||||||
const LOG_LEVEL_ORDER = {
|
const LOG_LEVEL_ORDER = {
|
||||||
[LogLevel.DEBUG]: 0,
|
[LogLevel.DEBUG]: 0,
|
||||||
[LogLevel.INFO]: 1,
|
[LogLevel.INFO]: 1,
|
||||||
[LogLevel.WARNING]: 2,
|
[LogLevel.WARNING]: 2,
|
||||||
[LogLevel.ERROR]: 3,
|
[LogLevel.ERROR]: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
class LogLine {
|
class LogLine {
|
||||||
public timestamp = new Date();
|
public timestamp = new Date();
|
||||||
public constructor(public level: LogLevel, public message: string) {}
|
public constructor(
|
||||||
|
public level: LogLevel,
|
||||||
|
public message: string
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Logger {
|
export class Logger {
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,18 @@ export interface CommonHistoryEntry {
|
||||||
export enum SyncType {
|
export enum SyncType {
|
||||||
CREATE = "CREATE",
|
CREATE = "CREATE",
|
||||||
UPDATE = "UPDATE",
|
UPDATE = "UPDATE",
|
||||||
DELETE = "DELETE",
|
DELETE = "DELETE"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SyncSource {
|
export enum SyncSource {
|
||||||
PUSH = "PUSH",
|
PUSH = "PUSH",
|
||||||
PULL = "PULL",
|
PULL = "PULL"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SyncStatus {
|
export enum SyncStatus {
|
||||||
NO_OP = "NO_OP",
|
NO_OP = "NO_OP",
|
||||||
SUCCESS = "SUCCESS",
|
SUCCESS = "SUCCESS",
|
||||||
ERROR = "ERROR",
|
ERROR = "ERROR"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HistoryEntry = CommonHistoryEntry & { timestamp: Date };
|
export type HistoryEntry = CommonHistoryEntry & { timestamp: Date };
|
||||||
|
|
@ -44,7 +44,7 @@ export class SyncHistory {
|
||||||
|
|
||||||
private status: HistoryStats = {
|
private status: HistoryStats = {
|
||||||
success: 0,
|
success: 0,
|
||||||
error: 0,
|
error: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
public getEntries(): HistoryEntry[] {
|
public getEntries(): HistoryEntry[] {
|
||||||
|
|
@ -55,7 +55,7 @@ export class SyncHistory {
|
||||||
this.entries.length = 0;
|
this.entries.length = 0;
|
||||||
this.status = {
|
this.status = {
|
||||||
success: 0,
|
success: 0,
|
||||||
error: 0,
|
error: 0
|
||||||
};
|
};
|
||||||
this.syncHistoryUpdateListeners.forEach((listener) => {
|
this.syncHistoryUpdateListeners.forEach((listener) => {
|
||||||
listener(this.status);
|
listener(this.status);
|
||||||
|
|
@ -72,7 +72,7 @@ export class SyncHistory {
|
||||||
public addHistoryEntry(entry: CommonHistoryEntry): void {
|
public addHistoryEntry(entry: CommonHistoryEntry): void {
|
||||||
const historyEntry = {
|
const historyEntry = {
|
||||||
...entry,
|
...entry,
|
||||||
timestamp: new Date(),
|
timestamp: new Date()
|
||||||
};
|
};
|
||||||
this.entries.push(historyEntry);
|
this.entries.push(historyEntry);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ export async function retriedFetch(
|
||||||
retryOn: function (attempt, error, response) {
|
retryOn: function (attempt, error, response) {
|
||||||
if (error !== null || !response || response.status >= 500) {
|
if (error !== null || !response || response.status >= 500) {
|
||||||
Logger.getInstance().warn(
|
Logger.getInstance().warn(
|
||||||
`Retrying fetch for ${getUrlFromInput(
|
`Retrying fetch for ${getUrlFromInput(input)}, attempt ${attempt}`
|
||||||
input
|
|
||||||
)}, attempt ${attempt}`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -33,6 +31,6 @@ export async function retriedFetch(
|
||||||
},
|
},
|
||||||
retries: 6,
|
retries: 6,
|
||||||
retryDelay: (attempt) => Math.pow(1.5, attempt) * 500,
|
retryDelay: (attempt) => Math.pow(1.5, attempt) * 500,
|
||||||
...init,
|
...init
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
import type { WorkspaceLeaf } from "obsidian";
|
import type { WorkspaceLeaf } from "obsidian";
|
||||||
import { Plugin } from "obsidian";
|
import { Plugin } from "obsidian";
|
||||||
|
import "./styles.scss";
|
||||||
import * as lib from "../../backend/sync_lib/pkg/sync_lib.js";
|
import "../manifest.json";
|
||||||
import * as wasmBin from "../../backend/sync_lib/pkg/sync_lib_bg.wasm";
|
import init, { setPanicHook } from "sync_lib";
|
||||||
import { SyncSettingsTab } from "./views/settings-tab.js";
|
import wasmBin from "sync_lib/sync_lib_bg.wasm";
|
||||||
import { HistoryView } from "./views/history-view.js";
|
import { SyncSettingsTab } from "./views/settings-tab";
|
||||||
|
import { HistoryView } from "./views/history-view";
|
||||||
import { ObsidianFileEventHandler } from "./events/obisidan-event-handler.js";
|
import { ObsidianFileEventHandler } from "./events/obisidan-event-handler";
|
||||||
import { SyncService } from "./services/sync-service.js";
|
import { SyncService } from "./services/sync-service";
|
||||||
import { Database } from "./database/database.js";
|
import { Database } from "./database/database";
|
||||||
import { applyRemoteChangesLocally } from "./sync-operations/apply-remote-changes-locally.js";
|
import { applyRemoteChangesLocally } from "./sync-operations/apply-remote-changes-locally";
|
||||||
import { ObsidianFileOperations } from "./file-operations/obsidian-file-operations.js";
|
import { ObsidianFileOperations } from "./file-operations/obsidian-file-operations";
|
||||||
import { StatusBar } from "./views/status-bar.js";
|
import { StatusBar } from "./views/status-bar";
|
||||||
import { Logger } from "./tracing/logger.js";
|
import { Logger } from "./tracing/logger";
|
||||||
import { SyncHistory } from "./tracing/sync-history.js";
|
import { SyncHistory } from "./tracing/sync-history";
|
||||||
import { LogsView } from "./views/logs-view.js";
|
import { LogsView } from "./views/logs-view";
|
||||||
import { Syncer } from "./sync-operations/syncer.js";
|
import { Syncer } from "./sync-operations/syncer";
|
||||||
import { StatusDescription } from "./views/status-description.js";
|
import { StatusDescription } from "./views/status-description";
|
||||||
|
|
||||||
export default class VaultLinkPlugin extends Plugin {
|
export default class VaultLinkPlugin extends Plugin {
|
||||||
private readonly operations = new ObsidianFileOperations(this.app.vault);
|
private readonly operations = new ObsidianFileOperations(this.app.vault);
|
||||||
|
|
@ -27,14 +27,10 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
public async onload(): Promise<void> {
|
public async onload(): Promise<void> {
|
||||||
Logger.getInstance().info("Starting plugin");
|
Logger.getInstance().info("Starting plugin");
|
||||||
|
|
||||||
await lib.default(
|
// eslint-disable-next-line
|
||||||
Promise.resolve(
|
await init((wasmBin as any).default);
|
||||||
// eslint-disable-next-line
|
|
||||||
(wasmBin as any).default
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
lib.setPanicHook();
|
setPanicHook();
|
||||||
|
|
||||||
const database = new Database(
|
const database = new Database(
|
||||||
await this.loadData(),
|
await this.loadData(),
|
||||||
|
|
@ -63,7 +59,7 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
database,
|
database,
|
||||||
syncService,
|
syncService,
|
||||||
statusDescription,
|
statusDescription,
|
||||||
syncer,
|
syncer
|
||||||
});
|
});
|
||||||
this.addSettingTab(this.settingsTab);
|
this.addSettingTab(this.settingsTab);
|
||||||
|
|
||||||
|
|
@ -90,7 +86,7 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
this.app.vault.on(
|
this.app.vault.on(
|
||||||
"rename",
|
"rename",
|
||||||
eventHandler.onRename.bind(eventHandler)
|
eventHandler.onRename.bind(eventHandler)
|
||||||
),
|
)
|
||||||
].forEach((event) => {
|
].forEach((event) => {
|
||||||
this.registerEvent(event);
|
this.registerEvent(event);
|
||||||
});
|
});
|
||||||
|
|
@ -196,7 +192,7 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
applyRemoteChangesLocally({
|
applyRemoteChangesLocally({
|
||||||
database,
|
database,
|
||||||
syncService,
|
syncService,
|
||||||
syncer,
|
syncer
|
||||||
}),
|
}),
|
||||||
intervalMs
|
intervalMs
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export class HistoryView extends ItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
element.createEl("span", {
|
element.createEl("span", {
|
||||||
text: entry.relativePath,
|
text: entry.relativePath
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncSourceIcon = HistoryView.getSyncSourceIcon(entry.source);
|
const syncSourceIcon = HistoryView.getSyncSourceIcon(entry.source);
|
||||||
|
|
@ -107,7 +107,7 @@ export class HistoryView extends ItemView {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
container.createDiv(
|
container.createDiv(
|
||||||
{
|
{
|
||||||
cls: ["history-card", entry.status.toLocaleLowerCase()],
|
cls: ["history-card", entry.status.toLocaleLowerCase()]
|
||||||
},
|
},
|
||||||
(card) => {
|
(card) => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -127,13 +127,13 @@ export class HistoryView extends ItemView {
|
||||||
|
|
||||||
card.createDiv(
|
card.createDiv(
|
||||||
{
|
{
|
||||||
cls: "history-card-header",
|
cls: "history-card-header"
|
||||||
},
|
},
|
||||||
(header) => {
|
(header) => {
|
||||||
header.createEl(
|
header.createEl(
|
||||||
"h5",
|
"h5",
|
||||||
{
|
{
|
||||||
cls: "history-card-title",
|
cls: "history-card-title"
|
||||||
},
|
},
|
||||||
(title) => {
|
(title) => {
|
||||||
HistoryView.renderSyncItemTitle(
|
HistoryView.renderSyncItemTitle(
|
||||||
|
|
@ -148,14 +148,14 @@ export class HistoryView extends ItemView {
|
||||||
entry.timestamp,
|
entry.timestamp,
|
||||||
new Date()
|
new Date()
|
||||||
),
|
),
|
||||||
cls: "history-card-timestamp",
|
cls: "history-card-timestamp"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
card.createEl("p", {
|
card.createEl("p", {
|
||||||
text: `${entry.message}.`,
|
text: `${entry.message}.`,
|
||||||
cls: "history-card-message",
|
cls: "history-card-message"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,13 @@ export class LogsView extends ItemView {
|
||||||
container.createEl(
|
container.createEl(
|
||||||
"p",
|
"p",
|
||||||
{
|
{
|
||||||
text: "This view displays logs generated by VaultLink. You can set the log level in the ",
|
text: "This view displays logs generated by VaultLink. You can set the log level in the "
|
||||||
},
|
},
|
||||||
(p) => {
|
(p) => {
|
||||||
p.createEl(
|
p.createEl(
|
||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
text: "settings",
|
text: "settings"
|
||||||
},
|
},
|
||||||
(button) => {
|
(button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
|
|
@ -95,17 +95,17 @@ export class LogsView extends ItemView {
|
||||||
logs.forEach((message) =>
|
logs.forEach((message) =>
|
||||||
element.createDiv(
|
element.createDiv(
|
||||||
{
|
{
|
||||||
cls: ["log-message", message.level],
|
cls: ["log-message", message.level]
|
||||||
},
|
},
|
||||||
(messageContainer) => {
|
(messageContainer) => {
|
||||||
messageContainer.createEl("span", {
|
messageContainer.createEl("span", {
|
||||||
text: LogsView.formatTimestamp(
|
text: LogsView.formatTimestamp(
|
||||||
message.timestamp
|
message.timestamp
|
||||||
),
|
),
|
||||||
cls: "timestamp",
|
cls: "timestamp"
|
||||||
});
|
});
|
||||||
messageContainer.createEl("span", {
|
messageContainer.createEl("span", {
|
||||||
text: message.message,
|
text: message.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import { Notice, PluginSettingTab, Setting } from "obsidian";
|
||||||
import type VaultLinkPlugin from "src/vault-link-plugin";
|
import type VaultLinkPlugin from "src/vault-link-plugin";
|
||||||
import type { Database } from "src/database/database";
|
import type { Database } from "src/database/database";
|
||||||
import type { SyncService } from "src/services/sync-service";
|
import type { SyncService } from "src/services/sync-service";
|
||||||
import { Logger, LogLevel } from "src/tracing/logger";
|
|
||||||
import type { Syncer } from "src/sync-operations/syncer";
|
import type { Syncer } from "src/sync-operations/syncer";
|
||||||
import type { StatusDescription } from "./status-description";
|
import type { StatusDescription } from "./status-description";
|
||||||
import { LogsView } from "./logs-view";
|
import { LogsView } from "./logs-view";
|
||||||
import { HistoryView } from "./history-view";
|
import { HistoryView } from "./history-view";
|
||||||
|
import { Logger, LogLevel } from "src/tracing/logger";
|
||||||
|
|
||||||
export class SyncSettingsTab extends PluginSettingTab {
|
export class SyncSettingsTab extends PluginSettingTab {
|
||||||
private editedVaultName: string;
|
private editedVaultName: string;
|
||||||
|
|
@ -26,7 +26,7 @@ export class SyncSettingsTab extends PluginSettingTab {
|
||||||
database,
|
database,
|
||||||
syncService,
|
syncService,
|
||||||
statusDescription,
|
statusDescription,
|
||||||
syncer,
|
syncer
|
||||||
}: {
|
}: {
|
||||||
app: App;
|
app: App;
|
||||||
plugin: VaultLinkPlugin;
|
plugin: VaultLinkPlugin;
|
||||||
|
|
@ -72,12 +72,12 @@ export class SyncSettingsTab extends PluginSettingTab {
|
||||||
private renderSettingsHeader(containerEl: HTMLElement): void {
|
private renderSettingsHeader(containerEl: HTMLElement): void {
|
||||||
containerEl.createEl("h2", { text: "VaultLink" }).createSpan({
|
containerEl.createEl("h2", { text: "VaultLink" }).createSpan({
|
||||||
text: this.plugin.manifest.version,
|
text: this.plugin.manifest.version,
|
||||||
cls: "version",
|
cls: "version"
|
||||||
});
|
});
|
||||||
|
|
||||||
containerEl.createDiv(
|
containerEl.createDiv(
|
||||||
{
|
{
|
||||||
cls: "description",
|
cls: "description"
|
||||||
},
|
},
|
||||||
(descriptionContainer) => {
|
(descriptionContainer) => {
|
||||||
this.setStatusDescriptionSubscription((): void => {
|
this.setStatusDescriptionSubscription((): void => {
|
||||||
|
|
@ -90,13 +90,13 @@ export class SyncSettingsTab extends PluginSettingTab {
|
||||||
|
|
||||||
containerEl.createDiv(
|
containerEl.createDiv(
|
||||||
{
|
{
|
||||||
cls: "button-container",
|
cls: "button-container"
|
||||||
},
|
},
|
||||||
(buttonContainer) => {
|
(buttonContainer) => {
|
||||||
buttonContainer.createEl(
|
buttonContainer.createEl(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
text: "Show history",
|
text: "Show history"
|
||||||
},
|
},
|
||||||
(button) =>
|
(button) =>
|
||||||
(button.onclick = async (): Promise<void> => {
|
(button.onclick = async (): Promise<void> => {
|
||||||
|
|
@ -108,7 +108,7 @@ export class SyncSettingsTab extends PluginSettingTab {
|
||||||
buttonContainer.createEl(
|
buttonContainer.createEl(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
text: "Show logs",
|
text: "Show logs"
|
||||||
},
|
},
|
||||||
(button) =>
|
(button) =>
|
||||||
(button.onclick = async (): Promise<void> => {
|
(button.onclick = async (): Promise<void> => {
|
||||||
|
|
@ -296,7 +296,7 @@ export class SyncSettingsTab extends PluginSettingTab {
|
||||||
[LogLevel.DEBUG]: LogLevel.DEBUG,
|
[LogLevel.DEBUG]: LogLevel.DEBUG,
|
||||||
[LogLevel.INFO]: LogLevel.INFO,
|
[LogLevel.INFO]: LogLevel.INFO,
|
||||||
[LogLevel.WARNING]: LogLevel.WARNING,
|
[LogLevel.WARNING]: LogLevel.WARNING,
|
||||||
[LogLevel.ERROR]: LogLevel.ERROR,
|
[LogLevel.ERROR]: LogLevel.ERROR
|
||||||
})
|
})
|
||||||
.onChange(async (value) =>
|
.onChange(async (value) =>
|
||||||
this.database.setSetting(
|
this.database.setSetting(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export class StatusBar {
|
||||||
private updateStatus(): void {
|
private updateStatus(): void {
|
||||||
this.statusBarItem.empty();
|
this.statusBarItem.empty();
|
||||||
const container = this.statusBarItem.createDiv({
|
const container = this.statusBarItem.createDiv({
|
||||||
cls: ["sync-status"],
|
cls: ["sync-status"]
|
||||||
});
|
});
|
||||||
|
|
||||||
let hasShownMessage = false;
|
let hasShownMessage = false;
|
||||||
|
|
@ -47,14 +47,14 @@ export class StatusBar {
|
||||||
if ((this.lastHistoryStats?.success ?? 0) > 0) {
|
if ((this.lastHistoryStats?.success ?? 0) > 0) {
|
||||||
hasShownMessage = true;
|
hasShownMessage = true;
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `${this.lastHistoryStats?.success ?? 0} ✅`,
|
text: `${this.lastHistoryStats?.success ?? 0} ✅`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.lastHistoryStats?.error ?? 0) > 0) {
|
if ((this.lastHistoryStats?.error ?? 0) > 0) {
|
||||||
hasShownMessage = true;
|
hasShownMessage = true;
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `${this.lastHistoryStats?.error ?? 0} ❌`,
|
text: `${this.lastHistoryStats?.error ?? 0} ❌`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ export class StatusBar {
|
||||||
} else {
|
} else {
|
||||||
const button = container.createEl("button", {
|
const button = container.createEl("button", {
|
||||||
text: "VaultLink is disabled, click to configure",
|
text: "VaultLink is disabled, click to configure",
|
||||||
cls: "initialize-button",
|
cls: "initialize-button"
|
||||||
});
|
});
|
||||||
button.onclick = (): void => {
|
button.onclick = (): void => {
|
||||||
this.plugin.openSettings();
|
this.plugin.openSettings();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Database } from "src/database/database";
|
import type { Database } from "src/database/database";
|
||||||
import type {
|
import type {
|
||||||
CheckConnectionResult,
|
CheckConnectionResult,
|
||||||
SyncService,
|
SyncService
|
||||||
} from "src/services/sync-service";
|
} from "src/services/sync-service";
|
||||||
import type { Syncer } from "src/sync-operations/syncer";
|
import type { Syncer } from "src/sync-operations/syncer";
|
||||||
import type { HistoryStats, SyncHistory } from "src/tracing/sync-history";
|
import type { HistoryStats, SyncHistory } from "src/tracing/sync-history";
|
||||||
|
|
@ -57,7 +57,7 @@ export class StatusDescription {
|
||||||
if (this.lastConnectionState == undefined) {
|
if (this.lastConnectionState == undefined) {
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: "VaultLink is starting up…",
|
text: "VaultLink is starting up…",
|
||||||
cls: "warning",
|
cls: "warning"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ export class StatusDescription {
|
||||||
if (!this.lastConnectionState.isSuccessful) {
|
if (!this.lastConnectionState.isSuccessful) {
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `VaultLink failed to connect to the remote server with the error "${this.lastConnectionState.message}"`,
|
text: `VaultLink failed to connect to the remote server with the error "${this.lastConnectionState.message}"`,
|
||||||
cls: "error",
|
cls: "error"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -73,18 +73,18 @@ export class StatusDescription {
|
||||||
container.createSpan({ text: "VaultLink is connected to the server " });
|
container.createSpan({ text: "VaultLink is connected to the server " });
|
||||||
container.createEl("a", {
|
container.createEl("a", {
|
||||||
text: this.database.getSettings().remoteUri,
|
text: this.database.getSettings().remoteUri,
|
||||||
href: this.database.getSettings().remoteUri,
|
href: this.database.getSettings().remoteUri
|
||||||
});
|
});
|
||||||
|
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: ` and has indexed approximately `,
|
text: ` and has indexed approximately `
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `${this.database.getDocuments().size}`,
|
text: `${this.database.getDocuments().size}`,
|
||||||
cls: "number",
|
cls: "number"
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: ` documents. `,
|
text: ` documents. `
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
@ -94,40 +94,40 @@ export class StatusDescription {
|
||||||
) {
|
) {
|
||||||
if (this.database.getSettings().isSyncEnabled) {
|
if (this.database.getSettings().isSyncEnabled) {
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: "Syncing is enabled but VaultLink hasn't found anything to sync yet.",
|
text: "Syncing is enabled but VaultLink hasn't found anything to sync yet."
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: "However, syncing is disabled right now.",
|
text: "However, syncing is disabled right now.",
|
||||||
cls: "warning",
|
cls: "warning"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: "The plugin has ",
|
text: "The plugin has "
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `${this.lastRemaining ?? 0}`,
|
text: `${this.lastRemaining ?? 0}`,
|
||||||
cls: "number",
|
cls: "number"
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: " outstanding operations while having succeeded ",
|
text: " outstanding operations while having succeeded "
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `${this.lastHistoryStats?.success ?? 0}`,
|
text: `${this.lastHistoryStats?.success ?? 0}`,
|
||||||
cls: ["number", "good"],
|
cls: ["number", "good"]
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: " times and failed ",
|
text: " times and failed "
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: `${this.lastHistoryStats?.error ?? 0}`,
|
text: `${this.lastHistoryStats?.error ?? 0}`,
|
||||||
cls: ["number", "bad"],
|
cls: ["number", "bad"]
|
||||||
});
|
});
|
||||||
container.createSpan({
|
container.createSpan({
|
||||||
text: " times.",
|
text: " times."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue