Lint files
This commit is contained in:
parent
2f7cad602a
commit
ff5af8aea5
11 changed files with 184 additions and 276 deletions
|
|
@ -1,11 +1,12 @@
|
|||
import { Logger } from "src/logger";
|
||||
import { DEFAULT_SETTINGS, SyncSettings } from "./sync-settings";
|
||||
import {
|
||||
RelativePath,
|
||||
DocumentMetadata,
|
||||
VaultUpdateId,
|
||||
import type { SyncSettings } from "./sync-settings";
|
||||
import { DEFAULT_SETTINGS } from "./sync-settings";
|
||||
import type {
|
||||
DocumentId,
|
||||
DocumentMetadata,
|
||||
RelativePath,
|
||||
VaultUpdateId,
|
||||
} from "./document-metadata";
|
||||
import { Logger } from "src/tracing/logger";
|
||||
|
||||
interface StoredDatabase {
|
||||
documents: Map<RelativePath, DocumentMetadata>;
|
||||
|
|
@ -13,27 +14,31 @@ interface StoredDatabase {
|
|||
lastSeenUpdateId: VaultUpdateId | undefined;
|
||||
}
|
||||
|
||||
// Todo: split it into settings and documents
|
||||
export class Database {
|
||||
private _documents: Map<RelativePath, DocumentMetadata> = new Map();
|
||||
private _documents = new Map<RelativePath, DocumentMetadata>();
|
||||
private _settings: SyncSettings;
|
||||
private _lastSeenUpdateId: VaultUpdateId | undefined;
|
||||
|
||||
private onSettingsChangeHandlers: Array<
|
||||
(newSettings: SyncSettings, oldSettings: SyncSettings) => void
|
||||
> = [];
|
||||
private readonly onSettingsChangeHandlers: ((
|
||||
newSettings: SyncSettings,
|
||||
oldSettings: SyncSettings
|
||||
) => void)[] = [];
|
||||
|
||||
public constructor(
|
||||
initialState: Partial<StoredDatabase> | undefined,
|
||||
private saveData: (data: unknown) => Promise<void>
|
||||
private readonly saveData: (data: unknown) => Promise<void>
|
||||
) {
|
||||
initialState = initialState || {};
|
||||
initialState ??= {};
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
Object.prototype.hasOwnProperty.call(initialState, "documents") &&
|
||||
initialState.documents
|
||||
) {
|
||||
for (const [relativePath, metadata] of Object.entries(
|
||||
initialState.documents
|
||||
)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
this._documents.set(relativePath, metadata as DocumentMetadata);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,11 +51,10 @@ export class Database {
|
|||
)}`
|
||||
);
|
||||
|
||||
this._settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
initialState.settings || {}
|
||||
);
|
||||
this._settings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
...(initialState.settings ?? {}),
|
||||
};
|
||||
|
||||
Logger.getInstance().debug(
|
||||
`Loaded settings: ${JSON.stringify(this._settings, null, 2)}`
|
||||
|
|
@ -74,15 +78,15 @@ export class Database {
|
|||
public async setSettings(value: SyncSettings): Promise<void> {
|
||||
const oldSettings = this._settings;
|
||||
this._settings = value;
|
||||
this.onSettingsChangeHandlers.forEach((handler) =>
|
||||
handler(value, oldSettings)
|
||||
);
|
||||
this.onSettingsChangeHandlers.forEach((handler) => {
|
||||
handler(value, oldSettings);
|
||||
});
|
||||
await this.save();
|
||||
}
|
||||
|
||||
public addOnSettingsChangeHandlers(
|
||||
handler: (settings: SyncSettings, oldSettings: SyncSettings) => void
|
||||
) {
|
||||
): void {
|
||||
this.onSettingsChangeHandlers.push(handler);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TAbstractFile } from "obsidian";
|
||||
import type { TAbstractFile } from "obsidian";
|
||||
|
||||
export interface FileEventHandler {
|
||||
onCreate: (path: TAbstractFile) => Promise<void>;
|
||||
|
|
|
|||
|
|
@ -1,57 +1,48 @@
|
|||
import { TAbstractFile, TFile } from "obsidian";
|
||||
import { FileEventHandler } from "./file-event-handler";
|
||||
import { Logger } from "src/logger";
|
||||
import { SyncService } from "src/services/sync-service";
|
||||
import { Database } from "src/database/database";
|
||||
import type { TAbstractFile } from "obsidian";
|
||||
import { TFile } from "obsidian";
|
||||
import type { FileEventHandler } from "./file-event-handler";
|
||||
import type { SyncService } from "src/services/sync-service";
|
||||
import type { Database } from "src/database/database";
|
||||
import { syncLocallyDeletedFile } from "src/sync-operations/sync-locally-deleted-file";
|
||||
import { syncLocallyUpdatedFile } from "src/sync-operations/sync-locally-updated-file";
|
||||
import { FileOperations } from "src/file-operations/file-operations";
|
||||
import type { FileOperations } from "src/file-operations/file-operations";
|
||||
import { syncLocallyCreatedFile } from "src/sync-operations/sync-locally-created-file";
|
||||
import { Logger } from "src/tracing/logger";
|
||||
import type { SyncHistory } from "src/tracing/sync-history";
|
||||
|
||||
export class SyncEventHandler implements FileEventHandler {
|
||||
export class ObsidianFileEventHandler implements FileEventHandler {
|
||||
public constructor(
|
||||
private database: Database,
|
||||
private syncServer: SyncService,
|
||||
private operations: FileOperations
|
||||
private readonly database: Database,
|
||||
private readonly syncServer: SyncService,
|
||||
private readonly operations: FileOperations,
|
||||
private readonly history: SyncHistory
|
||||
) {}
|
||||
|
||||
async onCreate(file: TAbstractFile): Promise<void> {
|
||||
public async onCreate(file: TAbstractFile): Promise<void> {
|
||||
if (file instanceof TFile) {
|
||||
Logger.getInstance().info(`File created: ${file.path}`);
|
||||
|
||||
if (!this.database.getSettings().isSyncEnabled) {
|
||||
Logger.getInstance().info(
|
||||
`Sync is disabled, not syncing ${file.path}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncLocallyCreatedFile({
|
||||
database: this.database,
|
||||
syncServer: this.syncServer,
|
||||
operations: this.operations,
|
||||
updateTime: new Date(file.stat.ctime),
|
||||
filePath: file.path,
|
||||
relativePath: file.path,
|
||||
history: this.history,
|
||||
});
|
||||
} else {
|
||||
Logger.getInstance().info(`Folder created: ${file.path}, ignored`);
|
||||
}
|
||||
}
|
||||
|
||||
async onDelete(file: TAbstractFile): Promise<void> {
|
||||
public async onDelete(file: TAbstractFile): Promise<void> {
|
||||
if (file instanceof TFile) {
|
||||
Logger.getInstance().info(`File deleted: ${file.path}`);
|
||||
|
||||
if (!this.database.getSettings().isSyncEnabled) {
|
||||
Logger.getInstance().info(
|
||||
`Sync is disabled, not syncing ${file.path}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncLocallyDeletedFile({
|
||||
database: this.database,
|
||||
syncServer: this.syncServer,
|
||||
history: this.history,
|
||||
relativePath: file.path,
|
||||
});
|
||||
} else {
|
||||
|
|
@ -59,25 +50,19 @@ export class SyncEventHandler implements FileEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
async onRename(file: TAbstractFile, oldPath: string): Promise<void> {
|
||||
public async onRename(file: TAbstractFile, oldPath: string): Promise<void> {
|
||||
if (file instanceof TFile) {
|
||||
Logger.getInstance().info(
|
||||
`File renamed: ${oldPath} -> ${file.path}`
|
||||
);
|
||||
|
||||
if (!this.database.getSettings().isSyncEnabled) {
|
||||
Logger.getInstance().info(
|
||||
`Sync is disabled, not syncing ${file.path}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncLocallyUpdatedFile({
|
||||
database: this.database,
|
||||
syncServer: this.syncServer,
|
||||
operations: this.operations,
|
||||
history: this.history,
|
||||
updateTime: new Date(file.stat.ctime),
|
||||
filePath: file.path,
|
||||
relativePath: file.path,
|
||||
oldPath,
|
||||
});
|
||||
} else {
|
||||
|
|
@ -87,23 +72,17 @@ export class SyncEventHandler implements FileEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
async onModify(file: TAbstractFile): Promise<void> {
|
||||
public async onModify(file: TAbstractFile): Promise<void> {
|
||||
if (file instanceof TFile) {
|
||||
Logger.getInstance().info(`File modified: ${file.path}`);
|
||||
|
||||
if (!this.database.getSettings().isSyncEnabled) {
|
||||
Logger.getInstance().info(
|
||||
`Sync is disabled, not syncing ${file.path}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncLocallyUpdatedFile({
|
||||
database: this.database,
|
||||
syncServer: this.syncServer,
|
||||
operations: this.operations,
|
||||
history: this.history,
|
||||
updateTime: new Date(file.stat.ctime),
|
||||
filePath: file.path,
|
||||
relativePath: file.path,
|
||||
});
|
||||
} else {
|
||||
Logger.getInstance().info(`Folder modified: ${file.path}, ignored`);
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
import { RelativePath } from "src/database/document-metadata";
|
||||
import type { RelativePath } from "src/database/document-metadata";
|
||||
|
||||
export interface FileOperations {
|
||||
listAllFiles(): Promise<RelativePath[]>;
|
||||
listAllFiles: () => Promise<RelativePath[]>;
|
||||
|
||||
read(path: RelativePath): Promise<Uint8Array>;
|
||||
read: (path: RelativePath) => Promise<Uint8Array>;
|
||||
|
||||
getModificationTime(path: RelativePath): Promise<Date>;
|
||||
getModificationTime: (path: RelativePath) => Promise<Date>;
|
||||
|
||||
create(path: RelativePath, newContent: Uint8Array): Promise<void>;
|
||||
create: (path: RelativePath, newContent: Uint8Array) => Promise<void>;
|
||||
|
||||
// Writes new content to the file at the given path. If the file's content has changed since the expectedContent was read, the write will merge the changes.
|
||||
write(
|
||||
write: (
|
||||
path: RelativePath,
|
||||
expectedContent: Uint8Array,
|
||||
newContent: Uint8Array
|
||||
): Promise<Uint8Array>;
|
||||
) => Promise<Uint8Array>;
|
||||
|
||||
remove(path: RelativePath): Promise<void>;
|
||||
remove: (path: RelativePath) => Promise<void>;
|
||||
|
||||
move(oldPath: RelativePath, newPath: RelativePath): Promise<void>;
|
||||
move: (oldPath: RelativePath, newPath: RelativePath) => Promise<void>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,33 @@
|
|||
import { normalizePath, Vault } from "obsidian";
|
||||
import { FileOperations } from "./file-operations";
|
||||
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 { isEqualBytes } from "src/utils/is-equal-bytes";
|
||||
import { RelativePath } from "src/database/document-metadata";
|
||||
import type { RelativePath } from "src/database/document-metadata";
|
||||
|
||||
export class ObsidianFileOperations implements FileOperations {
|
||||
public constructor(private vault: Vault) {}
|
||||
public constructor(private readonly vault: Vault) {}
|
||||
|
||||
async listAllFiles(): Promise<RelativePath[]> {
|
||||
public async listAllFiles(): Promise<RelativePath[]> {
|
||||
const files = this.vault.getFiles();
|
||||
return files.map((file) => file.path);
|
||||
}
|
||||
|
||||
async read(path: RelativePath): Promise<Uint8Array> {
|
||||
public async read(path: RelativePath): Promise<Uint8Array> {
|
||||
return new Uint8Array(
|
||||
await this.vault.adapter.readBinary(normalizePath(path))
|
||||
);
|
||||
}
|
||||
|
||||
async getModificationTime(path: RelativePath): Promise<Date> {
|
||||
return new Date(
|
||||
(await this.vault.adapter.stat(normalizePath(path)))!.mtime
|
||||
);
|
||||
public async getModificationTime(path: RelativePath): Promise<Date> {
|
||||
const file = await this.vault.adapter.stat(normalizePath(path));
|
||||
if (!file) {
|
||||
throw new Error(`File not found: ${path}`);
|
||||
}
|
||||
return new Date(file.mtime);
|
||||
}
|
||||
|
||||
async write(
|
||||
public async write(
|
||||
path: RelativePath,
|
||||
expectedContent: Uint8Array,
|
||||
newContent: Uint8Array
|
||||
|
|
@ -44,17 +47,16 @@ export class ObsidianFileOperations implements FileOperations {
|
|||
await this.vault.adapter.writeBinary(normalizePath(path), result);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
await this.vault.adapter.writeBinary(
|
||||
normalizePath(path),
|
||||
newContent
|
||||
);
|
||||
|
||||
return newContent;
|
||||
}
|
||||
await this.vault.adapter.writeBinary(normalizePath(path), newContent);
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
async create(path: RelativePath, newContent: Uint8Array): Promise<void> {
|
||||
public async create(
|
||||
path: RelativePath,
|
||||
newContent: Uint8Array
|
||||
): Promise<void> {
|
||||
if (await this.vault.adapter.exists(normalizePath(path))) {
|
||||
await this.write(path, new Uint8Array(0), newContent);
|
||||
return;
|
||||
|
|
@ -63,18 +65,21 @@ export class ObsidianFileOperations implements FileOperations {
|
|||
await this.vault.adapter.writeBinary(normalizePath(path), newContent);
|
||||
}
|
||||
|
||||
async remove(path: RelativePath): Promise<void> {
|
||||
public async remove(path: RelativePath): Promise<void> {
|
||||
if (await this.vault.adapter.exists(normalizePath(path))) {
|
||||
return this.vault.adapter.remove(normalizePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
async move(oldPath: RelativePath, newPath: RelativePath): Promise<void> {
|
||||
public async move(
|
||||
oldPath: RelativePath,
|
||||
newPath: RelativePath
|
||||
): Promise<void> {
|
||||
if (oldPath === newPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.vault.adapter.rename(
|
||||
await this.vault.adapter.rename(
|
||||
normalizePath(oldPath),
|
||||
normalizePath(newPath)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
import { Notice } from "obsidian";
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
}
|
||||
|
||||
class LogLine {
|
||||
public constructor(public level: LogLevel, public message: string) {}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.formatLevel()}: ${this.message}`;
|
||||
}
|
||||
|
||||
private formatLevel(): string {
|
||||
switch (this.level) {
|
||||
case LogLevel.DEBUG:
|
||||
return "DEBUG";
|
||||
case LogLevel.INFO:
|
||||
return "INFO";
|
||||
case LogLevel.WARNING:
|
||||
return "WARNING";
|
||||
case LogLevel.ERROR:
|
||||
return "ERROR";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private static readonly MAX_MESSAGES = 1000;
|
||||
|
||||
private static instance: Logger;
|
||||
private messages: LogLine[] = [];
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): Logger {
|
||||
if (!Logger.instance) {
|
||||
Logger.instance = new Logger();
|
||||
}
|
||||
return Logger.instance;
|
||||
}
|
||||
|
||||
public debug(message: string): void {
|
||||
this.pushMessage(message, LogLevel.DEBUG);
|
||||
console.debug(message);
|
||||
}
|
||||
|
||||
public info(message: string): void {
|
||||
this.pushMessage(message, LogLevel.INFO);
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
public warn(message: string): void {
|
||||
this.pushMessage(message, LogLevel.WARNING);
|
||||
console.warn(message);
|
||||
}
|
||||
|
||||
public error(message: string): void {
|
||||
this.pushMessage(message, LogLevel.ERROR);
|
||||
console.error(message);
|
||||
new Notice(message);
|
||||
}
|
||||
|
||||
public getMessages(mininumSeverity: LogLevel): LogLine[] {
|
||||
return this.messages.filter(
|
||||
(message) => message.level >= mininumSeverity
|
||||
);
|
||||
}
|
||||
|
||||
private pushMessage(message: string, level: LogLevel): void {
|
||||
this.messages.push(new LogLine(level, message));
|
||||
if (this.messages.length > Logger.MAX_MESSAGES) {
|
||||
this.messages.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js";
|
||||
|
||||
import createClient, { Client } from "openapi-fetch";
|
||||
import type { components, paths } from "./types.js"; // generated by openapi-typescript
|
||||
import { Logger } from "src/logger";
|
||||
import { Database } from "src/database/database";
|
||||
import { SyncSettings } from "src/database/sync-settings";
|
||||
import {
|
||||
VaultUpdateId,
|
||||
RelativePath,
|
||||
import type { Client } from "openapi-fetch";
|
||||
import createClient from "openapi-fetch";
|
||||
import type { components, paths } from "./types.js"; // Generated by openapi-typescript
|
||||
import type { Database } from "src/database/database";
|
||||
import type { SyncSettings } from "src/database/sync-settings";
|
||||
import type {
|
||||
DocumentId,
|
||||
RelativePath,
|
||||
VaultUpdateId,
|
||||
} from "src/database/document-metadata";
|
||||
import PQueue from "p-queue";
|
||||
import { Logger } from "src/tracing/logger.js";
|
||||
|
||||
export interface RequestCountStatus {
|
||||
waiting: number;
|
||||
|
|
@ -21,16 +22,17 @@ export interface RequestCountStatus {
|
|||
export class SyncService {
|
||||
private client: Client<paths>;
|
||||
|
||||
private promiseQueue: PQueue;
|
||||
private requestCountListeners: Array<(status: RequestCountStatus) => void> =
|
||||
[];
|
||||
private status: RequestCountStatus = {
|
||||
private readonly promiseQueue: PQueue;
|
||||
private readonly requestCountListeners: ((
|
||||
status: RequestCountStatus
|
||||
) => void)[] = [];
|
||||
private readonly status: RequestCountStatus = {
|
||||
waiting: 0,
|
||||
success: 0,
|
||||
failure: 0,
|
||||
};
|
||||
|
||||
public constructor(private database: Database) {
|
||||
public constructor(private readonly database: Database) {
|
||||
this.createClient(database.getSettings());
|
||||
this.promiseQueue = new PQueue({
|
||||
concurrency: database.getSettings().uploadConcurrency,
|
||||
|
|
@ -64,36 +66,21 @@ export class SyncService {
|
|||
listener({ ...this.status });
|
||||
}
|
||||
|
||||
private emitRequestCountChange(): void {
|
||||
this.requestCountListeners.forEach((listener) =>
|
||||
listener({ ...this.status })
|
||||
);
|
||||
}
|
||||
|
||||
private createClient(settings: SyncSettings) {
|
||||
this.client = createClient<paths>({
|
||||
baseUrl: settings.remoteUri,
|
||||
});
|
||||
}
|
||||
|
||||
private enqueue<T>(fn: () => Promise<T>): Promise<T> {
|
||||
return this.promiseQueue.add(fn) as Promise<T>;
|
||||
}
|
||||
|
||||
public async ping(): Promise<components["schemas"]["PingResponse"]> {
|
||||
const response = await this.enqueue(() =>
|
||||
const response = await this.enqueue(async () =>
|
||||
this.client.GET("/ping", {
|
||||
params: {
|
||||
header: {
|
||||
authorization:
|
||||
"Bearer " + this.database.getSettings().token,
|
||||
authorization: `Bearer ${
|
||||
this.database.getSettings().token
|
||||
}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
Logger.getInstance().debug(
|
||||
"Ping response: " + JSON.stringify(response.data)
|
||||
`Ping response: ${JSON.stringify(response.data)}`
|
||||
);
|
||||
|
||||
if (!response.data) {
|
||||
|
|
@ -112,15 +99,16 @@ export class SyncService {
|
|||
contentBytes: Uint8Array;
|
||||
createdDate: Date;
|
||||
}): Promise<components["schemas"]["DocumentVersion"]> {
|
||||
const response = await this.enqueue(() =>
|
||||
const response = await this.enqueue(async () =>
|
||||
this.client.POST("/vaults/{vault_id}/documents", {
|
||||
params: {
|
||||
path: {
|
||||
vault_id: this.database.getSettings().vaultName,
|
||||
},
|
||||
header: {
|
||||
authorization:
|
||||
"Bearer " + this.database.getSettings().token,
|
||||
authorization: `Bearer ${
|
||||
this.database.getSettings().token
|
||||
}`,
|
||||
},
|
||||
},
|
||||
body: {
|
||||
|
|
@ -136,7 +124,7 @@ export class SyncService {
|
|||
}
|
||||
|
||||
Logger.getInstance().debug(
|
||||
"Created document " + JSON.stringify(response.data)
|
||||
`Created document ${JSON.stringify(response.data)}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
|
|
@ -155,7 +143,7 @@ export class SyncService {
|
|||
contentBytes: Uint8Array;
|
||||
createdDate: Date;
|
||||
}): Promise<components["schemas"]["DocumentVersion"]> {
|
||||
const response = await this.enqueue(() =>
|
||||
const response = await this.enqueue(async () =>
|
||||
this.client.PUT("/vaults/{vault_id}/documents/{document_id}", {
|
||||
params: {
|
||||
path: {
|
||||
|
|
@ -163,8 +151,9 @@ export class SyncService {
|
|||
document_id: documentId,
|
||||
},
|
||||
header: {
|
||||
authorization:
|
||||
"Bearer " + this.database.getSettings().token,
|
||||
authorization: `Bearer ${
|
||||
this.database.getSettings().token
|
||||
}`,
|
||||
},
|
||||
},
|
||||
body: {
|
||||
|
|
@ -181,7 +170,7 @@ export class SyncService {
|
|||
}
|
||||
|
||||
Logger.getInstance().debug(
|
||||
"Updated document " + JSON.stringify(response.data)
|
||||
`Updated document ${JSON.stringify(response.data)}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
|
|
@ -196,7 +185,7 @@ export class SyncService {
|
|||
relativePath: RelativePath;
|
||||
createdDate: Date;
|
||||
}): Promise<void> {
|
||||
const response = await this.enqueue(() =>
|
||||
const response = await this.enqueue(async () =>
|
||||
this.client.DELETE("/vaults/{vault_id}/documents/{document_id}", {
|
||||
params: {
|
||||
path: {
|
||||
|
|
@ -204,8 +193,9 @@ export class SyncService {
|
|||
document_id: documentId,
|
||||
},
|
||||
header: {
|
||||
authorization:
|
||||
"Bearer " + this.database.getSettings().token,
|
||||
authorization: `Bearer ${
|
||||
this.database.getSettings().token
|
||||
}`,
|
||||
},
|
||||
},
|
||||
body: {
|
||||
|
|
@ -220,7 +210,7 @@ export class SyncService {
|
|||
}
|
||||
|
||||
Logger.getInstance().debug(
|
||||
"Updated document " + JSON.stringify(response.data)
|
||||
`Updated document ${JSON.stringify(response.data)}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
|
|
@ -231,7 +221,7 @@ export class SyncService {
|
|||
}: {
|
||||
documentId: DocumentId;
|
||||
}): Promise<components["schemas"]["DocumentVersion"]> {
|
||||
const response = await this.enqueue(() =>
|
||||
const response = await this.enqueue(async () =>
|
||||
this.client.GET("/vaults/{vault_id}/documents/{document_id}", {
|
||||
params: {
|
||||
path: {
|
||||
|
|
@ -239,8 +229,9 @@ export class SyncService {
|
|||
document_id: documentId,
|
||||
},
|
||||
header: {
|
||||
authorization:
|
||||
"Bearer " + this.database.getSettings().token,
|
||||
authorization: `Bearer ${
|
||||
this.database.getSettings().token
|
||||
}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -251,7 +242,7 @@ export class SyncService {
|
|||
}
|
||||
|
||||
Logger.getInstance().debug(
|
||||
"Get document " + JSON.stringify(response.data)
|
||||
`Get document ${JSON.stringify(response.data)}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
|
|
@ -260,15 +251,16 @@ export class SyncService {
|
|||
public async getAll(
|
||||
since?: VaultUpdateId
|
||||
): Promise<components["schemas"]["FetchLatestDocumentsResponse"]> {
|
||||
const response = await this.enqueue(() =>
|
||||
const response = await this.enqueue(async () =>
|
||||
this.client.GET("/vaults/{vault_id}/documents", {
|
||||
params: {
|
||||
path: {
|
||||
vault_id: this.database.getSettings().vaultName,
|
||||
},
|
||||
header: {
|
||||
authorization:
|
||||
"Bearer " + this.database.getSettings().token,
|
||||
authorization: `Bearer ${
|
||||
this.database.getSettings().token
|
||||
}`,
|
||||
},
|
||||
query: {
|
||||
since_update_id: since,
|
||||
|
|
@ -277,14 +269,32 @@ export class SyncService {
|
|||
})
|
||||
);
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error(`Failed to get documents: ${response.error}`);
|
||||
const { error } = response;
|
||||
if (error) {
|
||||
throw new Error(`Failed to get documents: ${error}`);
|
||||
}
|
||||
|
||||
Logger.getInstance().debug(
|
||||
"Get document " + JSON.stringify(response.data)
|
||||
`Get document ${JSON.stringify(response.data)}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
private emitRequestCountChange(): void {
|
||||
this.requestCountListeners.forEach((listener) => {
|
||||
listener({ ...this.status });
|
||||
});
|
||||
}
|
||||
|
||||
private createClient(settings: SyncSettings): void {
|
||||
this.client = createClient<paths>({
|
||||
baseUrl: settings.remoteUri,
|
||||
});
|
||||
}
|
||||
|
||||
private async enqueue<T>(fn: () => Promise<T>): Promise<T> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
return this.promiseQueue.add(fn) as Promise<T>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ export interface paths {
|
|||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
headers: Record<string, unknown>;
|
||||
content: {
|
||||
"application/json": components["schemas"]["PingResponse"];
|
||||
};
|
||||
|
|
@ -63,9 +61,7 @@ export interface paths {
|
|||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
headers: Record<string, unknown>;
|
||||
content: {
|
||||
"application/json": components["schemas"]["FetchLatestDocumentsResponse"];
|
||||
};
|
||||
|
|
@ -91,9 +87,7 @@ export interface paths {
|
|||
};
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
headers: Record<string, unknown>;
|
||||
content: {
|
||||
"application/json": components["schemas"]["DocumentVersion"];
|
||||
};
|
||||
|
|
@ -128,9 +122,7 @@ export interface paths {
|
|||
requestBody?: never;
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
headers: Record<string, unknown>;
|
||||
content: {
|
||||
"application/json": components["schemas"]["DocumentVersion"];
|
||||
};
|
||||
|
|
@ -156,9 +148,7 @@ export interface paths {
|
|||
};
|
||||
responses: {
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
headers: Record<string, unknown>;
|
||||
content: {
|
||||
"application/json": components["schemas"]["DocumentVersion"];
|
||||
};
|
||||
|
|
@ -186,9 +176,7 @@ export interface paths {
|
|||
responses: {
|
||||
/** @description no content */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
headers: Record<string, unknown>;
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { RelativePath } from "src/database/document-metadata";
|
||||
import type { RelativePath } from "src/database/document-metadata";
|
||||
|
||||
const locked = new Set<RelativePath>();
|
||||
const waiters = new Map<RelativePath, Array<() => void>>();
|
||||
const locked = new Set<RelativePath>(),
|
||||
waiters = new Map<RelativePath, (() => void)[]>();
|
||||
|
||||
export function tryLockDocument(relativePath: RelativePath): boolean {
|
||||
if (locked.has(relativePath)) {
|
||||
|
|
@ -12,17 +12,21 @@ export function tryLockDocument(relativePath: RelativePath): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function waitForDocumentLock(relativePath: RelativePath): Promise<void> {
|
||||
export async function waitForDocumentLock(
|
||||
relativePath: RelativePath
|
||||
): Promise<void> {
|
||||
if (tryLockDocument(relativePath)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (!waiters.has(relativePath)) {
|
||||
waiters.set(relativePath, []);
|
||||
let waiting = waiters.get(relativePath);
|
||||
if (!waiting) {
|
||||
waiting = [];
|
||||
waiters.set(relativePath, waiting);
|
||||
}
|
||||
|
||||
waiters.get(relativePath)!.push(resolve);
|
||||
waiting.push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
|
||||
export function hash(content: Uint8Array): string {
|
||||
let hash = 0;
|
||||
let result = 0;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
hash = (hash << 5) - hash + content[i];
|
||||
hash |= 0; // convert to 32bit integer
|
||||
result = (result << 5) - result + content[i];
|
||||
result |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash.toString(16);
|
||||
return Math.abs(result).toString(16);
|
||||
}
|
||||
|
||||
export const EMPTY_HASH = hash(new Uint8Array(0));
|
||||
|
|
|
|||
|
|
@ -1,42 +1,40 @@
|
|||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import { Logger, LogLevel } from "src/logger";
|
||||
import type { WorkspaceLeaf } from "obsidian";
|
||||
import { ItemView } from "obsidian";
|
||||
import { LogLevel, Logger } from "src/tracing/logger";
|
||||
|
||||
export class SyncView extends ItemView {
|
||||
public static TYPE = "example-view";
|
||||
public static readonly TYPE = "example-view";
|
||||
|
||||
public constructor(leaf: WorkspaceLeaf) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
public getViewType(): string {
|
||||
return SyncView.TYPE;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
public getDisplayText(): string {
|
||||
return "Example view";
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
public async onOpen(): Promise<void> {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
container.createEl("h4", { text: "Example view" });
|
||||
|
||||
setInterval(() => this.updateView(), 1000);
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
setInterval(async () => this.updateView(), 1000);
|
||||
}
|
||||
|
||||
async updateView() {
|
||||
public async updateView(): Promise<void> {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
|
||||
const messages = Logger.getInstance()
|
||||
.getMessages(LogLevel.INFO)
|
||||
.getMessages(LogLevel.DEBUG)
|
||||
.map((message) => message.toString())
|
||||
.join("\n");
|
||||
|
||||
container.createEl("pre", { text: messages });
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Nothing to clean up.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue