Fixes the issue with attachments replacing each other locally so files with same filename appears as the same file
132 lines
3.4 KiB
TypeScript
132 lines
3.4 KiB
TypeScript
import { Injectable, inject } from '@angular/core';
|
|
import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service';
|
|
import type { Attachment } from '../domain/attachment.models';
|
|
import {
|
|
resolveAttachmentStorageBucket,
|
|
resolveAttachmentStoredFilename,
|
|
sanitizeAttachmentRoomName
|
|
} from './attachment-storage.helpers';
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AttachmentStorageService {
|
|
private readonly electronBridge = inject(ElectronBridgeService);
|
|
|
|
async resolveExistingPath(
|
|
attachment: Pick<Attachment, 'filePath' | 'savedPath'>
|
|
): Promise<string | null> {
|
|
return this.findExistingPath([attachment.filePath, attachment.savedPath]);
|
|
}
|
|
|
|
async resolveLegacyImagePath(filename: string, roomName: string): Promise<string | null> {
|
|
const appDataPath = await this.resolveAppDataPath();
|
|
|
|
if (!appDataPath) {
|
|
return null;
|
|
}
|
|
|
|
return this.findExistingPath([`${appDataPath}/server/${sanitizeAttachmentRoomName(roomName)}/image/${filename}`]);
|
|
}
|
|
|
|
async readFile(filePath: string): Promise<string | null> {
|
|
const electronApi = this.electronBridge.getApi();
|
|
|
|
if (!electronApi || !filePath) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return await electronApi.readFile(filePath);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async saveBlob(
|
|
attachment: Pick<Attachment, 'id' | 'filename' | 'mime'>,
|
|
blob: Blob,
|
|
roomName: string
|
|
): Promise<string | null> {
|
|
const electronApi = this.electronBridge.getApi();
|
|
const appDataPath = await this.resolveAppDataPath();
|
|
|
|
if (!electronApi || !appDataPath) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const directoryPath = `${appDataPath}/server/${sanitizeAttachmentRoomName(roomName)}/${resolveAttachmentStorageBucket(attachment.mime)}`;
|
|
|
|
await electronApi.ensureDir(directoryPath);
|
|
|
|
const arrayBuffer = await blob.arrayBuffer();
|
|
const diskPath = `${directoryPath}/${resolveAttachmentStoredFilename(attachment.id, attachment.filename)}`;
|
|
|
|
await electronApi.writeFile(diskPath, this.arrayBufferToBase64(arrayBuffer));
|
|
|
|
return diskPath;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async deleteFile(filePath: string): Promise<void> {
|
|
const electronApi = this.electronBridge.getApi();
|
|
|
|
if (!electronApi || !filePath) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await electronApi.deleteFile(filePath);
|
|
} catch { /* best-effort cleanup */ }
|
|
}
|
|
|
|
private async resolveAppDataPath(): Promise<string | null> {
|
|
const electronApi = this.electronBridge.getApi();
|
|
|
|
if (!electronApi) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return await electronApi.getAppDataPath();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async findExistingPath(candidates: (string | null | undefined)[]): Promise<string | null> {
|
|
const electronApi = this.electronBridge.getApi();
|
|
|
|
if (!electronApi) {
|
|
return null;
|
|
}
|
|
|
|
for (const candidatePath of candidates) {
|
|
if (!candidatePath) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
if (await electronApi.fileExists(candidatePath)) {
|
|
return candidatePath;
|
|
}
|
|
} catch { /* keep trying remaining candidates */ }
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
let binary = '';
|
|
|
|
const bytes = new Uint8Array(buffer);
|
|
|
|
for (let index = 0; index < bytes.byteLength; index++) {
|
|
binary += String.fromCharCode(bytes[index]);
|
|
}
|
|
|
|
return btoa(binary);
|
|
}
|
|
}
|