fix: file saving not working on Ubuntu snap #4901

This commit is contained in:
Johannes Millan 2025-08-04 21:27:25 +02:00
parent ec44bd1391
commit 62c2649320
7 changed files with 122 additions and 1828 deletions

View file

@ -65,6 +65,11 @@ export interface ElectronAPI {
openExternalUrl(url: string): void;
saveFileDialog(
filename: string,
data: string,
): Promise<{ success: boolean; path?: string }>;
isLinux(): boolean;
isMacOS(): boolean;

View file

@ -54,6 +54,23 @@ export const initIpcInterfaces = (): void => {
ipcMain.on(IPC.OPEN_PATH, (ev, path: string) => shell.openPath(path));
ipcMain.on(IPC.OPEN_EXTERNAL, (ev, url: string) => shell.openExternal(url));
ipcMain.handle(IPC.SAVE_FILE_DIALOG, async (ev, { filename, data }) => {
const result = await dialog.showSaveDialog(getWin(), {
defaultPath: filename,
filters: [
{ name: 'JSON Files', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] },
],
});
if (!result.canceled && result.filePath) {
const fs = await import('fs');
await fs.promises.writeFile(result.filePath, data, 'utf-8');
return { success: true, path: result.filePath };
}
return { success: false };
});
ipcMain.on(IPC.LOCK_SCREEN, () => {
if ((app as any).isLocked) {
return;

View file

@ -75,6 +75,11 @@ const ea: ElectronAPI = {
openPath: (path: string) => _send('OPEN_PATH', path),
openExternalUrl: (url: string) => _send('OPEN_EXTERNAL', url),
saveFileDialog: (filename: string, data: string) =>
_invoke('SAVE_FILE_DIALOG', { filename, data }) as Promise<{
success: boolean;
path?: string;
}>,
scheduleRegisterBeforeClose: (id) => _send('REGISTER_BEFORE_CLOSE', { id }),
unscheduleRegisterBeforeClose: (id) => _send('UNREGISTER_BEFORE_CLOSE', { id }),
setDoneRegisterBeforeClose: (id) => _send('BEFORE_CLOSE_DONE', { id }),

View file

@ -61,6 +61,8 @@ export enum IPC {
OPEN_PATH = 'OPEN_PATH',
OPEN_EXTERNAL = 'OPEN_EXTERNAL',
SAVE_FILE_DIALOG = 'SAVE_FILE_DIALOG',
// Plugin Node Execution
PLUGIN_EXEC_NODE_SCRIPT = 'PLUGIN_EXEC_NODE_SCRIPT',

1879
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -194,11 +194,13 @@ export class FileImexComponent implements OnInit {
async downloadBackup(): Promise<void> {
const data = await this._pfapiService.pf.loadCompleteBackup();
download('super-productivity-backup.json', JSON.stringify(data));
if (IS_ANDROID_WEB_VIEW) {
const result = await download('super-productivity-backup.json', JSON.stringify(data));
if (IS_ANDROID_WEB_VIEW || result.isSnap) {
this._snackService.open({
type: 'SUCCESS',
msg: T.FILE_IMEX.S_BACKUP_DOWNLOADED,
msg: result.path
? `Backup saved to: ${result.path}`
: T.FILE_IMEX.S_BACKUP_DOWNLOADED,
});
}
// download('super-productivity-backup.json', privacyExport(data));
@ -206,11 +208,13 @@ export class FileImexComponent implements OnInit {
async privacyAppDataDownload(): Promise<void> {
const data = await this._pfapiService.pf.loadCompleteBackup();
download('super-productivity-backup.json', privacyExport(data));
if (IS_ANDROID_WEB_VIEW) {
const result = await download('super-productivity-backup.json', privacyExport(data));
if (IS_ANDROID_WEB_VIEW || result.isSnap) {
this._snackService.open({
type: 'SUCCESS',
msg: T.FILE_IMEX.S_BACKUP_DOWNLOADED,
msg: result.path
? `Backup saved to: ${result.path}`
: T.FILE_IMEX.S_BACKUP_DOWNLOADED,
});
}
}

View file

@ -2,13 +2,31 @@ import { saveAs } from 'file-saver';
import { Directory, Encoding, Filesystem, WriteFileResult } from '@capacitor/filesystem';
import { IS_ANDROID_WEB_VIEW } from './is-android-web-view';
import { Log } from '../core/log';
import '../core/window-ea';
export const download = async (filename: string, stringData: string): Promise<void> => {
const blob = new Blob([stringData], { type: 'text/plain;charset=utf-8' });
const isRunningInSnap = (): boolean => {
return !!window.ea?.isSnap?.();
};
export const download = async (
filename: string,
stringData: string,
): Promise<{ isSnap?: boolean; path?: string }> => {
if (IS_ANDROID_WEB_VIEW) {
await saveStringAsFile(filename, stringData);
return {};
} else if (isRunningInSnap() && window.ea?.saveFileDialog) {
// Use native dialog for snap to avoid AppArmor permission issues
const result = await window.ea.saveFileDialog(filename, stringData);
if (result.success && result.path) {
Log.log('File saved to:', result.path);
return { isSnap: true, path: result.path };
}
return { isSnap: true };
} else {
const blob = new Blob([stringData], { type: 'text/plain;charset=utf-8' });
saveAs(blob, filename);
return {};
}
};
@ -33,6 +51,6 @@ const saveStringAsFile = async (
};
// interestingly this can't live in the logs.ts or it leads to weird "window" not found errors
export const downloadLogs = (): void => {
download('SP-logs.json', Log.exportLogHistory());
export const downloadLogs = async (): Promise<void> => {
await download('SP-logs.json', Log.exportLogHistory());
};