-
파일시스템 사용하기Archive/메모노트 데스크탑 앱 2022. 7. 2. 17:31
fs모듈 (Node JS)
fs모듈은 Node.js에 기본으로 내장된 모듈로, 파일 입출력 처리를 위해 사용하는 모듈이다.
이번에 만드는 메모노트 앱에서 파일을 로컬로 저장하거나 불러오기 위해서는 해당 부분을 반드시 먼저 짚고 넘어가야 했다.
fs 모듈은 비동기 메서드와 동기 메서드를 둘 다 제공한다. 따라서 구상한 프로젝트에 맞는 것으로 골라서 사용하면 된다.
비동기 메서드의 경우, 마지막 인자로 콜백 함수를 받아서 실행되며 return 값은 없다.
반면 동기 메서드의 경우, 결괏값을 반환하며 예외를 일으킬 수 있다. (try-catch문 사용)
디렉터리 생성
fs모듈을 이용해 디렉터리를 생성하기 위해서는 mkdir()이라는 메서드를 사용한다.
const fs = require("fs"); fs.mkdir("fsDir", (err) => console.error(err));
메서드의 첫 번째 인자로 디렉터리의 경로를 적고, 두 번째 인자로 콜백 함수를 적는다.
해당 메서드에서 콜백 함수는 에러를 처리하는 데에 사용된다.
동기 버전의 경우 mkdirSync() 메서드를 사용한다.
const fs = require("fs"); try { fs.mkdirSync("fsDir"); } catch (err) { console.error(err); }
디렉터리 삭제
디렉터리를 삭제하기 위해서는 rmdir()이라는 메서드를 사용한다.
const fs = require("fs"); fs.rmdir("fsDir", (err) => console.error(err));
메서드의 첫 번째 인자로 삭제할 디렉터리의 경로를 적고, 두 번째 인자로 콜백 함수를 적는다.
해당 메서드에서 콜백 함수는 에러를 처리하는 데에 사용된다.
동기 버전의 경우 rmdirSync() 메서드를 사용한다.
const fs = require("fs"); try { fs.rmdirSync("fsDir"); } catch (err) { console.error(err); }
디렉터리 또는 파일 확인
지정된 경로에 해당하는 디렉터리나 파일이 이미 존재하는지 확인하기 위해 exists()라는 메서드를 사용한다.
const fs = require("fs"); fs.exists("fsDir", (exists) => console.log(exists));
첫 번째 인자로 확인할 디렉터리나 파일의 경로를 적고, 두 번째 인자로 콜백 함수를 적는다.
해당 메서드에서 콜백 함수는 파일이 존재하는지 여부를 boolean값으로 나타내 준다.
현재 해당 메서드는 deprecated상태인 것 같다. 이유는 잘 모르겠다.
따라서 해당 메서드의 동기 버전인 existsSync()를 사용할 것을 추천한다.
const fs = require("fs"); console.log(fs.existsSync("fsDir"));
파일 생성 및 데이터 쓰기
fs모듈의 writeFile()이라는 메서드를 사용하면 비동기로 파일을 생성하여 데이터를 쓸 수 있다.
const fs = require("fs"); const filePath = "electron/test.txt"; const contents = "Hello Electron"; fs.writeFile(filePath, contents, (err) => console.error(err));
첫 번째 인자로 생성할 파일의 경로를 파일명과 함께 적고, 두 번째 인자로 파일에 쓸 데이터를 적는다.
마지막으로 콜백 함수를 이용해 에러를 파악할 수 있다.
동기 버전의 경우 writeFileSync() 메서드를 이용할 수 있다.
const fs = require("fs"); const filePath = "electron/test.txt"; const contents = "Hello Electron"; try { fs.writeFileSync(filePath, contents); } catch (err) { console.error(err); }
파일에 데이터 추가
writeFile() 메서드를 사용하면, 기존에 파일에 있던 데이터를 모두 덮어쓴다는 점을 주의해야 한다.
따라서 기존의 내용에 새로운 데이터를 추가하고 싶다면, appendFile()이라는 메서드를 사용해야 한다.
const fs = require("fs"); const filePath = "electron/test.txt"; const contents = "Hello Electron"; fs.appendFile(filePath, contents, (err) => console.error(err));
메서드의 사용법은 writeFile() 메서드와 동일하다.
동기 버전의 경우 appendFileSync() 메서드를 사용하면 된다.
const fs = require("fs"); const filePath = "electron/test.txt"; const contents = "Hello Electron"; try { fs.appendFileSync(filePath, contents) } catch (err) { console.error(err) }
파일 데이터 읽기
readFile() 메서드를 사용하면, 기존에 존재하는 파일에 있는 데이터를 읽을 수 있다.
const fs = require("fs"); const filePath = "electron/test.txt"; fs.readFile(filePath, (err, data) => { if (err) console.error(err); console.log(data.toString()); });
첫 번째 인자로는 데이터를 읽을 파일의 경로를 적는다. 두 번째 인자로는 옵션을 사용할 수 있는데, 위의 코드에서는 사용하지 않고 바로 콜백 함수를 사용하였다. 만약 옵션을 사용하려면, 콜백 함수를 사용하기 전에 옵션을 적을 수 있다. 예를 들어 "utf8"과 같은 옵션을 사용하면, Buffer Encoding을 설정할 수 있다.
마지막 콜백 함수에서는 두 개의 인자를 가지는데, err는 에러를 의미하고, data는 실제 파일로부터 읽은 데이터를 의미한다.
해당 메서드의 동기 버전은 readFileSync()이다.
const fs = require("fs"); const filePath = "electron/test.txt"; try { console.log(fs.readFileSync(filePath, "utf8")); } catch (err) { console.error(err); }
위의 코드에서는 "utf8"옵션을 사용해보았다. 옵션이 필요하지 않다면, filePath만 명시해도 된다.
Electron에서 파일 시스템 사용하기
이제 위에서 공부한 내용을 바탕으로 fs모듈을 이용하여 실제 파일 입출력 시스템을 구현해보려 한다.
먼저 React를 이용해 파일 입출력에 필요한 UI를 미리 생성한다. 이는 해당 포스트에서 다루지 않는다.
이후 React에서 보여주는 화면에서 버튼을 눌렸을 때 Electron이 반응할 수 있도록, IPC 통신 쪽을 미리 만들어 둔다.
const { app, BrowserWindow, ipcMain, Notification, dialog, } = require("electron"); const fs = require("fs"); const path = require("path"); const rootPath = `${app.getPath("documents")}/electronNote`; app .whenReady() .then(createWindow) .then(async () => { ipcMain.on("fsWrite", (event, args) => { !fs.existsSync(rootPath) && fs.mkdirSync(rootPath); const tempPath = path.join(rootPath, `${args.fileName}.txt`); console.log(args); fs.writeFile(tempPath, args.contents, (err) => { if (err) console.error(err); }); }); ipcMain.on("fsRead", (event, args) => { dialog.showOpenDialog({ defaultPath: rootPath }).then((res) => { console.log(res.filePaths[0]); fs.readFile(res.filePaths[0], (err, data) => { if (err) console.error(err); event.reply("fsRead", data.toString()); }); }); }); });
여기서 "fsWirte" 채널명의 경우 파일 쓰기를 처리하고, "fsRead" 채널명의 경우 파일 읽기를 처리하도록 했다.
app.getPath()
가장 먼저 rootPath라는 상수를 보면 app.getPath()라는 Electron app의 메서드를 사용한 것이 보인다.
해당 상수는 파일을 저장하거나 읽어오기 위해 가장 기본으로 사용할 경로를 의미하도록 정했는데, 이때 app.getPath()를 이용해 운영체제별로 데이터 경로를 쉽게 설정해 줄 수 있다.
app.getPath()에는 첫 번째 인자에 특정 문자열을 넣어 운영체제에 따라 다른 경로를 제공한다.
getPath(name: 'home' | 'appData' | 'userData' | 'cache' | 'temp' | 'exe' | 'module' | 'desktop' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos' | 'recent' | 'logs' | 'crashDumps'): string;
실제 getPath가 어떻게 구성되어 있는지를 살펴보면, 다음과 같은 문자열을 사용할 수 있음을 확인할 수 있다.
이때 그 의미는 대충 짐작할 수 있을 거라 생각한다.
이번 프로젝트에서는 "documents"를 이용해, 문서 폴더를 기본으로 하여 그곳에 electronNote라는 디렉터리를 만들고, 해당 폴더에 관련 파일들을 저장하도록 설계하였다.
fsWrite
fsWrite에서는 먼저 rootPath로 설정된 경로가 존재하는지를 확인한다. 파일을 쓰기 전에 해당 디렉터리가 먼저 존재하는지를 확인하고, 만약 없다면 fs모듈의 mkdirSync() 메서드를 이용해 관련 디렉터리를 미리 생성하게 한다.
이후 fs.writeFile을 통해 ipcMain의 args 인자로 넘어온 데이터를 가지고 파일을 생성하도록 했다.
참고로 args는 fileName과 contents로 이루어진 객체의 형태를 사용하기로 정했다.
fsRead & dialog.showOpenDialog()
fsRead에서는 dialog.showOpenDialog()라는 메서드를 사용하였다. 이는 Electron에서 제공하는 API로, Native System Dialogs를 표시할 때 사용할 수 있다. 여기서는 파일을 선택하기 위한 창을 여는데 사용했다.
여기서 해당 메서드는 Promise이므로, then(res)를 이용해 그 결과를 비동기로 처리한다.
이때 Return값은 canceled, filePaths, bookmarks가 있는데, 위의 코드에서는 선택한 파일 경로를 사용하기 위해 filePaths만을 사용했다.
filePaths는 string[] 형태인데, 선택한 하나의 파일만 사용할 것이므로, res.filePaths[0]의 형태로 사용하였다.
후에 fs.readFile() 메서드에서 이 res.filePaths[0]를 이용해 읽을 파일의 경로를 사용한다.
마지막으로 readFile로 읽어 들인 data값은 event.reply를 통해 다시 React로 전송시켜, 화면에 글자 형태로 표시할 수 있도록 했다.
React
useEffect(() => { ipcRenderer.on("fsRead", (event: any, args: string) => { console.logs(args); }); }, []);
React에서는 useEffect 또는 ComponentDidMount에 ipcRenderer.on("fsRead")를 사용하여, Electron이 readFile을 이용해 읽은 데이터를 다시 reply 할 때 해당 데이터를 받을 준비를 한다.
참고
https://www.daleseo.com/js-node-fs/
https://kay0426.tistory.com/19