九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、少し前に公開した記事 Electron 画像をドラッグ&ドロップするだけでリサイズするアプリをつくる を書いていて感じていたことがあります。
それは、「Electronを使えば日々の業務を効率化するデスクトップアプリを簡単につくれる!」というものでした。
ここで重要なのが、「簡単に」という部分です。
もちろんデスクトップアプリはプログラミング言語の王者C言語やJavaを使えばネイティブで高速なアプリをつくることはできますが、さすがにElectron
ほど敷居が低いものではありません。
業務改善アプリを作ろうとして時間がなくなり、他の作業に影響がでてしまうようなことはしたくはありませんよね。
そこで、今回のテーマ「連番リネーム」するアプリをElectron
でつくってみることにしました。
ぜひElectron
の学習に役立ててくださいね。
※ 開発環境: Electron 2.0
目次
開発するアプリの内容
今回は開発するElectronアプリの詳細は次のとおりです。
- フォルダを選択したら、その中にあるファイルすべてをリネームする
- ただし、拡張子が複数ある場合は、その拡張子ごとに連番を振っていく(※1)
- 連番の数字を何桁にするのかを選べるようにする
- すでに同名のファイルがあっても上書きしない(※2)
- 並べ替え順は、名前(昇順/降順)と変更日時(昇順/降順)
※1: 次のように連番を振っていきます。
- 001.png, 002.png, 003.png ・・・
- 001.jpg, 002.jpg, 003.jpg ・・・
※2: つまり、ファイル数が減ることはありません。
では、実際に開発をしていきましょう!
Electronをインストールする
インストールは、初心者向き!electron で簡単なメモ帳をつくってみようの “electronのインストール” に詳しく書いてあるので、そちらを見てください。
フォルダ名はrename_files
です。
アプリの設定をする
main.js
を開いてアプリの設定をします。
ウィンドウは画面トップ中央で開くようにします。(詳しい説明を見たい人は、Electron 画像をドラッグ&ドロップするだけでリサイズするアプリをつくる の “アプリの設定をする” をご覧ください。)
const electron = require('electron') const app = electron.app const BrowserWindow = electron.BrowserWindow let mainWindow function createWindow () { const screen = electron.screen.getPrimaryDisplay() const width = 350 const height = 300 const x = parseInt(screen.workAreaSize.width*0.5 - width*0.5) mainWindow = new BrowserWindow({ width: width, height: height, x: x, y: 0, autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true } }) // 残りは省略
【追記:2020.3.19】Electron v5
以降のためにwebPreferences
を追加しました。
レイアウトをつくる
では、まずはアプリのレイアウトを作っていきます。
今回もレイアウトにはbootstrap 4
を使うので以下のコマンドでインストールします。
npm i bootstrap --save
そして、bootstrap
を使ってHTMLでレイアウトを作ります。
<link rel="stylesheet" href="./node_modules/bootstrap/dist/css/bootstrap.min.css">
<div id="app" class="container"> <div style="padding:15px 0;"> <label> <span class="btn btn-light"> フォルダを選択 <input type="file" style="display:none" webkitdirectory> </span> </label> </div> <table class="table"> <tr> <td>連番数字:</td> <td> <select class="form-control"> <option>4桁</option> </select> </td> </tr> <tr> <td>並べ替え順:</td> <td> <select class="form-control"> <option>名前(昇順)</option> </select> </td> </tr> </table> <div class="text-muted">(ここにメッセージ表示)</div> <button style="position:absolute;right:15px;bottom:15px;" class="btn btn-danger">開始する</button> </div>
これを実行するとこうなります。
JavaScript部分をつくる
Vueの基本形をつくる
今回もJavaScriptフレームワークにVueを使いますので次のコマンドでインストールしましょう。
npm i vue --save
そして、index.html
内にidをつけVueを読み込みます。
<div id="app" class="container">
<script src="./node_modules/vue/dist/vue.min.js"></script> <script> new Vue({ el: '#app' }) </script>
フォルダ選択部分
ではアプリの一番上にある “フォルダを選択” ボタンがクリックされ、リネームするフォルダが選択されたときのコードです。
まず、data
に選択されたフォルダのパスを格納する変数をつくります。
data: { folderPath: '' },
そして、<input>
タグにchange
イベントをつけ、onFolderChange()
が実行されるようにします。
<input type="file" style="display:none" @change="onFolderChanged($event)" webkitdirectory>
最後にonFolderChanged()
の本体です。
methods: { onFolderChanged(e) { const files = e.target.files if(files.length > 0) { const folder = files[0] this.folderPath = folder.path } else { this.folderPath = '' } } }
この中では、フォルダが選択させれていれば、folderPath
にそのパスを、そうでなければ空白を格納しています。
連番数字を選択する部分
続いて、連番数字を選択する部分です。
まずはdata
に選択されている桁を管理するdigit
(初期値4
)とその選択肢digitOptions
を追加します。
data: { folderPath: '', digit: 4, digitOptions: [3, 4, 5, 6] },
次にdigit
を<select>
タグへバインディングします。
<select class="form-control" v-model="digit">
そして、最後にv-for
を使って選択肢を作ります。
<option v-for="digitOption in digitOptions" :value="digitOption">{{ digitOption }}桁</option>
これで以下のような選択肢ができます。
並べ替え順を選択する部分
こちらもまず、選択された並べ替えタイプを管理するsort
とその選択肢のsortOptions
を変数登録します。
data: { // 省略 sort: 0, sortOptions: [ { value: 'name asc', text: '名前(昇順)' }, { value: 'name desc', text: '名前(降順)' }, { value: 'modified asc', text: '変更日時(昇順)' }, { value: 'modified desc', text: '変更日時(昇順)' }, ] },
そして、<select>
タグにsort
をバインディングさせ、
<select class="form-control" v-model="sort">
v-for
を使って選択肢をつくります。
<option v-for="(sortOption,index) in sortOptions" :value="index" v-text="sortOption.text"></option>
これを実行するとこうなります。
メッセージ部分をつくる
続いてリネームの完了やエラーメッセージを表示する部分をつくっていきましょう。
といってもdata
にmessage
を登録し、それをバインディングするだけです。
data: { // 省略 message: '' },
<div class="text-muted" v-text="message"></div>
リネームする部分をつくる
では本題のファイル名をリネームする部分です。
クリックイベントをつける
まずはボタンがクリックされたときのクリックイベントでrename()
が実行されるようにします。
<button style="position:absolute;right:15px;bottom:15px;" class="btn btn-danger" @click="rename()">開始する</button>
そして、rename()
内は次のようにします。
rename() { this.message = '' if(this.folderPath == '') { this.message = '[エラー] フォルダを選択してください。' } else if(confirm('連番リネームを実行しますか?')) { // ここでファイル名を変更 } },
ちなみに、フォルダが選択されてない場合はエラーが出るようになっています。
指定されたフォルダから全ファイル名を取得する
では、指定されたフォルダからすべてのファイル名を取得し、さらに選択された並び替え順でそのファイル名を並べかえたリストを取得してみましょう。
まずはElectron(NodeJS)でファイルとパスを操作するパッケージfs
、path
を読み込みます。
const fs = require('fs') const path = require('path')
そしてファイル名を取得するメソッドは、getFileNames()
です。
rename() { this.message = '' if(this.folderPath == '') { this.message = '[エラー] フォルダを選択してください。' } else if(confirm('連番リネームを実行しますか?')) { const fileNames = this.getFileNames() } },
そして次がその中身ですが、少し長いコードになったため、getFileNames()
本体と、並べ替えをするsortFiles()
に分割しています。それぞれ見ていきましょう。
(getFileNames)
getFileNames() { const dir = this.folderPath const files = fs.readdirSync(dir) .filter((name) => { return (fs.statSync(dir +'/'+ name).isFile()) }) .map((name) => { return { name: name, time: fs.statSync(dir +'/'+ name).mtime.getTime() } }) const sortedFiles = this.sortFiles(files) let fileNames = [] sortedFiles.map((file) => { fileNames.push(file.name) }) return fileNames },
まず、fs.readdirSync()
を使ってファイル名を取得します。ただし、このファイル名リストの中にはフォルダも含まれているのでfilter
を使ってフォルダ形式のものを除外。そして、並べ替えのためにname
(ファイル名)とtime
(変更日時)を持ったデータへ変換します。
そして、sortFiles()
で並べ替えをして(次の項目で詳しく説明します)、最後にそこからファイル名だけを抜き出してreturn
しています。
(sortFiles)
続いて並べ替え部分です。
sortFiles(files) { const sort = this.sortOptions[this.sort].value let sorted if(sort == 'name asc') { sorted = files.sort((a, b) => { return (b.name < a.name) ? 1 : -1 }) } else if(sort == 'name desc') { sorted = files.sort((a, b) => { return (b.name < a.name) ? -1 : 1 }) } else if(sort == 'modified asc') { sorted = files.sort((a, b) => { return a.time - b.time }) } else if(sort == 'modified desc') { sorted = files.sort((a, b) => { return b.time - a.time }) } return sorted }
まず始めに並べ替えの方式(例えば、name asc
など)を取得し各並べ替え計算を行っています。
上から、
- 名前(昇順)
- 名前(降順)
- 変更日時(昇順)
- 変更日時(降順)
です。
リネームする
では、実際にファイルをリネームする部分です。
// 省略 const fileNames = this.getFileNames() const dir = this.folderPath let numbers = {} for(let fileName of fileNames) { let originalPath = dir +'/'+ fileName let tempPath = dir +'/temp-'+ fileName fs.renameSync(originalPath, tempPath) } for(let i in fileNames) { let fileName = fileNames[i] let originalPath = dir +'/temp-'+ fileName let extension = path.extname(fileName) if(numbers[extension] == undefined) { numbers[extension] = 0 } numbers[extension]++ let fileNumber = numbers[extension] let newFileName = this.numberFileName(fileNumber, extension) let newPath = dir +'/'+ newFileName fs.renameSync(originalPath, newPath) } this.message = 'リネームが完了しました!' // 省略
まず、最初のfor
ループで一旦全てのファイル名にtemp-
をつけてリネームします。これは、新しい名前と同名のファイルがあると上書きされてしまうため、これを回避するための処理です。(本当はforループは1回でいけますが、どうしても説明が難しくなってしまうので、よりシンプルな方法を選びました。もし処理スピードをあげたい場合は、ご自身でリファクタリングしてください。m_ _m)
そして、新しい番号をつけたファイル名にリネームします。
ここで、重要なのが、numbers
というオブジェクトです。
これは、拡張子ごとに番号を管理していて、同じ拡張子のファイルが見つかると数字を1つ足してそこから新しいファイル名をつくるようにしています。
{ '.jpg': 1, '.png': 5, '.pdf': 3 }
教材ソースコードをダウンロードする
今回実際に開発したソースコード一式を以下からダウンロードすることができます。
※ ただし、あくまでプログラムの紹介のために作成したものですので、実際問題あまりテストしていません。そのため重要なファイルでは試さないようお願いいたします。
Electron でファイル名を連番リネーム