APP-Backend / src /services /file-system.js
Luca448's picture
Feat: Add native support for opening PDF, TXT, and CSV files directly from the folder view
e99ebba
Raw History Blame Contribute Delete
8.79 kB
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { db } from '../db/database.js';
class FileSystemService {
constructor() {
this.directory = Directory.Documents;
this.baseFolder = 'SchoolMind';
}
async init() {
try {
await Filesystem.mkdir({
path: this.baseFolder,
directory: this.directory,
recursive: true
});
} catch (e) {}
try {
await Filesystem.mkdir({
path: `${this.baseFolder}/.trash`,
directory: this.directory,
recursive: true
});
} catch (e) {}
// Run trash cleanup on init
this.cleanupTrash();
}
/**
* Liest alle Dateien in einem Ordner.
*/
async readDir(path = '') {
try {
const fullPath = path ? `${this.baseFolder}/${path}` : this.baseFolder;
const result = await Filesystem.readdir({
path: fullPath,
directory: this.directory
});
// Filter out internal .trash folder
return result.files.filter(f => {
const name = typeof f === 'string' ? f : f.name;
return name && !name.includes('.trash');
});
} catch (e) {
console.error('Error reading directory:', e);
return [];
}
}
/**
* Liest den Inhalt einer Datei als String.
*/
async readFile(filePath) {
try {
const fullPath = `${this.baseFolder}/${filePath}`;
const result = await Filesystem.readFile({
path: fullPath,
directory: this.directory,
encoding: Encoding.UTF8
});
return result.data;
} catch (e) {
console.error('Error reading file:', e);
return null;
}
}
/**
* Liest eine Datei als Base64-String.
*/
async readBinaryFile(filePath) {
try {
const fullPath = `${this.baseFolder}/${filePath}`;
const result = await Filesystem.readFile({
path: fullPath,
directory: this.directory
});
return result.data; // returns base64
} catch (e) {
console.error('Error reading binary file:', e);
return null;
}
}
/**
* Speichert einen Text in einer Datei (überschreibt bestehende).
*/
async writeFile(filePath, data) {
try {
const fullPath = `${this.baseFolder}/${filePath}`;
// Ensure parent directory exists first by getting the dir part
const parts = fullPath.split('/');
parts.pop();
if (parts.length > 1) {
const parentDir = parts.join('/');
try {
await Filesystem.mkdir({
path: parentDir,
directory: this.directory,
recursive: true
});
} catch (e) {
// ignore "Directory exists" error
}
}
await Filesystem.writeFile({
path: fullPath,
data: data,
directory: this.directory,
encoding: Encoding.UTF8
});
return true;
} catch (e) {
console.error('Error writing file:', e);
return false;
}
}
/**
* Speichert eine binäre Datei (z.B. PDF) aus einem Base64 String.
*/
async writeBinaryFile(filePath, base64Data) {
try {
const fullPath = `${this.baseFolder}/${filePath}`;
const parts = fullPath.split('/');
parts.pop();
if (parts.length > 1) {
const parentDir = parts.join('/');
try {
await Filesystem.mkdir({ path: parentDir, directory: this.directory, recursive: true });
} catch (e) {}
}
await Filesystem.writeFile({
path: fullPath,
data: base64Data, // without encoding it writes binary
directory: this.directory
});
return true;
} catch (e) {
console.error('Error writing binary file:', e);
return false;
}
}
/**
* Erstellt einen neuen Ordner.
*/
async createFolder(path) {
try {
const fullPath = `${this.baseFolder}/${path}`;
await Filesystem.mkdir({
path: fullPath,
directory: this.directory,
recursive: true
});
return true;
} catch (e) {
console.error('Error creating folder:', e);
return false;
}
}
/**
* Löscht eine Datei oder einen Ordner.
*/
async deletePath(path, isDir = false) {
try {
const fullPath = `${this.baseFolder}/${path}`;
if (isDir) {
await Filesystem.rmdir({
path: fullPath,
directory: this.directory,
recursive: true
});
} else {
await Filesystem.deleteFile({
path: fullPath,
directory: this.directory
});
}
return true;
} catch (e) {
console.error('Error deleting path:', e);
return false;
}
}
/**
* Verschiebt eine Datei oder einen Ordner in den Papierkorb (.trash).
*/
async moveToTrash(path, isDir = false) {
try {
const id = 'trash_' + Date.now();
const fromPath = `${this.baseFolder}/${path}`;
const toPath = `${this.baseFolder}/.trash/${id}`;
await Filesystem.rename({
from: fromPath,
to: toPath,
directory: this.directory
});
db.addTrashItem(id, path, isDir);
return true;
} catch (e) {
console.error('Error moving to trash:', e);
alert('Konnte Element nicht in den Papierkorb verschieben: ' + e.message);
return false;
}
}
/**
* Stellt ein Element aus dem Papierkorb wieder her.
*/
async restoreFromTrash(id) {
try {
const items = db.getTrashItems();
const item = items.find(i => i.id === id);
if (!item) return false;
const fromPath = `${this.baseFolder}/.trash/${id}`;
let toPath = `${this.baseFolder}/${item.original_path}`;
// Verify the source file actually exists
try {
await Filesystem.stat({
path: fromPath,
directory: this.directory
});
} catch (e) {
// If it doesn't exist, remove from DB and clean up the UI
db.removeTrashItem(id);
alert('Dieses Element konnte nicht wiederhergestellt werden, da die eigentliche Datei nicht mehr im Papierkorb-Ordner gefunden wurde (vermutlich wurde sie bereits gelöscht). Der fehlerhafte Eintrag wird jetzt entfernt.');
return false;
}
// Ensure parent directory exists for the restored item
const parts = toPath.split('/');
parts.pop();
if (parts.length > 1) {
const parentDir = parts.join('/');
try {
await Filesystem.mkdir({
path: parentDir,
directory: this.directory,
recursive: true
});
} catch (e) {}
}
// Conflict resolution: Check if destination already exists
let finalToPath = toPath;
let suffix = 1;
let nameWithoutExt = item.original_path;
let ext = '';
const isDir = item.is_dir === 1;
if (!isDir && item.original_path.includes('.')) {
const lastDot = item.original_path.lastIndexOf('.');
nameWithoutExt = item.original_path.substring(0, lastDot);
ext = item.original_path.substring(lastDot);
}
while (true) {
try {
await Filesystem.stat({
path: finalToPath,
directory: this.directory
});
// If stat succeeds, file exists! We must try a new name.
finalToPath = `${this.baseFolder}/${nameWithoutExt} (${suffix})${ext}`;
suffix++;
} catch (e) {
// Stat failed, meaning file does NOT exist. We found a valid path!
break;
}
}
await Filesystem.rename({
from: fromPath,
to: finalToPath,
directory: this.directory
});
// If the name changed and it was a folder, we might want to update metadata if needed, but it's okay, it just loses its custom icon if renamed.
db.removeTrashItem(id);
return true;
} catch (e) {
console.error('Error restoring from trash:', e);
alert('Restore Error: ' + e.message);
return false;
}
}
/**
* Löscht endgültig alle Elemente, die älter als 30 Tage sind.
*/
async cleanupTrash() {
try {
const items = db.getTrashItems();
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
const now = Date.now();
for (const item of items) {
const deletedAt = new Date(item.deleted_at).getTime();
if (now - deletedAt > THIRTY_DAYS_MS) {
const isDir = item.is_dir === 1;
const success = await this.deletePath(`.trash/${item.id}`, isDir);
if (success) {
db.removeTrashItem(item.id);
}
}
}
} catch (e) {
console.error('Error during trash cleanup:', e);
}
}
}
export const fsService = new FileSystemService();