Lint files

This commit is contained in:
Andras Schmelczer 2024-12-20 16:14:46 +00:00
commit ff5af8aea5
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
11 changed files with 183 additions and 275 deletions

View file

@ -1,11 +1,12 @@
import { Logger } from "src/logger"; import type { SyncSettings } from "./sync-settings";
import { DEFAULT_SETTINGS, SyncSettings } from "./sync-settings"; import { DEFAULT_SETTINGS } from "./sync-settings";
import { import type {
RelativePath,
DocumentMetadata,
VaultUpdateId,
DocumentId, DocumentId,
DocumentMetadata,
RelativePath,
VaultUpdateId,
} from "./document-metadata"; } from "./document-metadata";
import { Logger } from "src/tracing/logger";
interface StoredDatabase { interface StoredDatabase {
documents: Map<RelativePath, DocumentMetadata>; documents: Map<RelativePath, DocumentMetadata>;
@ -13,27 +14,31 @@ interface StoredDatabase {
lastSeenUpdateId: VaultUpdateId | undefined; lastSeenUpdateId: VaultUpdateId | undefined;
} }
// Todo: split it into settings and documents
export class Database { export class Database {
private _documents: Map<RelativePath, DocumentMetadata> = new Map(); private _documents = new Map<RelativePath, DocumentMetadata>();
private _settings: SyncSettings; private _settings: SyncSettings;
private _lastSeenUpdateId: VaultUpdateId | undefined; private _lastSeenUpdateId: VaultUpdateId | undefined;
private onSettingsChangeHandlers: Array< private readonly onSettingsChangeHandlers: ((
(newSettings: SyncSettings, oldSettings: SyncSettings) => void newSettings: SyncSettings,
> = []; oldSettings: SyncSettings
) => void)[] = [];
public constructor( public constructor(
initialState: Partial<StoredDatabase> | undefined, initialState: Partial<StoredDatabase> | undefined,
private saveData: (data: unknown) => Promise<void> private readonly saveData: (data: unknown) => Promise<void>
) { ) {
initialState = initialState || {}; initialState ??= {};
if ( if (
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
Object.prototype.hasOwnProperty.call(initialState, "documents") && Object.prototype.hasOwnProperty.call(initialState, "documents") &&
initialState.documents initialState.documents
) { ) {
for (const [relativePath, metadata] of Object.entries( for (const [relativePath, metadata] of Object.entries(
initialState.documents initialState.documents
)) { )) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
this._documents.set(relativePath, metadata as DocumentMetadata); this._documents.set(relativePath, metadata as DocumentMetadata);
} }
} }
@ -46,11 +51,10 @@ export class Database {
)}` )}`
); );
this._settings = Object.assign( this._settings = {
{}, ...DEFAULT_SETTINGS,
DEFAULT_SETTINGS, ...(initialState.settings ?? {}),
initialState.settings || {} };
);
Logger.getInstance().debug( Logger.getInstance().debug(
`Loaded settings: ${JSON.stringify(this._settings, null, 2)}` `Loaded settings: ${JSON.stringify(this._settings, null, 2)}`
@ -74,15 +78,15 @@ export class Database {
public async setSettings(value: SyncSettings): Promise<void> { public async setSettings(value: SyncSettings): Promise<void> {
const oldSettings = this._settings; const oldSettings = this._settings;
this._settings = value; this._settings = value;
this.onSettingsChangeHandlers.forEach((handler) => this.onSettingsChangeHandlers.forEach((handler) => {
handler(value, oldSettings) handler(value, oldSettings);
); });
await this.save(); await this.save();
} }
public addOnSettingsChangeHandlers( public addOnSettingsChangeHandlers(
handler: (settings: SyncSettings, oldSettings: SyncSettings) => void handler: (settings: SyncSettings, oldSettings: SyncSettings) => void
) { ): void {
this.onSettingsChangeHandlers.push(handler); this.onSettingsChangeHandlers.push(handler);
} }

View file

@ -1,4 +1,4 @@
import { TAbstractFile } from "obsidian"; import type { TAbstractFile } from "obsidian";
export interface FileEventHandler { export interface FileEventHandler {
onCreate: (path: TAbstractFile) => Promise<void>; onCreate: (path: TAbstractFile) => Promise<void>;

View file

@ -1,57 +1,48 @@
import { TAbstractFile, TFile } from "obsidian"; import type { TAbstractFile } from "obsidian";
import { FileEventHandler } from "./file-event-handler"; import { TFile } from "obsidian";
import { Logger } from "src/logger"; import type { FileEventHandler } from "./file-event-handler";
import { SyncService } from "src/services/sync-service"; import type { SyncService } from "src/services/sync-service";
import { Database } from "src/database/database"; import type { Database } from "src/database/database";
import { syncLocallyDeletedFile } from "src/sync-operations/sync-locally-deleted-file"; import { syncLocallyDeletedFile } from "src/sync-operations/sync-locally-deleted-file";
import { syncLocallyUpdatedFile } from "src/sync-operations/sync-locally-updated-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 { 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( public constructor(
private database: Database, private readonly database: Database,
private syncServer: SyncService, private readonly syncServer: SyncService,
private operations: FileOperations 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) { if (file instanceof TFile) {
Logger.getInstance().info(`File created: ${file.path}`); 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({ await syncLocallyCreatedFile({
database: this.database, database: this.database,
syncServer: this.syncServer, syncServer: this.syncServer,
operations: this.operations, operations: this.operations,
updateTime: new Date(file.stat.ctime), updateTime: new Date(file.stat.ctime),
filePath: file.path, relativePath: file.path,
history: this.history,
}); });
} else { } else {
Logger.getInstance().info(`Folder created: ${file.path}, ignored`); 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) { if (file instanceof TFile) {
Logger.getInstance().info(`File deleted: ${file.path}`); 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({ await syncLocallyDeletedFile({
database: this.database, database: this.database,
syncServer: this.syncServer, syncServer: this.syncServer,
history: this.history,
relativePath: file.path, relativePath: file.path,
}); });
} else { } 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) { if (file instanceof TFile) {
Logger.getInstance().info( Logger.getInstance().info(
`File renamed: ${oldPath} -> ${file.path}` `File renamed: ${oldPath} -> ${file.path}`
); );
if (!this.database.getSettings().isSyncEnabled) {
Logger.getInstance().info(
`Sync is disabled, not syncing ${file.path}`
);
return;
}
await syncLocallyUpdatedFile({ await syncLocallyUpdatedFile({
database: this.database, database: this.database,
syncServer: this.syncServer, syncServer: this.syncServer,
operations: this.operations, operations: this.operations,
history: this.history,
updateTime: new Date(file.stat.ctime), updateTime: new Date(file.stat.ctime),
filePath: file.path, relativePath: file.path,
oldPath, oldPath,
}); });
} else { } 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) { if (file instanceof TFile) {
Logger.getInstance().info(`File modified: ${file.path}`); 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({ await syncLocallyUpdatedFile({
database: this.database, database: this.database,
syncServer: this.syncServer, syncServer: this.syncServer,
operations: this.operations, operations: this.operations,
history: this.history,
updateTime: new Date(file.stat.ctime), updateTime: new Date(file.stat.ctime),
filePath: file.path, relativePath: file.path,
}); });
} else { } else {
Logger.getInstance().info(`Folder modified: ${file.path}, ignored`); Logger.getInstance().info(`Folder modified: ${file.path}, ignored`);

View file

@ -1,22 +1,22 @@
import { RelativePath } from "src/database/document-metadata"; import type { RelativePath } from "src/database/document-metadata";
export interface FileOperations { 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. // 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, path: RelativePath,
expectedContent: Uint8Array, expectedContent: Uint8Array,
newContent: 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>;
} }

View file

@ -1,30 +1,33 @@
import { normalizePath, Vault } from "obsidian"; import type { Vault } from "obsidian";
import { FileOperations } from "./file-operations"; import { normalizePath } from "obsidian";
import type { FileOperations } from "./file-operations";
import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js"; import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js";
import { isEqualBytes } from "src/utils/is-equal-bytes"; 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 { 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(); const files = this.vault.getFiles();
return files.map((file) => file.path); return files.map((file) => file.path);
} }
async read(path: RelativePath): Promise<Uint8Array> { public async read(path: RelativePath): Promise<Uint8Array> {
return new Uint8Array( return new Uint8Array(
await this.vault.adapter.readBinary(normalizePath(path)) await this.vault.adapter.readBinary(normalizePath(path))
); );
} }
async getModificationTime(path: RelativePath): Promise<Date> { public async getModificationTime(path: RelativePath): Promise<Date> {
return new Date( const file = await this.vault.adapter.stat(normalizePath(path));
(await this.vault.adapter.stat(normalizePath(path)))!.mtime if (!file) {
); throw new Error(`File not found: ${path}`);
}
return new Date(file.mtime);
} }
async write( public async write(
path: RelativePath, path: RelativePath,
expectedContent: Uint8Array, expectedContent: Uint8Array,
newContent: Uint8Array newContent: Uint8Array
@ -44,17 +47,16 @@ export class ObsidianFileOperations implements FileOperations {
await this.vault.adapter.writeBinary(normalizePath(path), result); await this.vault.adapter.writeBinary(normalizePath(path), result);
return 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))) { if (await this.vault.adapter.exists(normalizePath(path))) {
await this.write(path, new Uint8Array(0), newContent); await this.write(path, new Uint8Array(0), newContent);
return; return;
@ -63,18 +65,21 @@ export class ObsidianFileOperations implements FileOperations {
await this.vault.adapter.writeBinary(normalizePath(path), newContent); 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))) { if (await this.vault.adapter.exists(normalizePath(path))) {
return this.vault.adapter.remove(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) { if (oldPath === newPath) {
return; return;
} }
this.vault.adapter.rename( await this.vault.adapter.rename(
normalizePath(oldPath), normalizePath(oldPath),
normalizePath(newPath) normalizePath(newPath)
); );

View file

@ -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();
}
}
}

View file

@ -1,16 +1,17 @@
import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js"; import * as lib from "../../../backend/sync_lib/pkg/sync_lib.js";
import createClient, { Client } from "openapi-fetch"; import type { Client } from "openapi-fetch";
import type { components, paths } from "./types.js"; // generated by openapi-typescript import createClient from "openapi-fetch";
import { Logger } from "src/logger"; import type { components, paths } from "./types.js"; // Generated by openapi-typescript
import { Database } from "src/database/database"; import type { Database } from "src/database/database";
import { SyncSettings } from "src/database/sync-settings"; import type { SyncSettings } from "src/database/sync-settings";
import { import type {
VaultUpdateId,
RelativePath,
DocumentId, DocumentId,
RelativePath,
VaultUpdateId,
} from "src/database/document-metadata"; } from "src/database/document-metadata";
import PQueue from "p-queue"; import PQueue from "p-queue";
import { Logger } from "src/tracing/logger.js";
export interface RequestCountStatus { export interface RequestCountStatus {
waiting: number; waiting: number;
@ -21,16 +22,17 @@ export interface RequestCountStatus {
export class SyncService { export class SyncService {
private client: Client<paths>; private client: Client<paths>;
private promiseQueue: PQueue; private readonly promiseQueue: PQueue;
private requestCountListeners: Array<(status: RequestCountStatus) => void> = private readonly requestCountListeners: ((
[]; status: RequestCountStatus
private status: RequestCountStatus = { ) => void)[] = [];
private readonly status: RequestCountStatus = {
waiting: 0, waiting: 0,
success: 0, success: 0,
failure: 0, failure: 0,
}; };
public constructor(private database: Database) { public constructor(private readonly database: Database) {
this.createClient(database.getSettings()); this.createClient(database.getSettings());
this.promiseQueue = new PQueue({ this.promiseQueue = new PQueue({
concurrency: database.getSettings().uploadConcurrency, concurrency: database.getSettings().uploadConcurrency,
@ -64,36 +66,21 @@ export class SyncService {
listener({ ...this.status }); 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"]> { public async ping(): Promise<components["schemas"]["PingResponse"]> {
const response = await this.enqueue(() => const response = await this.enqueue(async () =>
this.client.GET("/ping", { this.client.GET("/ping", {
params: { params: {
header: { header: {
authorization: authorization: `Bearer ${
"Bearer " + this.database.getSettings().token, this.database.getSettings().token
}`,
}, },
}, },
}) })
); );
Logger.getInstance().debug( Logger.getInstance().debug(
"Ping response: " + JSON.stringify(response.data) `Ping response: ${JSON.stringify(response.data)}`
); );
if (!response.data) { if (!response.data) {
@ -112,15 +99,16 @@ export class SyncService {
contentBytes: Uint8Array; contentBytes: Uint8Array;
createdDate: Date; createdDate: Date;
}): Promise<components["schemas"]["DocumentVersion"]> { }): Promise<components["schemas"]["DocumentVersion"]> {
const response = await this.enqueue(() => const response = await this.enqueue(async () =>
this.client.POST("/vaults/{vault_id}/documents", { this.client.POST("/vaults/{vault_id}/documents", {
params: { params: {
path: { path: {
vault_id: this.database.getSettings().vaultName, vault_id: this.database.getSettings().vaultName,
}, },
header: { header: {
authorization: authorization: `Bearer ${
"Bearer " + this.database.getSettings().token, this.database.getSettings().token
}`,
}, },
}, },
body: { body: {
@ -136,7 +124,7 @@ export class SyncService {
} }
Logger.getInstance().debug( Logger.getInstance().debug(
"Created document " + JSON.stringify(response.data) `Created document ${JSON.stringify(response.data)}`
); );
return response.data; return response.data;
@ -155,7 +143,7 @@ export class SyncService {
contentBytes: Uint8Array; contentBytes: Uint8Array;
createdDate: Date; createdDate: Date;
}): Promise<components["schemas"]["DocumentVersion"]> { }): Promise<components["schemas"]["DocumentVersion"]> {
const response = await this.enqueue(() => const response = await this.enqueue(async () =>
this.client.PUT("/vaults/{vault_id}/documents/{document_id}", { this.client.PUT("/vaults/{vault_id}/documents/{document_id}", {
params: { params: {
path: { path: {
@ -163,8 +151,9 @@ export class SyncService {
document_id: documentId, document_id: documentId,
}, },
header: { header: {
authorization: authorization: `Bearer ${
"Bearer " + this.database.getSettings().token, this.database.getSettings().token
}`,
}, },
}, },
body: { body: {
@ -181,7 +170,7 @@ export class SyncService {
} }
Logger.getInstance().debug( Logger.getInstance().debug(
"Updated document " + JSON.stringify(response.data) `Updated document ${JSON.stringify(response.data)}`
); );
return response.data; return response.data;
@ -196,7 +185,7 @@ export class SyncService {
relativePath: RelativePath; relativePath: RelativePath;
createdDate: Date; createdDate: Date;
}): Promise<void> { }): Promise<void> {
const response = await this.enqueue(() => const response = await this.enqueue(async () =>
this.client.DELETE("/vaults/{vault_id}/documents/{document_id}", { this.client.DELETE("/vaults/{vault_id}/documents/{document_id}", {
params: { params: {
path: { path: {
@ -204,8 +193,9 @@ export class SyncService {
document_id: documentId, document_id: documentId,
}, },
header: { header: {
authorization: authorization: `Bearer ${
"Bearer " + this.database.getSettings().token, this.database.getSettings().token
}`,
}, },
}, },
body: { body: {
@ -220,7 +210,7 @@ export class SyncService {
} }
Logger.getInstance().debug( Logger.getInstance().debug(
"Updated document " + JSON.stringify(response.data) `Updated document ${JSON.stringify(response.data)}`
); );
return response.data; return response.data;
@ -231,7 +221,7 @@ export class SyncService {
}: { }: {
documentId: DocumentId; documentId: DocumentId;
}): Promise<components["schemas"]["DocumentVersion"]> { }): Promise<components["schemas"]["DocumentVersion"]> {
const response = await this.enqueue(() => const response = await this.enqueue(async () =>
this.client.GET("/vaults/{vault_id}/documents/{document_id}", { this.client.GET("/vaults/{vault_id}/documents/{document_id}", {
params: { params: {
path: { path: {
@ -239,8 +229,9 @@ export class SyncService {
document_id: documentId, document_id: documentId,
}, },
header: { header: {
authorization: authorization: `Bearer ${
"Bearer " + this.database.getSettings().token, this.database.getSettings().token
}`,
}, },
}, },
}) })
@ -251,7 +242,7 @@ export class SyncService {
} }
Logger.getInstance().debug( Logger.getInstance().debug(
"Get document " + JSON.stringify(response.data) `Get document ${JSON.stringify(response.data)}`
); );
return response.data; return response.data;
@ -260,15 +251,16 @@ export class SyncService {
public async getAll( public async getAll(
since?: VaultUpdateId since?: VaultUpdateId
): Promise<components["schemas"]["FetchLatestDocumentsResponse"]> { ): Promise<components["schemas"]["FetchLatestDocumentsResponse"]> {
const response = await this.enqueue(() => const response = await this.enqueue(async () =>
this.client.GET("/vaults/{vault_id}/documents", { 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: authorization: `Bearer ${
"Bearer " + this.database.getSettings().token, this.database.getSettings().token
}`,
}, },
query: { query: {
since_update_id: since, since_update_id: since,
@ -277,14 +269,32 @@ export class SyncService {
}) })
); );
if (!response.data) { const { error } = response;
throw new Error(`Failed to get documents: ${response.error}`); if (error) {
throw new Error(`Failed to get documents: ${error}`);
} }
Logger.getInstance().debug( Logger.getInstance().debug(
"Get document " + JSON.stringify(response.data) `Get document ${JSON.stringify(response.data)}`
); );
return 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>;
}
} }

View file

@ -23,9 +23,7 @@ export interface paths {
requestBody?: never; requestBody?: never;
responses: { responses: {
200: { 200: {
headers: { headers: Record<string, unknown>;
[name: string]: unknown;
};
content: { content: {
"application/json": components["schemas"]["PingResponse"]; "application/json": components["schemas"]["PingResponse"];
}; };
@ -63,9 +61,7 @@ export interface paths {
requestBody?: never; requestBody?: never;
responses: { responses: {
200: { 200: {
headers: { headers: Record<string, unknown>;
[name: string]: unknown;
};
content: { content: {
"application/json": components["schemas"]["FetchLatestDocumentsResponse"]; "application/json": components["schemas"]["FetchLatestDocumentsResponse"];
}; };
@ -91,9 +87,7 @@ export interface paths {
}; };
responses: { responses: {
200: { 200: {
headers: { headers: Record<string, unknown>;
[name: string]: unknown;
};
content: { content: {
"application/json": components["schemas"]["DocumentVersion"]; "application/json": components["schemas"]["DocumentVersion"];
}; };
@ -128,9 +122,7 @@ export interface paths {
requestBody?: never; requestBody?: never;
responses: { responses: {
200: { 200: {
headers: { headers: Record<string, unknown>;
[name: string]: unknown;
};
content: { content: {
"application/json": components["schemas"]["DocumentVersion"]; "application/json": components["schemas"]["DocumentVersion"];
}; };
@ -156,9 +148,7 @@ export interface paths {
}; };
responses: { responses: {
200: { 200: {
headers: { headers: Record<string, unknown>;
[name: string]: unknown;
};
content: { content: {
"application/json": components["schemas"]["DocumentVersion"]; "application/json": components["schemas"]["DocumentVersion"];
}; };
@ -186,9 +176,7 @@ export interface paths {
responses: { responses: {
/** @description no content */ /** @description no content */
200: { 200: {
headers: { headers: Record<string, unknown>;
[name: string]: unknown;
};
content?: never; content?: never;
}; };
}; };

View file

@ -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 locked = new Set<RelativePath>(),
const waiters = new Map<RelativePath, Array<() => void>>(); waiters = new Map<RelativePath, (() => void)[]>();
export function tryLockDocument(relativePath: RelativePath): boolean { export function tryLockDocument(relativePath: RelativePath): boolean {
if (locked.has(relativePath)) { if (locked.has(relativePath)) {
@ -12,17 +12,21 @@ export function tryLockDocument(relativePath: RelativePath): boolean {
return true; return true;
} }
export function waitForDocumentLock(relativePath: RelativePath): Promise<void> { export async function waitForDocumentLock(
relativePath: RelativePath
): Promise<void> {
if (tryLockDocument(relativePath)) { if (tryLockDocument(relativePath)) {
return Promise.resolve(); return Promise.resolve();
} }
return new Promise((resolve) => { return new Promise((resolve) => {
if (!waiters.has(relativePath)) { let waiting = waiters.get(relativePath);
waiters.set(relativePath, []); if (!waiting) {
waiting = [];
waiters.set(relativePath, waiting);
} }
waiters.get(relativePath)!.push(resolve); waiting.push(resolve);
}); });
} }

View file

@ -1,11 +1,12 @@
// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript // https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
export function hash(content: Uint8Array): string { 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++) { for (let i = 0; i < content.length; i++) {
hash = (hash << 5) - hash + content[i]; result = (result << 5) - result + content[i];
hash |= 0; // convert to 32bit integer result |= 0; // Convert to 32bit integer
} }
return hash.toString(16); return Math.abs(result).toString(16);
} }
export const EMPTY_HASH = hash(new Uint8Array(0)); export const EMPTY_HASH = hash(new Uint8Array(0));

View file

@ -1,42 +1,40 @@
import { ItemView, WorkspaceLeaf } from "obsidian"; import type { WorkspaceLeaf } from "obsidian";
import { Logger, LogLevel } from "src/logger"; import { ItemView } from "obsidian";
import { LogLevel, Logger } from "src/tracing/logger";
export class SyncView extends ItemView { export class SyncView extends ItemView {
public static TYPE = "example-view"; public static readonly TYPE = "example-view";
public constructor(leaf: WorkspaceLeaf) { public constructor(leaf: WorkspaceLeaf) {
super(leaf); super(leaf);
} }
getViewType() { public getViewType(): string {
return SyncView.TYPE; return SyncView.TYPE;
} }
getDisplayText() { public getDisplayText(): string {
return "Example view"; return "Example view";
} }
async onOpen() { public async onOpen(): Promise<void> {
const container = this.containerEl.children[1]; const container = this.containerEl.children[1];
container.empty(); container.empty();
container.createEl("h4", { text: "Example view" }); 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]; const container = this.containerEl.children[1];
container.empty(); container.empty();
const messages = Logger.getInstance() const messages = Logger.getInstance()
.getMessages(LogLevel.INFO) .getMessages(LogLevel.DEBUG)
.map((message) => message.toString()) .map((message) => message.toString())
.join("\n"); .join("\n");
container.createEl("pre", { text: messages }); container.createEl("pre", { text: messages });
} }
async onClose() {
// Nothing to clean up.
}
} }