[Electron] 4.이미지 바이너리화 - 레시피 일렉트론 앱

Git: https://github.com/bsorryman/recipe.electron

 

GitHub - bsorryman/recipe.electron: 레시피 정보를 검색 및 다운로드 할 수 있는 일렉트론 앱.

레시피 정보를 검색 및 다운로드 할 수 있는 일렉트론 앱. Contribute to bsorryman/recipe.electron development by creating an account on GitHub.

github.com

개요

각 레시피에 해당하는 이미지 파일을 image_file 이라는 새로운 BLOB 필드에 삽입할 예정이다. BLOB 필드에 이미지를 삽입하려면 파일을 바이너리화 해야한다.

즉, 모든 이미지 파일을 한번 씩 조회하면서 그에 맞는 레코드의 image_file 필드에 파일을 바이너리화 하여 INSERT 해야한다. 이 로직을 Electron 프로젝트 내에서 구현할 것이다.

  1. Sqlite DB 연결
  2. id 1번 레코드의 image_name 필드 조회
  3. 조회한 image_name의 이미지 파일을 경로에서 찾아 바이너리화
  4. 바이너리화한 파일을 해당 레코드의 image_file BLOB 필드에 삽입
  5. 모든 레코드에 이미지를 삽입할 때까지 2-4번을 반복.

DB 경로 확인 및 경로 저장

먼저 Electron 실행 후 DB 접속 전에 올바른 위치에 DB 파일이 있는 지 확인토록 한다. 없으면 앱을 종료시킨다. 또한 올바른 위치에 있다면 후에 DB 접속 및 다른 곳에도 쉽게 경로를 쓸 수 있도록, electron-settings 라이브러리를 사용하여 값을 저장한다.

npm install electron-settings
npm install better-sqlite3
//main.js
const { app, BrowserWindow, dialog} = require('electron');
const path = require('path');
const fs = require('fs');
import RcpSetting from './setting';

.
.
.
mainWindow.once('ready-to-show', () => {
    console.log('ready-to-show');
    mainWindow.show();

    let dbPath;
    if (process.env.NODE_ENV == 'production') {
      dbPath = path.resolve('D://db/recipe db/recipe.db');
    } else {
      dbPath = path.resolve('D://db/recipe db/recipe.db');
    }
    RcpSetting.setDatabasePath(dbPath);

    dbPath = RcpSetting.getDatabasePath();

    let existDb = fs.existsSync(dbPath);

    if (!existDb) {
      let timer = setInterval(() => {
        dialog.showMessageBox(mainWindow, {type:'error',title:'Error', message:'DB file not found.'})
        .then(() => {
          mainWindow.close();
        }); 
        clearInterval(timer);
      }, 500);
    }

  });
.
.
.

DB 클래스

main.js 에서 DB Instance를 생성해 사용할 수 있도록, DB 관련 클래스를 만든다.

//rcp_sqlite_db.js
import DatabaseConstructor, {Database} from 'better-sqlite3';
import RcpSetting from './setting';

export default class RcpSqliteDB {
    db = null;
    dbPath = '';
    is_open = false;

    constructor() {
    }
.
.
.
    updateImageFileByImageName(imageBuffer, imageName) {
        if (this.db == null)
            return '';

        const stmt = this.db.prepare(
            `
            UPDATE tb_recipe SET image_file = ?
            WHERE image_name = ?
            `
        );

        stmt.run(imageBuffer, imageName);
    }
}

클래스 내에 DB 접속 및 쿼리문 실행을 위한 함수를 선언해 놓는다. 이후 DB Instance는 main.js (main process) 에서 하나만 생성해두고 open() 함수를 통해 앱 실행과 동시에 연결을 해둔다.

이미지 바이너리화 및 DB 삽입

//image_binary.js
const path = require('path');
const fs = require('fs');

export default class ImageBinary {
    db = null;

    constructor(db) {
        this.db = db;
    }

    InsertBinaryToSqliteDB() {
        const maxId = this.db.selectMaxId()[0].max_id;
        console.log('maxId: ' + maxId);

        let id = 0;
        while(id <= maxId) {
            let imageName = this.db.selectImageNameById(id)[0];
            
            if (imageName === undefined) {
                console.log('This is undefined.');
                id++;
                continue;
            }
            
            imageName = imageName.image_name;

            let imagePath = path.resolve('D://db/recipe db/Food Images/' + imageName + '.jpg');
            let existImage = fs.existsSync(imagePath);

            if (existImage) {
                let imageBuffer = fs.readFileSync(imagePath);

                this.db.updateImageFileByImageName(imageBuffer, imageName);
                console.log('update completed: ' + id);
                console.log(imageName + 'is completed: ' + imagePath);
            
            } else {
                console.log('There is not this image: ' + imageName);
            }
            id++;

        }
    }
    
}

 

//main.js
.
.
.
import RcpSqliteDB from "../rcp_sqlite_db";
import ImageBinary from "./binary/image_binary";
.
.
.
const createWindow = () => {
  .
  .
  .
  mainWindow.once('ready-to-show', () => {
    .
    .
    .

    //db open
    let db = new RcpSqliteDB();
    db.open();

    let imageBinary = new ImageBinary(db);
    imageBinary.InsertBinaryToSqliteDB();

  });
  .
  .
  .
 };

 

ImageBinary 라는 클래스를 만들고 해당 Instance를 main.js (main process)에서 생성한다. ImageBinary의 생성자는 DB Instance를 파라미터로 받는다. 그렇게 생성된 ImageBinary Instance는 내부 함수에서 쿼리문을 실행할 수 있다.

  • 가장 큰 id 값을 조회하고 그 수 만큼 반복시킨다.
  • 매 반복마다 해당 id로 image_name을 조회하여 이미지 파일 위치를 찾는다.
  • 찾은 이미지 파일을 fs.readFileSync(path) 를 통해 버퍼로 변환한다.
  • 변환된 binary 데이터는 DB의 BLOB 컬럼에 저장된다.

이제 이후 레시피 이미지를 페이지에서 쓸 일이 있으면, 이미지 파일 없이 DB에서 image_file 컬럼을 조회하고 인코딩하여 사용하면 된다.