見出し画像

『有能執事くん』、デスクトップアプリに進化しました!

こんにちは!
ALH の開発エンジニア、REIYAです。

以前つくった『有能執事くん バッチ版』
noteでも紹介の記事を書き、ソースも公開していました。

今回はなんとそのデスクトップアプリ版をつくってみましたので、ご紹介します。

前回の記事はこちら↓↓

執事くん

以前のバッチ版を、デスクトップアプリ版にしたもの。
Windows用です。
Electronにて実装しました。頒布はここクリックでDL
zip展開後にREADME.mdがあるので、したがってインストールバッチを実行してください。
起動すると、ランダムなローディングアニメーションが流れたのち、以下のホーム画面が表示されます。

バッチ版との差異

バッチ版の弱点として、フォルダを自動で作成し、構成を縛る、というものがありました。
こちらのアプリでは設定機能にて、本アプリで使用するフォルダを指定することができます。

実装機能

・ファイル/リンク群を起動
・バックアップ
・バックアップ転送
・リンク群のフォルダをエクスプローラで開く

となります。ボタンホバーで説明文が表示されるのでチュートリアルは不要でしょう。

バッチ版と違い、追加バッチを呼び出す機能はありません。
そこまでこの機能が活躍することがないと判断し、今回のアプリでは淘汰されました。。。

また、バックアップ進捗バーや圧縮アニメなど、視覚的な機能の向上がメインとなっているうえ
大きなファイルを圧縮する際には、内臓した7zipを用いた圧縮を使用します。

ノンビルドでzipを配布しているのでソースは見放題です。ご自由にカスタムできます。
Electron以外の実装部分ソースの一部を以下で紹介したいと思います。

ソース

全量はこちら

バックアップ機能部分のソースはこんな感じです。

// ========================================
// BK機能用画面をロード
// ========================================
import Forge from '../../core/Forge.js'
import CMD from '../../core/CMD.js'
import Fsys from '../../core/Fsys.js'

// exeファイルのあるディレクトリを取得
const chdir = CMD.cmdSync('chdir').replace(/\r\n/g, '');

// config.json
const cfDict = JSON.parse(
  Fsys.read(`${chdir}\\src\\config.json`)
  .replace(/\r\n/g, ''));

// タイトルを設定
const setTitle = v => document.querySelector('#title').innerHTML = v;

// 進捗バー
let progress = document.querySelector('.progress-bar');

// 作業フォルダをハンドリング
function handl() {
  setTitle('ハンドリング開始');
  if (!Fsys.existance(cfDict['bkto'])
    || !Fsys.existance(cfDict['bkfrom'])) {
    Forge.load('contents', 'home', () => {
      document.querySelector('#console').innerHTML = 'BKフォルダが存在しません, 設定画面から設定してください。';
    });
    return false;
  }
  return true;
}

let bktoDirName = null;
let bkFileAllNo = null;

// 実行関数
function exeBk() {
  setTitle('処理開始');
  let now = new Date();
  let yyyy = now.getFullYear();
  let mm = now.getMonth() >= 9 ? now.getMonth() + 1 : `0${now.getMonth() + 1}`;
  let dd = now.getDate();
  // 新規ヘッダ or 今日と同日でない場合
  let dirname = `${cfDict['bkheader']}_${yyyy}_${mm}_${dd}_ver1`;
  let flist = Fsys.fList(cfDict['bkto']);
  let dirList = Fsys.dirList(cfDict['bkto']);
  if (flist || dirList) {
    // ファイル名も考慮
    // 同ヘッダファイルについて
    let sameHeadFile = flist
      ? flist.concat(dirList).filter(v => v)
        .filter(v => v.startsWith(cfDict['bkheader'])).sort()
      : dirList.concat(flist).filter(v => v)
        .filter(v => v.startsWith(cfDict['bkheader'])).sort();
    if (sameHeadFile.length != 0) {
      // 最新バージョンを取得
      let el = /(.+)_(.+)_(.+)_(.+)_ver(.+)/.exec(sameHeadFile.slice(-1)[0]);
      // 今日と同日なら, バージョンを+1
      if (el[2] == yyyy && el[3] == mm && el[4] == dd) {
        // ファイルの場合拡張子を削除
        let ver = el[5].split('.')[0];
        dirname = `${cfDict['bkheader']}_${yyyy}_${mm}_${dd}_ver${Number(ver) + 1}`;
      }
    }
  }
  setTitle('名前ok');
  bktoDirName = `${cfDict['bkto']}\\${dirname}`;
  new Promise(resolve => {
    // フォルダ作成
    setTitle('フォルダ作成を開始');
    let reMkdir = Fsys.mkdir(bktoDirName);
    setTitle('フォルダ作成完了' + reMkdir);
    resolve();
  })
  .then(() => {
    // 並列でコピー、進捗を監視して圧縮/転送
    setTitle('コピー開始');
    let resRobocp = Fsys.robocopy(cfDict['bkfrom'], bktoDirName);
    setTitle('robocopyコマンド 結果' + resRobocp);
    let asdasd = Fsys.count(cfDict['bkfrom']);
    setTitle('カウント 結果' + asdasd);
    calcProgress(Fsys.count(cfDict['bkfrom']));
  });
}

// 進捗監視, 圧縮, 転送
function calcProgress(bkAll) {
  if (!bkAll) {
    calcProgress(Fsys.count(cfDict['bkfrom']));
    return;
  }
  let transfared = 0;
  try {
    transfared = Number(Fsys.count(bktoDirName));
  } catch(e) {
    // 0件
  }
  setTitle(`コピー中(${transfared}/${bkAll})`);
  progress.style.width = `${Math.floor(transfared/bkAll*100)}%`;// 進捗バー更新
  if (transfared >= bkAll) {
    // コピー完了
    new Promise(resolve => {
      progress.style.width = `100%`;
      setTitle(`コピー完了`);
      setTimeout(() => resolve(), 300);
    })
    // 圧縮画面表示
    .then(() => new Promise(resolve => {
      setTitle('圧縮中...');
      document.querySelector('.progress-base').style.display = 'none';
      document.querySelector('.bg').classList.add('compress');
      setTimeout(() => resolve(), 2000);
    }))
    // 圧縮/コピーフォルダ削除
    .then(() => new Promise(resolve => {
      let resComp = Fsys.compress(bktoDirName);
      if (!resComp) {
        setTitle('圧縮失敗 7zipでリトライ');
        let resSeven = CMD.cmdSync(`${chdir}\\src\\lib\\7za.exe a ${bktoDirName}.zip ${bktoDirName}`);
        setTitle(resSeven);
      }
      let resDel = Fsys.delCmd(bktoDirName);
      if (!resDel) {
        setTitle('BK用にコピーしたファイルを削除できませんでした');
      }
      // いずれかに失敗した場合、ここで終了
      if (!resComp || !resDel) {
        return;
      }
      if (resComp && resDel) {
        setTitle('圧縮完了');
      }
      resolve();
    }))
    // 転送しない場合は終了
    .then(() => new Promise(resolve => {
      if (!Fsys.existance(cfDict['sendto'])) {
        setTimeout(() => {
          Forge.load('contents', 'home');
        }, 1000);
        return;
      }
      resolve();
    }))
    // 転送画面表示
    .then(() => new Promise(resolve => {
      setTitle('転送中...');
      document.querySelector('.bg').classList.remove('compress');
      document.querySelector('.bg').classList.add('transfar');
      setTimeout(() => resolve(), 1000);
    }))
    // 転送
    .then(() => new Promise(resolve => {
      Fsys.cpSync(`${bktoDirName}.zip`, cfDict['sendto']);
      setTitle('転送完了');
      setTimeout(() => {
        document.querySelector('.bg').classList.remove('transfar')
        Forge.load('contents', 'home');
        resolve();
      }, 1000);
    }));
    return;
  }
  // コピーが完了するまで再帰
  setTimeout(() => calcProgress(bkAll), 100);
}

// ハンドリングして実行
setTitle('処理を開始します');
if (handl()) {
  exeBk();
}



ALH Inc.について知る


↓ ↓ ↓ 採用サイトはこちら ↓ ↓ ↓


↓ ↓ ↓ ALHについてはこちら ↓ ↓ ↓


↓ ↓ ↓ もっとALHについて知りたい? ↓ ↓ ↓