Reputation: 1284
I am making my first Electron application. I am trying to save a text file to the appData folder (example C:\Users\user\AppData\Roaming
). I know I need to add import { app } from "electron";
somewhere but I am unsure where to place it.
In my index.js
javascript I am writing the database settings that the user submits in his form to a text file. This is where I need to have the appData
directory address.
// Write data to text file
var filepath = app.getPath("appData")
var filename = "database_quick_image_forensics.txt"
var inp_data = inp_host + "|" + inp_username + "|" + inp_password + "|" + inp_database_name + "|" + inp_table_prefix;
write_to_file(filepath, filename, inp_data);
My entire code is below:
./setup/index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Setup</title>
<!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
<!-- CSS -->
<link rel="stylesheet" type="text/css" href="../_webdesign/dark/dark.css" />
<!-- // CSS -->
<!-- jQuery -->
<script>window.$ = window.jQuery = require('../javascripts/jquery/jquery-3.4.1.js');</script>
<script src="../javascripts/jquery/jquery-3.4.1.js" charset="utf-8"></script>
<!-- //jQuery -->
<!-- jQuery -->
<script src="./index.js" charset="utf-8"></script>
<!-- //jQuery -->
</head>
<body>
<div id="main_single_column">
<h1>Setup</h1>
<!-- Feedback -->
<div id="feedback_div" class="success">
<p id="feedback_p">Success</p>
</div>
<!-- //Feedback -->
<!-- Database connection form -->
<p>Host:<br />
<input type="text" name="inp_host" id="inp_host" value="localhost" />
</p>
<p>Port:<br />
<input type="text" name="inpport" id="inp_port" value="" />
</p>
<p>Username:<br />
<input type="text" name="inp_username" id="inp_username" value="root" />
</p>
<p>Password:<br />
<input type="text" name="inp_password" id="inp_password" />
</p>
<p>Database name:<br />
<input type="text" name="inp_database_name" id="inp_database_name" value="quick" />
</p>
<p>Table prefix:<br />
<input type="text" name="inp_table_prefix" id="inp_table_prefix" value="cf_" />
</p>
<p>
<button id="form_connect_to_database_submit">Connect to database</button>
</p>
<!-- //Database connection form -->
</div>
</body>
</html>
./setup/index.js
:
const fs = require('fs');
// Action = On submit
$(document).ready(function(){
$("#form_connect_to_database_submit").click( function() {
// Feedback
$('#feedback_div').show();
$('#feedback_div').removeClass("success");
$('#feedback_div').addClass("info");
$('#feedback_p').text("Connecting!")
// get all the inputs
var inp_host = $("#inp_host"). val();
var inp_username = $("#inp_username"). val();
var inp_password = $("#inp_password"). val();
var inp_database_name = $("#inp_database_name"). val();
var inp_table_prefix = $("#inp_table_prefix"). val();
// Test connection
var connection_result = connect_to_database(inp_host, inp_username, inp_password, inp_database_name, inp_table_prefix);
if(connection_result != "connection_ok"){
// Connection Failed
$('#feedback_div').removeClass("info");
$('#feedback_div').addClass("error");
$('#feedback_p').text(connection_result)
}
else{
// Connection OK
$('#feedback_div').removeClass("info");
$('#feedback_div').addClass("success");
$('#feedback_p').text("Connected")
// Write data to text file
var filepath = app.getPath("appData")
var filename = "database_quick_image_forensics.txt"
var inp_data = inp_host + "|" + inp_username + "|" + inp_password + "|" + inp_database_name + "|" + inp_table_prefix;
$('#feedback_p').text("Connected " + filepath)
write_to_file(filepath, filename, inp_data);
// Feedback
$('#feedback_div').removeClass("info");
$('#feedback_div').addClass("success");
$('#feedback_p').text("Connected to")
}
});
$('#inp_host').focus();
});
// Function connect to database
function connect_to_database(inp_host, inp_username, inp_password, inp_database_name, inp_table_prefix){
var mysql = require('mysql');
// Add the credentials to access your database
var connection = mysql.createConnection({
host : inp_host,
user : inp_username,
password : null, // or the original password : 'apaswword'
database : inp_database_name
});
// connect to mysql
connection.connect(function(err) {
// in case of error
if(err){
console.log(err.code);
console.log(err.fatal);
return err.code;
}
});
// Perform a query
$query = 'SELECT * FROM `cf_admin_liquidbase` LIMIT 10';
connection.query($query, function(err, rows, fields) {
if(err){
console.log("An error ocurred performing the query.");
console.log(err);
return;
}
console.log("Query succesfully executed", rows);
});
return "connection_ok";
} // connect_to_database
// Function write setup
function write_to_file(filepath, filename, inp_data){
var fullpath = filepath + "\\" + filename;
fs.writeFile(fullpath, inp_data, (err) => {
// throws an error, you could also catch it here
if (err) throw err;
// success case, the file was saved
console.log('Lyric saved!');
});
} // write_to_file
./main.js
:
const { app, BrowserWindow } = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('index.html')
// Open the DevTools.
// win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
Upvotes: 2
Views: 9260
Reputation: 1
The Answer in 2024 As of this writing I'm on electron-28.2.5. Electron has made improvements in securing your app. It proposes a framework that you only expose Main process APIs you need to your Renderer process.
You define the handler for writing data to a file in the Main
// main/index.js
ipcMain.handle('file:save', async (event, args) => {
// As previous answer stated, Main has access to
// app.getPath('appData')
//
// use node:fs to write data to file
// data comes from args
}
You then expose the API in the Preload
// preload/index.js
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const { ipcRenderer } = electronAPI
const api = {
save: (data) => ipcRenderer.invoke('file:save', data)
}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}
Finally in your Frontend code (Renderer) you call the exposed/bridged api
// event handler where you have collected the data
function onDataUpdate(e) {
// collect data
...
window.api.save(data)
...
}
This is cleaner. Notice you never directly reference ipcRenderer in your Frontend components. For reference, this tutorial describes additional scenarios.
I also highly recommend using electron-vite for building Electron Apps. It comes with structured Main, Preload and Renderer stubs.
Upvotes: 0
Reputation: 18487
I know I need to add import { app } from "electron"; some where but I am unsure where to place it.
The app module is always (in my experience) imported in your main
process so you can control the applications lifecycle. However, if you want to use some of the app
module functionality in your renderer
process, you can import it there through the remote
module ( as shown in the accepted answer to this question: How to use electron's app.getPath() to store data? )
const remote = require('electron').remote;
const app = remote.app;
console.log(app.getPath('userData'));
The main
and renderer
processes are key concepts in Electron
so I'd suggest reading up on those. The gist is that you have one main
process – it has no visual representation and it is involved with the lifecycle of your app, creating and destroying renderer
processes (like BrowserWindows), communication between renderer processes, etc. – and you can have as many renderer
processes as you need.
So if you want to read and write files you can do it in the renderer
process as shown above – or you can do it in the main
process. In the latter case, if a renderer
process wants to save a file, it can message the main
process through IPC, sending the data to be saved.
Which way you do it is an architectural choice.
Upvotes: 3
Reputation: 4854
To get the app path at your main process. Then use this code at your main.js
switch(process.platform) {
case 'darwin': {
return path.join(process.env.HOME, 'Library', 'Application Support', ...);
}
case 'win32': {
return path.join(process.env.APPDATA, ...);
}
case 'linux': {
return path.join(process.env.HOME, ...);
}
}
And going to get the path from the renderer then use this code at your renderer
const remote = require('electron').remote;
const app = remote.app;
console.log(app.getPath('userData'));
But to use require at your renderer, please make sure nodeintegration is true.
If I were you, I was going to get the app path at main process and store the file at main process as well. Hence, importing many dependencies at renderer process is not a good choice. The renderer process mainly takes care of showing your app in the Chromium browser.
So to make this operation at main process. Use this
at your main.js
const { ipcMain } = require('electron')
const appPath = () => {
switch(process.platform) {
case 'darwin': {
return path.join(process.env.HOME, 'Library', 'Application Support');
}
case 'win32': {
return process.env.APPDATA;
}
case 'linux': {
return process.env.HOME;
}
}
}
const writeToFile = (fileName, inData) => {
const fullPath = path.join(appPath(), "\\", fileName);
fs.writeFile(fullPath, inData, (err) => {
// throws an error, you could also catch it here
if (err) throw err;
// success case, the file was saved
console.log('Lyric saved!');
});
} // write_to_file
ipcMain.on('WRITE_TEXT', async (event, arg) => {
writeToFile(arg.fileName, arg.inData)
});
At your renderer process add this code.
const {ipcRenderer} = require('electron')
ipcRenderer.sendSync('WRITE_TEXT',{fileName, inData})
As you can see, at renderer process, this is sending the inp_data
to your main process through 'WRITE_TEXT' IPC channel.
One more thing here, at your code. You are connecting your DB at your renderer and it's possible but this is not a right choice. Please think while you are having the several renderer. You should move this to main process too.
Upvotes: 0