FlashAir(W-04)の画像をGoogle Photos(Picasa Web)のアルバムにアップロードするスクリプトを改善 その2

以前、FlashAir(W-04)の画像をサーバー側のスクリプトを使って一時的にダウンロード、リサイズ、Google Photosにアップロードというのを自動化する方法を実施しました。

gentoolinux.hatenablog.com

だいぶ快適になりましたが、FlashAirの無線LANは電波の送受信が弱く、WebDAVとしても非力なため、転送が中断されることもしばしばありました。
転送が中断されると、写真の半分がグレーになった写真がアップされてしまいます。
また、中断中はサーバースクリプトが長くダンマリするため、カメラの電源OFF/ONしてFlashAirを復旧しても、Google Photosへのアップロードが始まらないということがあったわけです。

この辺を直してみました。
まず、FlashAirからの転送が中断するとダンマリになる原因は、wgetのデフォルトの振る舞いにありました。
wgetを使ってFlashAirからhttpで写真を転送します。

#!/bin/bash

#初期変数一覧

#Flashairのアドレス
FLASHAIR_IP="http://192.168.x.161"

#DCIMフォルダの名称
DCIM_FOLDER="CANON"

#DCIMフォルダのURLを生成
WGET_PRE_URL=${FLASHAIR_IP}"/DCIM/"

#(中略)
  echo "Flashair - Get file "${JPG_LIST[JPG_LOOP]}
     /usr/bin/wget -q -N --no-host-directories --no-directories ${FLASHAIR_IP}${JPG_LIST[JPG_LOOP]}

このとき、wgetタイムアウトは900秒、リトライ回数は20回です。
つまり、15分以上ダンマリを決め込むわけです。いくら気長な私でも待てません。
タイムアウトを30秒、リトライを5回にすると、約1分くらいで処理をあきらめます。

#!/bin/bash
echo "Flashair - Get file "${JPG_LIST[JPG_LOOP]}
/usr/bin/wget -q --tries=5 --timeout=30 --no-host-directories --no-directories ${FLASHAIR_IP}${JPG_LIST[JPG_LOOP]}

ついでに、既存ファイルの上書き禁止の-Nオプションを廃止しました。

さて、続いてこの中断したファイルを消すことと、正しく転送されたファイルリスト「flashair_done.txt」に追記する処理をスキップしなければなりません。

まず、wgetの終了ステイタスコードを理解します。

  • 0 No problems occurred.(正常終了)
  • 1 Generic error code.(一般的なエラー)
  • 2 Parse error—for instance, when parsing command-line options, the ‘.wgetrc’ or ‘.netrc’...(パースエラー、.wgetrcや.netrcの記述が間違っている)
  • 3 File I/O error.(ファイル入出力エラー)
  • 4 Network failure.(ネットワークの異常)
  • 5 SSL verification failure.(SSL認証エラー)
  • 6 Username/password authentication failure.(ユーザー/パスワード認証エラー)
  • 7 Protocol errors.(プロトコルエラー)
  • 8 Server issued an error response. (サーバーがエラーを応答)

FlashAirの異常終了はほぼほぼ4ですが、まあ正常終了の0以外はファイル削除することにします。
シェルスクリプトの中で直前のコマンドの終了ステイタスコードは「$?」に代入されます。しかも、次に何らかのコマンドが実行されると消えてしまいますので、何らかの変数に代入しておきます。
0なら正常終了なので「flashair_done.txt」に追記します。

#!/bin/bash
#wgetによるファイルの取得
   echo "Flashair - Get file "${JPG_LIST[JPG_LOOP]}
    /usr/bin/wget -q --tries=5 --timeout=30 --no-host-directories --no-directories ${FLASHAIR_IP}${JPG_LIST[JPG_LOOP]}

   #wgetのステイタスを確認
   WGET_STATUS=$?
   echo "Flashair - wget exit status is "$WGET_STATUS
      if [ $WGET_STATUS -eq 0 ]; then

     #wgetが正常終了ならflashair_done.txtにファイル名を追加
     echo "Flashair - Output to flashair_done.txt "${JPG_LIST[JPG_LOOP]}
     echo ${JPG_LIST[JPG_LOOP]} >> flashair_done.txt

つづいて、else以下にwgetの終了ステイタスコードが0以外、つまりwget異常終了時の振る舞いとして、ダウンロード途中で終わった現在のファイルを削除します。
が、${JPG_LIST[JPG_LOOP]}には、/DCIM/100CANON/1NA0001.JPGのように、ディレクトリ名まで代入されています。
しかし、中途半端にダウンロードしているファイルは、スクリプトが実行されているフォルダの直下に1NA0001.JPGとして保存されています。"/DCIM/100CANON/"は文字列置換で削除する必要があります。
sedを使って削除です。
ちょうど良いことに、${DCIM_LIST[DCIM_LOOP]}にこの"/DCIM/100CANON"(配列になっており、100だけじゃなく101だったり102もあったりします)が代入されていますので、これと/の1文字を削除です。

#!/bin/bash
DEL_JPEG=(`echo ${JPG_LIST[JPG_LOOP]} | sed -e "s@${DCIM_LIST[DCIM_LOOP]}@@" -e "s@/@@"`)
rm $DEL_JPEG
DEL_JPEG=

ずーっとsed -e "s/${DCIM_LIST[DCIM_LOOP]}//"と記載していて、sedシンタックスエラーで悩んでいたのですが、変数に"/"が入っているため、sedがそれを変数としてではなく、セパレーターとして認識してしまっていたため、セパレーターを@にすることで解決しました。

では、リトライを調整して完成した改善後のスクリプト全文はコチラです↓
(192.168.x.161のIPアドレスは、FlashAirのIPアドレスに置き換えてください。)

#!/bin/bash

#初期変数一覧

#Flashairのアドレス
FLASHAIR_IP="http://192.168.x.161"

#DCIMフォルダの名称
DCIM_FOLDER="EOS7D"


#現在のディレクトリに移動
cd `dirname $0`

#プロセスIDを保存
echo $$ > pid_flashair.txt

#DCIMフォルダのURLを生成
WGET_PRE_URL=${FLASHAIR_IP}"/DCIM/"

echo "Starting Get Photos from Flashair"

while :
do

#Flashairを死活監視
ALIVE=$(/usr/bin/wget -nv --spider --timeout 60 -t 1 ${FLASHAIR_IP} 2>&1 | grep -c '200 OK')

  #もし死活監視で生きていたら
  if [ $ALIVE -eq 1 ]; then

    #配列を初期化
    echo "Flashair - Format Arrys."
    DCIM_LIST=()
    JPG_LIST=()
    FLASHAIR_DONE=()
    WGET_STATUS=

    #DCIM配下のディレクトリを配列に代入
    echo "Flashair - get under DCIM directory list."
    DCIM_LIST=(`/usr/bin/wget ${WGET_PRE_URL} -q -O - | grep 'wlansd.push({"r_uri"' | sed -e 's/wlansd.push({"r_uri":"//' -e 's/", "fname":"/\//' -e 's/", "fsize".*//' | grep ${DCIM_FOLDER}`)

    #フォルダの数だけループ処理を実施
    echo "Flashair - Starting loop under DCIM directory."
    DCIM_LOOP=${#DCIM_LIST[@]}
    DCIM_LOOP=$((DCIM_LOOP - 1))
    while [ $DCIM_LOOP -ge 0 ];
    do

      #フォルダの中のファイルを配列に代入
      echo "Flashair - Get file list under "${DCIM_LIST[DCIM_LOOP]}
      JPG_LIST=(`/usr/bin/wget ${FLASHAIR_IP}${DCIM_LIST[${DCIM_LOOP}]} -q -O - | grep 'wlansd.push({"r_uri"' | sed -e 's/wlansd.push({"r_uri":"//' -e 's/", "fname":"/\//' -e 's/", "fsize".*//'`)

      #ファイルの数だけループ
      echo "Flashair - Starting loop under "${DCIM_LIST[DCIM_LOOP]}
      JPG_LOOP=${#JPG_LIST[@]}
      JPG_LOOP=$((JPG_LOOP - 1))
      while [ $JPG_LOOP -ge 0 ];
      do

      #アップ済みのファイルかどうかをチェック
      echo "Flashair - Check FLASHAIR_DONE "${JPG_LIST[JPG_LOOP]}
      if ! `cat flashair_done.txt | grep -q "${JPG_LIST[JPG_LOOP]}"` ; then

      #アップ済みになかった場合の処理
      #wgetによるファイルの取得
      echo "Flashair - Get file "${JPG_LIST[JPG_LOOP]}
      /usr/bin/wget -q --tries=5 --timeout=30 --no-host-directories --no-directories ${FLASHAIR_IP}${JPG_LIST[JPG_LOOP]}

      #wgetのステイタスを確認
      WGET_STATUS=$?
      echo "Flashair - wget exit status is "$WGET_STATUS
        if [ $WGET_STATUS -eq 0 ]; then

        #wgetが正常終了ならflashair_done.txtにファイル名を追加
        echo "Flashair - Output to flashair_done.txt "${JPG_LIST[JPG_LOOP]}
        echo ${JPG_LIST[JPG_LOOP]} >> flashair_done.txt

        #wgetが異常終了ならダウンロード途中のファイルを削除
        else
        DEL_JPEG=(`echo ${JPG_LIST[JPG_LOOP]} | sed -e "s@${DCIM_LIST[DCIM_LOOP]}@@" -e "s@/@@"`)
        echo "Flashair - Download Uncomplete. Delete File "$DEL_JPEG
        rm $DEL_JPEG
        DEL_JPEG=
        fi

      fi

      JPG_LOOP=$((JPG_LOOP - 1))
      done


    DCIM_LOOP=$((DCIM_LOOP - 1))

    #ダウンロードした全てのJPGを2048pxにリサイズ
    echo "Flashair - Resize Images"
    /usr/bin/mogrify -resize 2048x2048 -quality 100 *.JPG

    #ディレクトリ上にあるJPGファイルをpicasaweb APIでアップロード
    echo "Flashair - Uploading Images"
    ./upload_images_in_dir.sh

    #ディレクトリ上にあるJPGファイルを日付ディレクトリで整理
    echo "Flashair - Move Images to YMD directory "
    /usr/bin/exiftool -q '-Directory < CreateDate' -d %Y%m%d *.JPG

    done

  fi

  sleep 10
done

これで、スクリプトの再起動はしばらくやらなくてよさそう。