implement model methods for backup

This commit is contained in:
marlon-erler
2026-08-29 02:10:26 +02:00
parent 2c8b078b80
commit 2245fb5885
20 changed files with 11999 additions and 8945 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ export default class ChatModel {
this.storeInfo();
this.subscribe();
};
readonly setNamespace = (namespace: string): void => {
this.info.namespace = namespace;
this.storeInfo();
+16 -5
View File
@@ -57,7 +57,10 @@ export default class BoardsAndTasksModel {
];
};
readonly getTaskReferencePath = (boardId: string, fileId: string): string[] => {
readonly getTaskReferencePath = (
boardId: string,
fileId: string,
): string[] => {
return [...this.getTaskContainerPath(boardId), fileId];
};
@@ -99,17 +102,23 @@ export default class BoardsAndTasksModel {
return boardInfoFileContent;
};
readonly updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => {
readonly updateBoard = (
boardInfoFileContent: BoardInfoFileContent,
): void => {
this.storeBoard(boardInfoFileContent);
this.boardHandlerManager.trigger(boardInfoFileContent);
};
readonly updateBoardAndSend = (boardInfoFileContent: BoardInfoFileContent): void => {
readonly updateBoardAndSend = (
boardInfoFileContent: BoardInfoFileContent,
): void => {
this.updateBoard(boardInfoFileContent);
this.chatModel.sendMessage("", undefined, boardInfoFileContent);
};
readonly storeBoard = (boardInfoFileContent: BoardInfoFileContent): void => {
readonly storeBoard = (
boardInfoFileContent: BoardInfoFileContent,
): void => {
// store info
this.fileModel.storeFileContent(boardInfoFileContent);
@@ -194,7 +203,9 @@ export default class BoardsAndTasksModel {
return versionIds;
};
readonly getLatestTaskFileContent = (taskId: string): TaskFileContent | null => {
readonly getLatestTaskFileContent = (
taskId: string,
): TaskFileContent | null => {
const taskFileContentOrNull: TaskFileContent | null =
this.fileModel.getLatestFileContent(
taskId,
+6 -3
View File
@@ -12,11 +12,11 @@ export default class CalendarModel {
readonly settingsModel: SettingsModel;
// paths
get basePath (): string[] {
get basePath(): string[] {
return this.fileModel.getModelContainerPath(
FileModelSubPath.ModelCalendar,
);
};
}
readonly getViewPath = (): string[] => {
return [...this.basePath, FileModelSubPath.ModelView];
@@ -44,7 +44,10 @@ export default class CalendarModel {
this.storageModel.write(referencePath, "");
};
readonly deleteTaskReference = (monthString: string, taskId: string): void => {
readonly deleteTaskReference = (
monthString: string,
taskId: string,
): void => {
const monthPath: string[] = this.getMonthPath(monthString);
const referencePath: string[] = [...monthPath, taskId];
+14 -5
View File
@@ -29,13 +29,15 @@ export default class FileModel {
StorageModelSubPaths.Chat,
filePaths.chat.files(this.chatModel.id),
);
};
}
readonly getFileContainerPath = (): string[] => {
return [...this.basePath, FileModelSubPath.Data];
};
readonly getModelContainerPath = (modelName: FileModelSubPath): string[] => {
readonly getModelContainerPath = (
modelName: FileModelSubPath,
): string[] => {
return [...this.basePath, FileModelSubPath.Model, modelName];
};
@@ -43,13 +45,18 @@ export default class FileModel {
return [...this.getFileContainerPath(), fileId];
};
readonly getFileContentPath = (fileId: string, fileContentId: string): string[] => {
readonly getFileContentPath = (
fileId: string,
fileContentId: string,
): string[] => {
const filePath: string[] = this.getFilePath(fileId);
return [...filePath, fileContentId];
};
// handlers
readonly handleStringifiedFileContent = (stringifiedFileContent: string): void => {
readonly handleStringifiedFileContent = (
stringifiedFileContent: string,
): void => {
const fileContent: FileContent<string> | null = parseValidObject(
stringifiedFileContent,
FileContentReference,
@@ -68,7 +75,9 @@ export default class FileModel {
};
// methods
readonly addFileContentAndSend = (fileContent: FileContent<string>): void => {
readonly addFileContentAndSend = (
fileContent: FileContent<string>,
): void => {
this.handleFileContent(fileContent);
this.chatModel.sendMessage("", undefined, fileContent);
};
+38 -9
View File
@@ -69,7 +69,9 @@ export default class FileTransferModel {
this.handleTransferredFile(data.messageBody);
};
readonly handleTransferredFile = async (encryptedFileData: string): Promise<void> => {
readonly handleTransferredFile = async (
encryptedFileData: string,
): Promise<void> => {
if (this.transferData == undefined) return;
const decrypted: string = await decryptString(
@@ -77,9 +79,22 @@ export default class FileTransferModel {
this.transferData.key,
);
this.handleDecryptedFile(decrypted);
this.handleDecryptedFile(decrypted);
};
readonly handleBackupFile = async (encryptedBackup: string, passphrase: string): Promise<void> => {
const decrypted: string = await decryptString(encryptedBackup, passphrase);
const parsed = parse(decrypted);
if (!Array.isArray(parsed)) {
console.error("Canceling backup - not an array", decrypted);
return;
}
for (const file of parsed) {
this.handleDecryptedFile(file);
}
}
readonly handleDecryptedFile = (data: string): void => {
const parsed: any = parse(data);
const isFileData: boolean = checkMatchesObjectStructure(
@@ -95,25 +110,40 @@ export default class FileTransferModel {
...fileData.path,
);
this.fileHandlerManager.trigger(pathString);
}
};
// sending
readonly sendFiles = (
directoryPaths: IteratorObject<string[]>,
callback: (path: string) => void,
fileCallback: (path: string) => void,
): void => {
for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => {
this.prepareFileForSending(filePath);
const stringifiedFileData =
this.prepareFileForSending(filePath);
this.sendFile(stringifiedFileData);
const pathString: string = StorageModel.pathComponentsToString(
...filePath,
);
callback(pathString);
fileCallback(pathString);
});
}
};
readonly prepareFileForSending = (filePath: string[]): void => {
readonly generateBackup = async (directoryPaths: IteratorObject<string[]>, passphrase: string): Promise<string> => {
const files: string[] = [];
for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => {
const stringifiedFileData =
this.prepareFileForSending(filePath);
files.push(stringifiedFileData);
});
}
const rawBackup = stringify(files);
return await encryptString(rawBackup, passphrase);
};
readonly prepareFileForSending = (filePath: string[]): string => {
if (this.transferData == undefined) return;
const fileContent: string | null = this.storageModel.read(filePath);
if (fileContent == null) return;
@@ -122,8 +152,7 @@ export default class FileTransferModel {
path: filePath,
body: fileContent,
};
const stringifiedFileData: string = stringify(fileData);
this.sendFile(stringifiedFileData);
return stringify(fileData);
};
readonly sendFile = async (stringifiedFileData: string): Promise<void> => {
+1 -1
View File
@@ -21,7 +21,7 @@ export default class SettingsModel {
filePaths.settingsModel[pathName],
);
this.storageModel.write(path, value);
}
};
readonly setName = (newValue: string): void => {
this.username = newValue;
+4 -1
View File
@@ -69,7 +69,10 @@ export default class StorageModel {
};
// recursion
readonly recurse = (rootDirectory: string[], fn: (path: string[]) => void): void => {
readonly recurse = (
rootDirectory: string[],
fn: (path: string[]) => void,
): void => {
loop_over_files: for (const key of Object.keys(localStorage)) {
// get path of current entity
const pathComponentsOfCurrentEntity: string[] =
+1 -1
View File
@@ -81,4 +81,4 @@
#chat-page > div > #main > #calendar-page .grid-pane-wrapper {
border-right: 1px solid var(--border);
}
}
}
+1 -3
View File
@@ -103,9 +103,7 @@ export function SettingsPage(
coreViewModel.translations.chatPage.settings
.setNamespaceButtonAudioLabel
}
on:click={
settingsPageViewModel.setNamespace
}
on:click={settingsPageViewModel.setNamespace}
toggle:disabled={
settingsPageViewModel.cannotSetNamespace
}
@@ -20,8 +20,8 @@ export default class NotificationViewModel {
if (this.chatListViewModel.selectedChat.value == undefined) return;
const currentChat =
this.chatListViewModel.selectedChat.value.chatModel.
unwrappedPrimaryChannel
this.chatListViewModel.selectedChat.value.chatModel
.unwrappedPrimaryChannel;
const currentPage =
this.chatListViewModel.selectedChat.value.selectedPage.value;
if (
@@ -41,7 +41,8 @@ export default class NotificationViewModel {
const chat = [
...this.chatListViewModel.chatViewModels.value.values(),
].find(
(chat) => chat.chatModel.unwrappedPrimaryChannel == notification.chat,
(chat) =>
chat.chatModel.unwrappedPrimaryChannel == notification.chat,
);
chat.open();
chat.openPage(ChatPageTypes.Messages);
@@ -79,7 +80,7 @@ export default class NotificationViewModel {
};
// init
constructor(public readonly chatListViewModel: ChatListViewModel) { }
constructor(public readonly chatListViewModel: ChatListViewModel) {}
// util
static createNotification(message: ChatMessage): Notification {
+6 -13
View File
@@ -32,8 +32,7 @@ export default class SettingsPageViewModel extends Context {
);
cannotSetNamespace: React.State<boolean> = React.createProxyState(
[this.namespace, this.namespaceInput],
() =>
this.namespaceInput.value == this.namespace.value,
() => this.namespaceInput.value == this.namespace.value,
);
cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState(
[this.newSecondaryChannelInput],
@@ -53,11 +52,8 @@ export default class SettingsPageViewModel extends Context {
};
setNamespace = (): void => {
this.chatViewModel.chatModel.setNamespace(
this.namespaceInput.value,
);
this.namespace.value =
this.chatViewModel.chatModel.info.namespace;
this.chatViewModel.chatModel.setNamespace(this.namespaceInput.value);
this.namespace.value = this.chatViewModel.chatModel.info.namespace;
};
addSecondaryChannel = (): void => {
@@ -101,17 +97,14 @@ export default class SettingsPageViewModel extends Context {
preloadData = (): void => {
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
this.namespace.value =
this.chatViewModel.chatModel.info.namespace;
this.namespace.value = this.chatViewModel.chatModel.info.namespace;
this.color.value = this.chatViewModel.chatModel.color;
};
loadData = (): void => {
this.primaryChannelInput.value =
this.primaryChannel.value;
this.namespaceInput.value =
this.namespace.value;
this.primaryChannelInput.value = this.primaryChannel.value;
this.namespaceInput.value = this.namespace.value;
this.loadSecondaryChannels();