プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

スマホの写真バックアップに dropbox を使ってみようとしたが諦めました

スマホ( Nexus 6P, Android 8.1.0 )で撮った写真のバックアップ先として、スマホへのログイン時の Google アカウント( Google Drive)を利用していました。Google アカウントは無料枠として 15GB の Google Drive の容量があり、写真のバックアップもここに含まれています。

以前はバックアップとして『高画質』を選択していれば、容量にはカウントされずに無制限だったりしました(2021年5月末で終了)。ですが、個人的には写真の画質は落としたくないので、元の画質で保存していました。いまはもう画質に関係なく容量にカウントされますね。

そんなに大量に写真を撮ることもないのですが、何年も使っているとそれなりに容量を使ってしまい、最近、容量がいっぱいになりました、と警告が出るようになりました。PCのブラウザで Google Photo を開くと、こんな感じに警告が表示されます。

写真のバックアップが取れないだけならまだしも、同じアカウントの Gmail もメールの受信ができなくなる、とあります。

これは困りますね。スマホのアカウントのメールは、子どもの学校からの緊急通知などを受けるようにしているので、いざという時に使えないのでは話になりません。

素直に考えれば、 Google One にアップグレードして、上納金 お金を払って、快適に使うことになると思いますが、なんかいやです。 ということで、いろいろとあがいてみたのをメモにまとめておきます。

とはいえ、結局、最終的には Google One にしたんですけどね。

Dropbox

スマホの写真のバックアップの選択肢が、 Google One 以外にないものか調べてみると、いろいろとあるにはあるようです。でも、一番身近なものとしては Dropbox が対応しているので、それを使うのが手っ取り早そうです。

Dropbox は仕事の関係で使っているため、有料プラン(Plus)を契約しているのですが、 2TB の容量は大きすぎて余りまくってます。なので、写真の保存先としてはよさそうです。スマホ側でも普段から使ってますしね。

使ってみる

写真のバックアップは、 Dropbox では『カメラアップロード』という機能になるようです。

使い方は簡単で、スマホ上で Dropbox アプリを開いて『設定』->『カメラアップロード』を選択して、『写真をバックアップ』を押して、オンにすれば終わりです。

オンにすれば、自動的に写真のアップロードが始まります。これはよさそうですね。

他の設定として、WiFi接続時のみアップロードするようにするとか、バッテリー残量に応じてアップロードを中止する、というようなオプションがありますので、お好みに合わせて設定してください。

で、実際に使ってみると、個人的に気になる点がいくつか出てきたので、以下にまとめておきます。

問題点1

Dropbox のカメラアップロードを有効にして、どれぐらいデータがあるのか確認するため、PC のブラウザで Google Photo を見ると、 2014 年~今年( 2022 年)まで写真があります。結構な量ですね。ということで、しばらくほっけばいいやと思って仕事してました。

しばらくしてから、ふと、 Dropbox のフォルダを見ると、 2022 年の新しいほうからアップロードが始まっていたのですが、 2018 年ごろの写真をアップロードしたあたりで処理が終わっています。 ここで、昼ご飯だったのでいったんアップロード処理を中断して(というか、スマホをもって外に出て)、帰ってから原因を調べてみました。

Dropbox のトラブルシューティングなどを見ると、カメラアップロードが中断される場合

  • WiFi 接続がない
  • バッテリー残量が少ない
  • Dropbox の空き容量がない

あたりがよくある原因だそうですが、どれにも該当しません。また、スマホ上の Dropbox アプリを再起動してみても状況は変わりません。

ここで、 Dropbox に問い合わせしてみようと思って、写真フォルダのパスなどの情報を確認するために、スマホ本体を PC につなげて写真の保存先を確認すると

あれ?

日付を元にして、付けられているファイル名ですが、2018年より昔のものがありません。

なんだろうな?と思って、ブラウザで Google Photo にログインして、スマホ内にファイルがあるものの日付より古い日付の画像を選択して確認してみると、昔使っていた機種の名前が出てきます。

あ、そういうことか!

スマホ上の Google Photo のアプリだと、常にクラウドと連携してバックアップをしています。なので、スマホを交換した際も、同じ Google アカウントでセットアップさえしてやれば、昔の写真を見ることができます。が、この時、新しい端末内に写真はありません(せいぜいキャッシュが作られるぐらい?明示的にダウンロードしたら別でしょうけど)。 なので、 Dropbox からしたら、端末内にないファイルのことなんてしらないよ、ということなんでしょうね。

これについては、最悪、 Google Photo からデータをダウンロードして Dropbox 側にアップロードすればいいかな?と思われます。

問題点2

むしろこっちのほうが面倒そうなんですが、どうもカメラアップロードの場合、一部のファイルがアップロードされてないようです。 こちらはファイルそのものは、端末内にちゃんと存在しています。なんでアップロード対象にならないのかは、今一つわかりませんでした。

ただ、Windows PC にスマホをつないでカメラフォルダを見ていたら、サムネイルが生成されない jpg ファイルがいくつかあって、カメラアップロードされていないものと対応しているように見えました(数個のファイル見ただけなので、確定的なことは何もいえないですが)。

jpg の詳細なファイル形式の差異なのか、スマホ側で何かイレギュラーな情報を付加しているのか・・・、実際のところ、どういう理由かまでは分かりませんが、カメラアップロードの対象から漏れるファイルが存在し、バックアップとしては、中途半端な状態になっていることは確かなようです。

これへの対応はちょっと面倒ですね。どのファイルがまだアップロードされていないのか調べるなんてやりたくないです。 もしやるなら、

  • いったんカメラアップロードはオフにして
  • アップロード済みのデータをすべて消去して
  • Google Photo から全画像データをダウンロードして
  • Dropbox の『カメラアップロード』フォルダにデータをアップロードする

というような方法ですかね?で、その後追加で撮った写真は、カメラアップロードで Dropbox にバックアップする、という形が現実的なようです。

にしてもやりたくないな・・・

結論

ということで、今回の結論としては、使ってるスマホは Android 端末というのもあり、素直に Google One を使うことにしました。

とはいえ、せっかく調べたので、 Dropbox を写真のバックアップ先に使う場合のケースを考えてみると、

  • Dropbox の容量が余っている場合
  • または Google One の費用がかさむようになった場合
  • 1台目のスマホを使いだして以降同じ機種をずっと使ってるような場合
  • 写真の数が少なくて、バックアップが正しい状態であることを簡単に確認できる場合

などになりそうです。私の場合だと、当面は Google One を使うにしても、理由の2番目の Google One の費用が気になり、先々は Dropbox に切り替えることになるかもしれないですね。

また、もし、Dropbox を写真のバックアップ先に使う場合は、次の点を事前に考えておくのがいいかと思います。

  • スマホを替えたときは、(そのままでは)昔の写真を Google photo (Androd の標準の写真をみるアプリ)で見れない(はず)
  • カメラフォルダ以外(スクリーンショットとか)の写真もバックアップ対象になってる

というのがありそうです(2つ目については今回試したスマホがこの状態でした)。

なんにせよ、使う前にはいろいろとテストをすることをお勧めします。

LINE ボット 『リフティング回数レコーダー』を作成してみた

第25回伊勢IT交流会でネタがなかったので、ちょうど作りたいと思っていた、 LINE ボットを GAS を利用して作ってみました。

作ったボットは『リフティング回数レコーダー』というものです。

子どもがサッカーやってるんですが、そこではリフティングを頑張るのが推奨されてます。で、毎日、リフティングやって、保護者が回数を数えているので、どうせなら簡単に記録しておきたいなと思ったのが発端です。

LINE ボットを作るには、 Webhook を受け取るサーバーが必要になります。サーバーを立てるとなると面倒だし、第一お金もかかってしまいます。その点、 GAS を使えば、ウェブアプリとしてデプロイすることができるので、これならお金もかからずにお手軽に遊べそうです。

ということで、ボットとしては、まだ未完成なのですが、作っていく際に気づいたあれこれをメモっておきます。

GAS を Web アプリとして仕立てる

GAS のリファレンスを見ると、 doGet() と doPost() を関数として用意して、デプロイする際に『ウェブアプリ』を選択すれば、それだけでよいようです。

たとえば、こんな風に定義してみます。

function doGet(e) {
  let param = e;
  Logger.log("GET: " + JSON.stringify(param));

  return ContentService.createTextOutput(JSON.stringify(param)).setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  let param = e;
  Logger.log("POST: " + JSON.stringify(param));

  return ContentService.createTextOutput(JSON.stringify(param)).setMimeType(ContentService.MimeType.JSON);  
}

関数を定義したら、Apps Script の編集画面の右上にある『デプロイ』を選択します。

デプロイの設定画面で、『デプロイタイプを選択してください』とあるので、

左上あたりにある歯車アイコンをクリックして、『ウェブアプリ』を選びます。ウェブアプリ用の設定画面が表示されるので、

  • 『次のユーザーとして実行』:自分
  • 『アクセスできるユーザー』:全員

として、デプロイします。『アクセスできるユーザー』の『全員』は Google アカウントを持たない場合も含まれており、LINE の Webhook から呼び出す場合は、こちらを選んでおく必要があります。

デプロイに成功したら、curl で呼び出してみます。

mor@DESKTOP-DE7IL4F:~$ curl -L https://script.google.com/macros/s/デプロイID/exec
{"contextPath":"","parameters":{},"contentLength":-1,"parameter":{},"queryString":""}mor@DESKTOP-DE7IL4F:~$
mor@DESKTOP-DE7IL4F:~$

こんな感じで返ってきました。問題なく動作しているようです。

なお、上記のサンプルコードでは認証は求められないのですが、コード内部で Google Drive へのアクセスがあったりすると、最初にデプロイする際に、

という感じで承認を求められますので、画面の指示に従って、承認を行います(実行ユーザーに対する承認ということですね)。

LINE ボット

GAS をウェブアプリとしてデプロイできるようになったので、本題の LINE ボットを作ります。

LINE ボットは Messaging API の Webhook URL で指定した URL に POST でイベントが送られてくるので、それを処理するようにしてやればOKです。

おおまか処理としては

  • LINE からイベントが送られてくる(複数の可能性あり)
  • 複数イベントをループでひとつづく処理する
  • イベント種類に応じて、ボットで行いたい処理を決める

という感じです。今回は、 message イベントにのみ対応するように実装しました。message イベントではメッセージの最初に行いたい処理を記述することで、リフティング回数の登録と確認ができるようにしました。

スプレッドシート

リフティング回数の記録は、せっかく GAS を使うので、スプレッドシートにユーザーごとのシートを用意して、そこに記入していくようにします。

ユーザー名は、 LINE の Webhook で取得できる『ユーザーID』を使ってます。GAS だと簡易的に DB の代わりもさせられるので、その点も楽ですね。

スプレッドシート(というより Google Drive)を扱ううえで、ちょっと注意が必要だったのが、

  • ファイルを新規作成すると必ずルートフォルダに作られるので、所定のフォルダに動かすのは自分でやる
  • 同名ファイル/フォルダ をいくつでも作成可能

なので、コードでファイルを指定して開くときも、ファイル名で指定すると複数のファイルが取得されたりします。そのあたりの扱いが WIndows などとはちょっと違うので注意が必要です。

具体的な中身は GitHub にアップしたコードを見てください。

github.com

(参考)

Messaging API と GAS を連携

LINE ボットは以前試した際に理解したのですが、LINE Official Account manager で公式アカウントを作り Messaging API を有効にして、 Webhook URL を指定してやればOKです(LINE Developer Console から作る方法ありですが、公式アカウントが勝手に作られます)。

なので、GAS 側の処理を記述したら、デプロイを行って、URLを Messaging API の Webhook URL として入力します。

ちなみに、このとき、 LINE Developer Console を開いて、 Messaging API の設定画面で

Webhook URL のところにある『検証』を押しても、下記のようなエラーになり検証できません。

GAS をウェブアプリとして公開している場合、一度リダイレクトされるので、それが影響しているっぽいですね。 ただ、実際に Webhook として用いることはできるので、ご安心を。

GCP プロジェクトへの切り替え

GAS 側の処理を書いて、 LINE のボットの設定も行ったので、これで動作するはずです。

でも、GAS のエディタ上で実行した場合は、画面上でログ(Logger.log や console.log の出力)を見ることができますが、doGet や doPost で呼び出した場合は、このままではログを見ることができません(以前は、何も設定を変えなくても見れたように思いますが、いつのころからか設定が変わったようです)。

LINE とかで連携して動作させる場合、このままログが見れないとデバッグ時とかなかなかつらいものがあります。

で、ログを見れるようにするには、 GAS のプロジェクトの設定を、デフォルトのものから、標準の GCP のプロジェクト (standard GCP Project) に切り替える必要があるそうです。

ということで、この切り替え作業を行っておきます。

標準の GCP プロジェクト (standard GCP Project) への切り替え

標準の GCP プロジェクトへの切り替えは、いろいろな記事で紹介されているので、ここでは詳しく触れません。

これらの記事を読んでいて、気になった点だけ補足しておきます。

  • OAuth 同意画面の設定時、スコープおよびテストユーザーは指定しなくてもよい
  • OAuth 同意画面の構成後、『アプリを公開』を実行し、公開ステータス『本番環境』へ変更する
  • 認証情報『 OAuth 2.0 クライアントID』が自動的に作成されるので、これも明示的に作成しなくてよい

といった点に気をつければ、トラブルも少ないかと思います。

承認について

GCP プロジェクトに切り替え後、同じURLでアクセスすると、画面のような表示が出てきます。

じゃあ、承認してやればいいやと思ったのですが、ブラウザ上には認証できそうなリンクやボタンがありません。

ここで、少々はまったのですが、結論としては、下記の記事にあるように、開発環境上で実行してやることで、認証を呼び出すことができました。

Google Apps Scriptの承認エラー 401の対処方法

適当な関数を選んでエディタから『実行』を呼ぶと、

と表示されるので、あとは画面の指示に従って、承認を行います。

承認時のエラー

もし、途中で、

のようなエラーが表示される場合、OAuth 同意画面の設定で『公開ステータス』が『テスト』のままになっていると思われます(上記の記事にもありますね)。

OAuth 同意画面を開いて、『アプリを公開』を押すと、

のような確認画面が表示されるので、確認を押して、公開します。下記のように、公開ステータスが『本番環境』となってれば、OKです。

ちなみに、参考までに、GCP プロジェクトへ切り替える方法を紹介している昔の記事などでは、GCP の『OAuth 同意画面』で認証が出きるっぽいことが書いてありますが、このあたりのフローがどうも若干変わっているようです。

Google Apps Script試行錯誤 Blog: デフォルトのGCPプロジェクトを標準のGCPプロジェクトに切り替えたい

エラー

標準の GCP プロジェクトに切り替えた後、承認もしたので、動くだろうと思って試してみると、動きません。エディタから動かしてみると、こんな感じになります。

ブラウザだとこんな感じのそっけないメッセージです。

さきほど、プロジェクトを切り替えて、ログがみれるようになったので、そちらを見てみると、 permission denied との文が見られますので、どうも権限関係のようです。 ですが、いろいろと調べてみても、どこから権限を有効にするのかが全然わかりません。

ためしに、GASのファイルをコピーして、同じコードでも別ファイルにして動かすと、問題なく動きます。GASのファイルをコピーすると、プロジェクトはデフォルトのプロジェクトに戻るので、どうも、標準のGCPプロジェクトに切り替えたのが要因のようです。

そんなのも踏まえて探してみると、だいぶ苦労しましたが、やっとのことで探し当てた記事に、ずばり解決策が載ってました。

GASでDriveAppクラスを使うと`Exception: We're sorry, a server error occurred. Please wait a bit and try again.`エラーが出る - Qiita

GCP のプロジェクト側で Google Drive API を有効にするというのがポイントのようです。

『有効なAPIとサービス』を選択し、『APIとサービスの有効化』から Google Drive API を検索します。

Google Drive API が出てきたら、これを有効にます。

この設定後、もう一度実行したら、問題なく表示できるようになりました!

にしても、エラーメッセージの内容から一時的な Google 側の問題なのかな?と思ってしまうので、『権限がないよ』というのをもうちょっとわかりやすく示してほしいものです > Google さん

動作

ここまでできたら、あとは動作させてみます。

公式アカウントのQRコードをラインで読み込んで、友達になります。

登録, 2022/7/2, 10

のように、日付と回数を送ると、登録されます。

スプレッドシート側にも間違いなく追加されています。

また、

確認, 2022/7/2

のように、日付を送ると、その日の回数を表示します。

記録が無いときは、ないことを教えてくれます。

これで、とりあえず最低限の機能は実現できました。

今後

これだけだと、ほんとためしに作りました、というだけですし、インターフェースがいかにも使いにくいです。なので、

  • 登録・確認はメニューからできるようにする
  • 指定した期間の推移をグラフで取得する
  • 同じ日付の場合の重複処理、日付が前後した場合の処理などを実装する
  • 目標回数を設定して、達成できたら、おめでとうメッセージを送る

などを作りこんでいきたいと思います。

そこまでできたら、一番怖いユーザー(子供)のレビューですかね。 もうちょっとかんばります。

Excel の右クリックメニューへのメニュー追加でトラブル

先日、お客様から普段使ってるPCでトラブルがあったので予備のノートPCに変えたら、Excelで右クリックメニューに追加しているはずのメニューが表示されなくなった、と連絡がきました(マクロでメニューの追加表示処理を行っているファイルになります)。

最初は、ファイルが壊れたか?と思ってファイルを送ってもらってこちらで試しても問題なく動作します。

じゃあ、環境が変わったのだから、Excelのマクロ周りの設定かな?と思って、そちらも確認してもらっても、こんな感じに

『警告を表示してすべてのマクロを無効にする』が選ばれていました。おまけに、ファイルを開いた際に黄色の警告バーで『マクロを有効にする』を押した覚えもあるとのことでした。

という感じで、どうなってんの?と、ちょっとはまってしまったので、顛末をメモにして残しておきます。

調査

まずは、何が起きているか調査しました。ネットをあさってみると、割とすぐ、似たような現象に触れている記事を見つけることができました。

「追加したはずの右クリックメニューが表示されない」事件 - Infomentのブログ ~Excel VBA奮闘記~

これによると、改ページプレビューの場合、右クリックメニューが表示されないことがある、とのことでした。でも、問題が起きてるPCで開いているのは、『標準』ビューだしなー、と思って、もうちょっと記事を追いかけてみると、

「追加したはずの右クリックメニューが表示されない事件」の真相 ① - Infomentのブログ ~Excel VBA奮闘記~

とあります。

CommandBars に 『Cell』の名前を持つオブジェクトが複数ある、とあります。

まさかと思って、問題が起きているPCでの挙動をよく見てみると、『標準』ビューでは右クリックメニューの追加メニューが表示されていないのですが、『改ページプレビュー』で右クリックすると、見事に追加メニューが表示されていました。

ああ、これですね。 どうも、CommandBarsのオブジェクトの並びがExcelによって変わっているようです(問題が起きているノートと問題がないPCのExcelのバージョンは全く同じでしたので、バージョン以外で何が影響しているかまでは分かりませんでした)。

対応策

原因が分かってしまえば、あとは簡単です。 今回の場合は、どちらのビューの場合にも右クリックメニューを表示させれば問題ないはずです。

ということで、 ThisWorkbook に

Private Function listupCommandBars() As Collection

    Dim lst As Collection
    Set lst = New Collection
    
    ' 指定の名前を持つコマンドバーをリストアップ
    Dim cmdbar As CommandBar
    For Each cmdbar In Application.CommandBars
    
        If cmdbar.Name = "Cell" Then
            lst.add cmdbar
        End If
    Next

    Set listupCommandBars = lst
End Function


' コンテキストメニューの追加
Public Sub addContextMenu()

    Dim lst As Collection
    Set lst = listupCommandBars
    
    Dim cmdbar As CommandBar
    For Each cmdbar In lst
        addContextMenuOnCmdbar cmdbar
    Next
    
End Sub
Private Sub addContextMenuOnCmdbar(ByRef cmdbar As CommandBar)

    Dim ctx_menu As CommandBarControl
    
    Set ctx_menu = cmdbar.Controls.add(Type:=msoControlButton, temporary:=True)

    With ctx_menu
        .Caption = "テスト"
        .OnAction = callbackMethodTest
        .BeginGroup = True
    End With
End Sub

' コンテキストメニューの削除
Public Sub removeContextMenu()

    Dim lst As Collection
    Set lst = listupCommandBars
    
    Dim cmdbar As CommandBar
    For Each cmdbar In lst
        removeContextMenuOnCmdbar cmdbar
    Next

End Sub
Private Sub removeContextMenuOnCmdbar(ByRef cmdbar As CommandBar)
    ' コンテキストメニュー未登録時にはエラーが発生するので回避できるようにする
    On Error Resume Next
    cmdbar.Controls( "テスト").Delete
    On Error GoTo 0
End Sub

という感じで、右クリックメニュー追加・削除用の処理を書いておいて、実際に右クリックメニューを追加したいワークシート側で

Option Explicit

' ワークシートがフォーカスを失ったら、追加したメニューを取り除く
Private Sub Worksheet_Deactivate()
    'Debug.Print "worksheet.deactivate called"
    ThisWorkbook.removeContextMenu
End Sub

' コンテキストメニューの追加
'   ワークシート上で右クリックすると、メニューが追加されて表示される
'
Private Sub Worksheet_BeforeRightClick(ByVal target As Range, Cancel As Boolean)
    ThisWorkbook.removeContextMenu
    ThisWorkbook.addContextMenu
End Sub

こんな感じにして、対応しました。

まとめ

このマクロ、細かい修正はあれこれ手を入れていますが、おおもとの部分は結構前に作ったはずです。それでも、つい最近までこんな問題があることに気が付きませんでした。Excel、怖いな。