WIP: Migrate to using taskfile #187

Closed
schmelczer wants to merge 26 commits from asch/taskfiles into main
10 changed files with 92 additions and 98 deletions
Showing only changes of commit e3a90833ff - Show all commits
Andras Schmelczer 2026-01-04 14:14:05 +00:00

View file

@ -74,7 +74,6 @@ export class Database {
this.documents.forEach((doc) => { this.documents.forEach((doc) => {
this.lastSeenUpdateIds.add(doc.metadata?.parentVersionId); this.lastSeenUpdateIds.add(doc.metadata?.parentVersionId);
}); });
} }
public get length(): number { public get length(): number {
@ -104,7 +103,7 @@ export class Database {
i === 0 i === 0
? false ? false
: records[i - 1].parallelVersion === : records[i - 1].parallelVersion ===
current.parallelVersion current.parallelVersion
) )
) { ) {
throw new Error( throw new Error(
@ -282,7 +281,6 @@ export class Database {
candidate.isDeleted = true; candidate.isDeleted = true;
} }
public getLastSeenUpdateId(): VaultUpdateId { public getLastSeenUpdateId(): VaultUpdateId {
return this.lastSeenUpdateIds.min; return this.lastSeenUpdateIds.min;
} }
@ -317,7 +315,7 @@ export class Database {
...metadata! // `resolvedDocuments` only returns docs with metadata set ...metadata! // `resolvedDocuments` only returns docs with metadata set
}) })
), ),
lastSeenUpdateId: this.lastSeenUpdateIds.min, lastSeenUpdateId: this.lastSeenUpdateIds.min
}); });
} }
@ -341,7 +339,7 @@ export class Database {
if (duplicates.length > 0) { if (duplicates.length > 0) {
throw new Error( throw new Error(
"Document IDs are not unique, found duplicates: " + "Document IDs are not unique, found duplicates: " +
duplicates.join("; ") duplicates.join("; ")
); );
} }
} }

View file

@ -157,7 +157,8 @@ export class SyncService {
(await response.json()) as DocumentUpdateResponse; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion (await response.json()) as DocumentUpdateResponse; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
this.logger.debug( this.logger.debug(
`Updated document ${JSON.stringify(result)} with id ${result.documentId `Updated document ${JSON.stringify(result)} with id ${
result.documentId
}}` }}`
); );
@ -209,7 +210,8 @@ export class SyncService {
(await response.json()) as DocumentUpdateResponse; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion (await response.json()) as DocumentUpdateResponse; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
this.logger.debug( this.logger.debug(
`Updated document ${JSON.stringify(result)} with id ${result.documentId `Updated document ${JSON.stringify(result)} with id ${
result.documentId
}}` }}`
); );
@ -336,7 +338,7 @@ export class SyncService {
return this.retryForever(async () => { return this.retryForever(async () => {
this.logger.debug( this.logger.debug(
"Getting all documents" + "Getting all documents" +
(since != null ? ` since ${since}` : "") (since != null ? ` since ${since}` : "")
); );
const url = new URL(this.getUrl("/documents")); const url = new URL(this.getUrl("/documents"));

View file

@ -40,7 +40,7 @@ export class WebSocketManager {
private readonly logger: Logger, private readonly logger: Logger,
private readonly settings: Settings, private readonly settings: Settings,
private readonly webSocketFactoryImplementation: typeof globalThis.WebSocket = WebSocket private readonly webSocketFactoryImplementation: typeof globalThis.WebSocket = WebSocket
) { } ) {}
public get isWebSocketConnected(): boolean { public get isWebSocketConnected(): boolean {
return ( return (
@ -260,10 +260,9 @@ export class WebSocketManager {
this.resolveDisconnectingPromise?.(); this.resolveDisconnectingPromise?.();
this.resolveDisconnectingPromise = null; this.resolveDisconnectingPromise = null;
} else { } else {
const delay = this.settings.getSettings().webSocketRetryIntervalMs; const delay =
this.logger.info( this.settings.getSettings().webSocketRetryIntervalMs;
`Reconnecting to WebSocket in ${delay}ms...` this.logger.info(`Reconnecting to WebSocket in ${delay}ms...`);
);
this.reconnectTimeoutId = setTimeout(() => { this.reconnectTimeoutId = setTimeout(() => {
this.reconnectTimeoutId = undefined; this.reconnectTimeoutId = undefined;
this.initializeWebSocket(); this.initializeWebSocket();

View file

@ -56,7 +56,7 @@ export class SyncClient {
database: Partial<StoredDatabase>; database: Partial<StoredDatabase>;
}> }>
> >
) { } ) {}
public get documentCount(): number { public get documentCount(): number {
return this.database.length; return this.database.length;

View file

@ -166,7 +166,7 @@ export class Syncer {
// in that case, we mustn't move it again. // in that case, we mustn't move it again.
if ( if (
this.database.getLatestDocumentByRelativePath(relativePath) === this.database.getLatestDocumentByRelativePath(relativePath) ===
undefined || undefined ||
this.database.getLatestDocumentByRelativePath(relativePath) this.database.getLatestDocumentByRelativePath(relativePath)
?.isDeleted === true ?.isDeleted === true
) { ) {
@ -484,6 +484,5 @@ export class Syncer {
return this.syncLocallyDeletedFile(relativePath); return this.syncLocallyDeletedFile(relativePath);
}) })
); );
} }
} }

View file

@ -87,7 +87,7 @@ export class UnrestrictedSyncer {
forceMerge: true forceMerge: true
}); });
this.handleMaybeMergingResponse({ await this.handleMaybeMergingResponse({
document, document,
response, response,
contentHash, contentHash,
@ -159,14 +159,14 @@ export class UnrestrictedSyncer {
const updateDetails: SyncUpdateDetails | SyncMovedDetails = const updateDetails: SyncUpdateDetails | SyncMovedDetails =
oldPath !== undefined oldPath !== undefined
? { ? {
type: SyncType.MOVE, type: SyncType.MOVE,
relativePath: document.relativePath, relativePath: document.relativePath,
movedFrom: oldPath movedFrom: oldPath
} }
: { : {
type: SyncType.UPDATE, type: SyncType.UPDATE,
relativePath: document.relativePath relativePath: document.relativePath
}; };
await this.executeSync(updateDetails, async () => { await this.executeSync(updateDetails, async () => {
const originalRelativePath = document.relativePath; const originalRelativePath = document.relativePath;
@ -181,7 +181,7 @@ export class UnrestrictedSyncer {
const contentBytes = await this.operations.read( const contentBytes = await this.operations.read(
document.relativePath document.relativePath
); // this can throw FileNotFoundError ); // this can throw FileNotFoundError
let contentHash = hash(contentBytes); const contentHash = hash(contentBytes);
const areThereLocalChanges = !( const areThereLocalChanges = !(
document.metadata.hash === contentHash && oldPath === undefined document.metadata.hash === contentHash && oldPath === undefined
@ -205,22 +205,22 @@ export class UnrestrictedSyncer {
response = response =
isText && cachedVersion !== undefined isText && cachedVersion !== undefined
? await this.syncService.putText({ ? await this.syncService.putText({
documentId: document.metadata.documentId, documentId: document.metadata.documentId,
parentVersionId: parentVersionId:
document.metadata.parentVersionId, document.metadata.parentVersionId,
relativePath: document.relativePath, relativePath: document.relativePath,
content: diff( content: diff(
new TextDecoder().decode(cachedVersion), new TextDecoder().decode(cachedVersion),
new TextDecoder().decode(contentBytes) new TextDecoder().decode(contentBytes)
) )
}) })
: await this.syncService.putBinary({ : await this.syncService.putBinary({
documentId: document.metadata.documentId, documentId: document.metadata.documentId,
parentVersionId: parentVersionId:
document.metadata.parentVersionId, document.metadata.parentVersionId,
relativePath: document.relativePath, relativePath: document.relativePath,
contentBytes contentBytes
}); });
} else { } else {
if (!force) { if (!force) {
this.logger.debug( this.logger.debug(
@ -234,10 +234,9 @@ export class UnrestrictedSyncer {
}); });
} }
await this.handleMaybeMergingResponse({
this.handleMaybeMergingResponse({
document, document,
response: response!, response: response,
contentHash, contentHash,
originalRelativePath, originalRelativePath,
originalContentBytes: contentBytes originalContentBytes: contentBytes
@ -255,16 +254,16 @@ export class UnrestrictedSyncer {
const actualUpdateDetails: SyncUpdateDetails | SyncMovedDetails = const actualUpdateDetails: SyncUpdateDetails | SyncMovedDetails =
oldPath !== undefined || oldPath !== undefined ||
response.relativePath != originalRelativePath response.relativePath != originalRelativePath
? { ? {
type: SyncType.MOVE, type: SyncType.MOVE,
relativePath: response.relativePath, relativePath: response.relativePath,
movedFrom: oldPath ?? originalRelativePath movedFrom: oldPath ?? originalRelativePath
} }
: { : {
type: SyncType.UPDATE, type: SyncType.UPDATE,
relativePath: response.relativePath relativePath: response.relativePath
}; };
if (areThereLocalChanges) { if (areThereLocalChanges) {
this.history.addHistoryEntry({ this.history.addHistoryEntry({
@ -288,7 +287,8 @@ export class UnrestrictedSyncer {
type: SyncType.DELETE, type: SyncType.DELETE,
relativePath: document.relativePath relativePath: document.relativePath
}, },
message: "File has been deleted remotely, so we deleted it locally", message:
"File has been deleted remotely, so we deleted it locally",
author: response.userId, author: response.userId,
timestamp: new Date(response.updatedDate) timestamp: new Date(response.updatedDate)
}); });
@ -460,24 +460,21 @@ export class UnrestrictedSyncer {
} }
} }
private async handleMaybeMergingResponse( private async handleMaybeMergingResponse({
{ document,
document, response,
response, contentHash,
contentHash, originalRelativePath,
originalRelativePath, originalContentBytes
originalContentBytes }: {
}: { document: DocumentRecord;
document: DocumentRecord; response: DocumentVersion | DocumentUpdateResponse;
response: DocumentVersion | DocumentUpdateResponse, contentHash: string;
contentHash: string, originalRelativePath: string;
originalRelativePath: string, originalContentBytes: Uint8Array;
originalContentBytes: Uint8Array }): Promise<void> {
}
): Promise<void> {
// `document` is mutable and reflects the latest state in the local database // `document` is mutable and reflects the latest state in the local database
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (document.isDeleted) { if (document.isDeleted) {
this.logger.info( this.logger.info(
`Document ${document.relativePath} has been deleted before we could finish updating it` `Document ${document.relativePath} has been deleted before we could finish updating it`
@ -572,8 +569,9 @@ export class UnrestrictedSyncer {
type: SyncType.SKIPPED, type: SyncType.SKIPPED,
relativePath relativePath
}, },
message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${maxFileSizeMB message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${
} MB` maxFileSizeMB
} MB`
}; };
} }
} }
@ -598,8 +596,6 @@ export class UnrestrictedSyncer {
document: DocumentRecord, document: DocumentRecord,
response: DocumentVersion | DocumentUpdateResponse response: DocumentVersion | DocumentUpdateResponse
): Promise<void> { ): Promise<void> {
this.database.delete(document.relativePath); this.database.delete(document.relativePath);
this.database.updateDocumentMetadata( this.database.updateDocumentMetadata(
{ {

View file

@ -102,13 +102,13 @@ export class MockClient implements FileSystemOperations {
.map((part) => part.trim()); .map((part) => part.trim());
const newParts = newContent.split(" ").map((part) => part.trim()); const newParts = newContent.split(" ").map((part) => part.trim());
existingParts.forEach((part) => existingParts.forEach((part) =>
// all changes should be additive // all changes should be additive
{ {
assert( assert(
newParts.includes(part), newParts.includes(part),
`Part ${part} not found in new content: ${newContent}` `Part ${part} not found in new content: ${newContent}`
); );
} }
); );
} }

View file

@ -9,24 +9,24 @@ server:
max_clients_per_vault: 256 max_clients_per_vault: 256
response_timeout: 30m response_timeout: 30m
mergeable_file_extensions: mergeable_file_extensions:
- md - md
- txt - txt
users: users:
user_configs: user_configs:
- name: admin - name: admin
token: test-token-change-me token: test-token-change-me
vault_access: vault_access:
type: allow_access_to_all type: allow_access_to_all
- name: other-admin - name: other-admin
token: test-token-change-me2 token: test-token-change-me2
vault_access: vault_access:
type: allow_access_to_all type: allow_access_to_all
- name: test - name: test
token: other-test-token token: other-test-token
vault_access: vault_access:
type: allow_list type: allow_list
allowed: allowed:
- default - default
logging: logging:
log_directory: logs log_directory: logs
log_rotation: 7days log_rotation: 7days

View file

@ -249,7 +249,7 @@ pub async fn merge_with_stored_version(
}; };
// We can only update the relative path if we're the first one to do so // We can only update the relative path if we're the first one to do so
let new_relative_path = if parent_document_path == &latest_version.relative_path let new_relative_path = if parent_document_path == latest_version.relative_path
&& latest_version.relative_path != sanitized_relative_path && latest_version.relative_path != sanitized_relative_path
{ {
let new_path = find_first_available_path( let new_path = find_first_available_path(

View file

@ -1,7 +1,7 @@
use crate::app_state::database::models::VaultId; use crate::app_state::database::models::VaultId;
use crate::{app_state::database::Transaction, utils::dedup_paths::dedup_paths}; use crate::{app_state::database::Transaction, utils::dedup_paths::dedup_paths};
use anyhow::Result; use anyhow::Result;
use log::{debug, info}; use log::info;
pub async fn find_first_available_path( pub async fn find_first_available_path(
vault_id: &VaultId, vault_id: &VaultId,