Europa
Europa

Reputation: 1284

Write to file in "appData" in Electron. Where to add import { app } from "electron";?

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

Answers (3)

Shawn Zhu
Shawn Zhu

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

spring
spring

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

tpikachu
tpikachu

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

Related Questions