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