Implement basic file handling functions in main
This commit is contained in:
parent
920cc53ce3
commit
9096973dc6
|
|
@ -13,6 +13,12 @@ type FolderTree = {
|
|||
children?: FolderTree[];
|
||||
};
|
||||
|
||||
// Track the currently opened folder for security checks
|
||||
let currentWorkspaceRoot: string | null = null;
|
||||
|
||||
// Track previously opened files outside the workspace
|
||||
const openedFiles = new Set<string>();
|
||||
|
||||
async function readTree(dirPath: string): Promise<FolderTree[]> {
|
||||
const stats = await fsp.stat(dirPath);
|
||||
if (!stats.isDirectory()) return [];
|
||||
|
|
@ -35,7 +41,7 @@ async function readTree(dirPath: string): Promise<FolderTree[]> {
|
|||
type: "file" as const,
|
||||
};
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
return children;
|
||||
}
|
||||
|
|
@ -48,6 +54,7 @@ export async function handleOpenFolder(
|
|||
});
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
const folderPath = result.filePaths[0];
|
||||
currentWorkspaceRoot = folderPath; // Track the opened folder
|
||||
return {
|
||||
name: path.basename(folderPath),
|
||||
path: folderPath,
|
||||
|
|
@ -57,3 +64,217 @@ export async function handleOpenFolder(
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Security helper: Check if a path is within the current workspace or previously opened
|
||||
function isPathSecure(filePath: string): boolean {
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
|
||||
// Allow if within workspace
|
||||
if (currentWorkspaceRoot) {
|
||||
const resolvedRoot = path.resolve(currentWorkspaceRoot);
|
||||
|
||||
if (
|
||||
resolvedPath.startsWith(resolvedRoot + path.sep) ||
|
||||
resolvedPath === resolvedRoot
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow if previously opened
|
||||
if (openedFiles.has(resolvedPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// File Operations
|
||||
export async function handleReadFile(
|
||||
mainWindow: BrowserWindow,
|
||||
filePath?: string,
|
||||
): Promise<{ content: string; path: string } | null> {
|
||||
let targetPath: string;
|
||||
|
||||
if (filePath) {
|
||||
// If path is provided, check if it's secure
|
||||
if (!isPathSecure(filePath)) {
|
||||
throw new Error(
|
||||
"Access denied: File is outside the workspace and not previously opened",
|
||||
);
|
||||
}
|
||||
targetPath = filePath;
|
||||
} else {
|
||||
// Show file dialog
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openFile"],
|
||||
defaultPath: currentWorkspaceRoot || undefined,
|
||||
// filters: [
|
||||
// { name: "All Files", extensions: ["*"] },
|
||||
// {
|
||||
// name: "Text Files",
|
||||
// extensions: [
|
||||
// "txt",
|
||||
// "md",
|
||||
// "js",
|
||||
// "ts",
|
||||
// "json",
|
||||
// "html",
|
||||
// "css",
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
});
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
targetPath = result.filePaths[0];
|
||||
|
||||
// Track files opened via dialog to allow future access
|
||||
openedFiles.add(path.resolve(targetPath));
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fsp.readFile(targetPath, "utf8");
|
||||
return { content, path: targetPath };
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSaveFile(
|
||||
mainWindow: BrowserWindow,
|
||||
content: string,
|
||||
filePath?: string,
|
||||
): Promise<{ path: string } | null> {
|
||||
let targetPath: string;
|
||||
|
||||
if (filePath) {
|
||||
// If path is provided, check if it's secure
|
||||
if (!isPathSecure(filePath)) {
|
||||
throw new Error(
|
||||
"Access denied: File is outside the workspace and not previously opened",
|
||||
);
|
||||
}
|
||||
targetPath = filePath;
|
||||
} else {
|
||||
// Show save dialog
|
||||
const result = await dialog.showSaveDialog(mainWindow, {
|
||||
defaultPath: currentWorkspaceRoot || undefined,
|
||||
// filters: [
|
||||
// { name: "All Files", extensions: ["*"] },
|
||||
// {
|
||||
// name: "Text Files",
|
||||
// extensions: [
|
||||
// "txt",
|
||||
// "md",
|
||||
// "js",
|
||||
// "ts",
|
||||
// "json",
|
||||
// "html",
|
||||
// "css",
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
});
|
||||
|
||||
if (result.canceled || !result.filePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
targetPath = result.filePath;
|
||||
|
||||
// Track files saved via dialog (always allowed)
|
||||
openedFiles.add(path.resolve(targetPath));
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(targetPath);
|
||||
await fsp.mkdir(dir, { recursive: true });
|
||||
|
||||
await fsp.writeFile(targetPath, content, "utf8");
|
||||
return { path: targetPath };
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// export async function handleCreateFile(
|
||||
// mainWindow: BrowserWindow,
|
||||
// fileName: string,
|
||||
// content = "",
|
||||
// directory?: string,
|
||||
// ): Promise<{ path: string } | null> {
|
||||
// let targetDir: string;
|
||||
|
||||
// if (directory) {
|
||||
// // If directory is provided, check if it's secure
|
||||
// if (!isPathSecure(directory)) {
|
||||
// throw new Error(
|
||||
// "Access denied: Directory is outside the workspace",
|
||||
// );
|
||||
// }
|
||||
// targetDir = directory;
|
||||
// } else if (currentWorkspaceRoot) {
|
||||
// // Use current workspace root
|
||||
// targetDir = currentWorkspaceRoot;
|
||||
// } else {
|
||||
// // Show directory selection dialog
|
||||
// const result = await dialog.showOpenDialog(mainWindow, {
|
||||
// properties: ["openDirectory"],
|
||||
// title: "Select directory to create file in",
|
||||
// });
|
||||
|
||||
// if (result.canceled || result.filePaths.length === 0) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// targetDir = result.filePaths[0];
|
||||
// }
|
||||
|
||||
// const targetPath = path.join(targetDir, fileName);
|
||||
|
||||
// // Double-check the final path is secure
|
||||
// if (!isPathSecure(targetPath)) {
|
||||
// throw new Error("Access denied: Target path is outside the workspace");
|
||||
// }
|
||||
|
||||
// try {
|
||||
// // Check if file already exists
|
||||
// const exists = await fsp
|
||||
// .access(targetPath)
|
||||
// .then(() => true)
|
||||
// .catch(() => false);
|
||||
// if (exists) {
|
||||
// // Ask user if they want to overwrite
|
||||
// const choice = await dialog.showMessageBox(mainWindow, {
|
||||
// type: "question",
|
||||
// buttons: ["Cancel", "Overwrite"],
|
||||
// defaultId: 0,
|
||||
// message: `File "${fileName}" already exists. Do you want to overwrite it?`,
|
||||
// });
|
||||
|
||||
// if (choice.response === 0) {
|
||||
// return null; // User cancelled
|
||||
// }
|
||||
// }
|
||||
|
||||
// await fsp.writeFile(targetPath, content, "utf8");
|
||||
// return { path: targetPath };
|
||||
// } catch (error) {
|
||||
// throw new Error(`Failed to create file: ${error.message}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Utility function to get current workspace info
|
||||
export function getCurrentWorkspace(): { root: string | null } {
|
||||
return { root: currentWorkspaceRoot };
|
||||
}
|
||||
|
||||
// Utility function to get opened files (for debugging/info)
|
||||
export function getOpenedFiles(): string[] {
|
||||
return Array.from(openedFiles);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import { app, BrowserWindow, ipcMain } from "electron";
|
||||
import { handleOpenFolder } from "./fileOperations";
|
||||
import {
|
||||
handleOpenFolder,
|
||||
handleReadFile,
|
||||
handleSaveFile,
|
||||
// handleCreateFile,
|
||||
getCurrentWorkspace,
|
||||
getOpenedFiles,
|
||||
} from "./fileOperations";
|
||||
import path from "node:path";
|
||||
import started from "electron-squirrel-startup";
|
||||
|
||||
|
|
@ -44,6 +51,34 @@ app.whenReady().then(() => {
|
|||
const senderWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
return await handleOpenFolder(senderWindow);
|
||||
});
|
||||
|
||||
// File operation handlers
|
||||
ipcMain.handle("file:read", async (event, filePath?: string) => {
|
||||
const senderWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
return await handleReadFile(senderWindow, filePath);
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
"file:save",
|
||||
async (event, content: string, filePath?: string) => {
|
||||
const senderWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
return await handleSaveFile(senderWindow, content, filePath);
|
||||
},
|
||||
);
|
||||
|
||||
// ipcMain.handle("file:create", async (event, fileName: string, content = '', directory?: string) => {
|
||||
// const senderWindow = BrowserWindow.fromWebContents(event.sender);
|
||||
// return await handleCreateFile(senderWindow, fileName, content, directory);
|
||||
// });
|
||||
|
||||
ipcMain.handle("workspace:getCurrentInfo", () => {
|
||||
return getCurrentWorkspace();
|
||||
});
|
||||
|
||||
ipcMain.handle("workspace:getOpenedFiles", () => {
|
||||
return getOpenedFiles();
|
||||
});
|
||||
|
||||
createWindow();
|
||||
if (process.platform === "darwin") {
|
||||
app.on("activate", function () {
|
||||
|
|
|
|||
|
|
@ -7,4 +7,32 @@ import type { FolderTree } from "./types/global";
|
|||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
openFolder: () =>
|
||||
ipcRenderer.invoke("dialog:openFolder") as Promise<FolderTree | null>,
|
||||
|
||||
// File operations
|
||||
readFile: (filePath?: string) =>
|
||||
ipcRenderer.invoke("file:read", filePath) as Promise<{
|
||||
content: string;
|
||||
path: string;
|
||||
} | null>,
|
||||
|
||||
saveFile: (content: string, filePath?: string) =>
|
||||
ipcRenderer.invoke("file:save", content, filePath) as Promise<{
|
||||
path: string;
|
||||
} | null>,
|
||||
|
||||
// createFile: (fileName: string, content = "", directory?: string) =>
|
||||
// ipcRenderer.invoke(
|
||||
// "file:create",
|
||||
// fileName,
|
||||
// content,
|
||||
// directory,
|
||||
// ) as Promise<{ path: string } | null>,
|
||||
|
||||
getCurrentWorkspace: () =>
|
||||
ipcRenderer.invoke("workspace:getCurrentInfo") as Promise<{
|
||||
root: string | null;
|
||||
}>,
|
||||
|
||||
getOpenedFiles: () =>
|
||||
ipcRenderer.invoke("workspace:getOpenedFiles") as Promise<string[]>,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,20 @@ declare global {
|
|||
interface Window {
|
||||
electronAPI: {
|
||||
openFolder: () => Promise<FolderTree | null>;
|
||||
// Add other methods as needed
|
||||
|
||||
// File operations
|
||||
readFile: (
|
||||
filePath?: string,
|
||||
) => Promise<{ content: string; path: string } | null>;
|
||||
saveFile: (
|
||||
content: string,
|
||||
filePath?: string,
|
||||
) => Promise<{ path: string } | null>;
|
||||
// createFile: (fileName: string, content?: string, directory?: string) => Promise<{ path: string } | null>;
|
||||
|
||||
// Workspace info
|
||||
getCurrentWorkspace: () => Promise<{ root: string | null }>;
|
||||
getOpenedFiles: () => Promise<string[]>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue