そうだ!Dropboxのdiff(差分)をとろう!

そもそも前回なぜ、Dropboxcurlコマンドでアクセスしたかったのか?というと、Dropboxの過去のバージョンとの差分を見たかったのだ。Dropboxの素晴らしすぎる機能の一つに、過去一ヶ月間くらいのファイルの変更履歴を保持する仕組みがある。過去のバージョンを見たいときは...

  • FinderでDropbox内のファイルを右クリックして「以前のバージョンを表示」を選択する。
  • すると、WebブラウザDropboxページが開いて、バージョン履歴がリスト表示されるのだ。
    • そのリンクをクリックすれば、その内容が表示される。
    • そのリンクをoption-クリックすれば、ファイルとしてダウンロードされる。

素晴らしい!AppleOSXでバージョンという仕組みを作る以前からバージョン管理されていた。ファイルの同期も信頼性が高く、使い勝手もすこぶる快適。常にiDiskの一歩先を進んでいた。そんなDropboxで、diffをとりたくなったのだ。

先人の技を探す

  • 自分がやりたいと思ったことは、世界のどこかで、誰かがきっと試している。
  • 車輪の再発明をしないように、まずはWebを念入りに検索するべきなのだ。
  • そのようにして、二つの仕組みを見つけた。
DropDiff(コマンド)
$ chmod a+x ~/Downloads/dropdiff.sh
$ ~/Downloads/dropdiff.sh ~/Dropbox/hello.txt
Unhandled DB schema version 2
  • 実行権限を付加して、いくつかのバージョンを試してみたが、エラーが出てしまう。
  • どうもDropboxのDBを参照してパスを取得しているようなのだけど、それがうまくできていない感じ。
  • また、たとえ正常に実行できたとしても、最新ファイルと一つ前のバージョンの差分に限定されるようだ。
DropboxDiff(Chrome機能拡張)


  • こちらはChromeの機能拡張。GUIでとりたい差分を自由に選択して、Diffボタンで出来るはずなんだけど...
  • マウスがくるくる回ったまま、いっこうに終わる気配がない...。
  • (設定のDiff Programには、/usr/bin/opendiff を設定した)

どちらも、自分の環境ではうまく動いてくれなかった...。

dropbox_diffプロジェクトの開始

というわけで、自分で作ってみる気になって、前回につながる。(そんな経緯でcurlDropboxにアクセスしたくなったのだ)

方針など
  • シンプルに、diffコマンドの出力で十分。
  • dropdiff.shを参考にしようとしたが、diff本来のコードより、周辺のコードが自分にとっては複雑過ぎ。
  • よって、全部自分で書き直す。
  • 最新バージョンだけでなく、あらゆるバージョンの差分をとれるようにする。
前回のコード
#!/bin/bash

EMAIL_ADRESS=$1
PASSWORD=$2
FILE_NAME=$3

curl -L -c cookie.txt -o output.html https://www.dropbox.com/login
TOKEN=`cat output.html | grep -e '<input type="hidden" name="t" value=".*" />' | grep -o 'value=".*"' | grep -o '".*"' | grep -o '[^"].*[^"]'`
curl -L -b cookie.txt -c login_cookie.txt -o output.html \
     --data-urlencode "t=$TOKEN" \
     --data-urlencode "login_email=$EMAIL_ADRESS" \
     --data-urlencode "login_password=$PASSWORD" \
     https://www.dropbox.com/login
curl -L -b login_cookie.txt -o output.html "https://www.dropbox.com/revisions/$FILE_NAME"
最新のdiffのみ出力できるバージョン
  • 最初はとにかく動くことを目指して、最新バージョンとその一つ前のdiffのみを実現する。
  • 前回、ログインするところまではできているので、
  • あとはバージョン履歴のページを出力して、
  • その中から、各バージョンへのリンクURLを抜き出し、
  • 最新バージョンの一つ前のファイルをダウンロードして、
  • diff出力している。
  • コマンド引数にパスワードなどを入力するのはセキュリティ上よろしくないので、(コマンド履歴に残ってしまう)
  • コマンド実行後に対話的にメールアドレスとパスワードを入力するように変更した。
#!/bin/bash

FILE_PATH=$1
FILE_NAME="${FILE_PATH##*/}"
REVISION_FILE_PATH="https://www.dropbox.com/revisions/$FILE_NAME"

read -p 'email-adress: ' EMAIL_ADRESS
read -s -p 'password: ' PASSWORD; echo

curl -L -c cookie.txt -o output.html https://www.dropbox.com/login
TOKEN=`cat output.html | grep -e '<input type="hidden" name="t" value=".*" />' | grep -o 'value=".*"' | grep -o '".*"' | grep -o '[^"].*[^"]'`
curl -L -b cookie.txt -c login_cookie.txt -o output.html \
     --data-urlencode "t=$TOKEN" \
     --data-urlencode "login_email=$EMAIL_ADRESS" \
     --data-urlencode "login_password=$PASSWORD" \
     https://www.dropbox.com/login

curl -L -w "%{url_effective}" -b login_cookie.txt -o output.html $REVISION_FILE_PATH
echo
URLS=(`cat output.html|grep -o '<a href="https://dl-web.dropbox.com/get/.*</a>'|grep -o '"https://.*"'|grep -o '[^"].*[^"]'`)
echo ${URLS[@]}
CONTENTS=`curl -s -b login_cookie.txt ${URLS[1]}`
echo -en $CONTENTS|diff -u - $FILE_PATH
  • 実行結果
$ ./dropbox_curl.sh ~/Dropbox/hello.txt
email-adress: xxxx@mail.com
password: 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 19952    0 19952    0     0  14062      0 --:--:--  0:00:01 --:--:-- 21639
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   369    0   274  100    95    293    101 --:--:-- --:--:-- --:--:--   340
100  100k    0  100k    0     0  45941      0 --:--:--  0:00:02 --:--:--  163k
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 24212    0 24212    0     0  20839      0 --:--:--  0:00:01 --:--:-- 23529
https://www.dropbox.com/revisions/hello.txt
https://dl-web.dropbox.com/get/hello.txt?w=507561d6&sjid=29 https://dl-web.dropbox.com/get/hello.txt?w=507561d6&sjid=28
--- -	2012-10-07 17:12:28.000000000 +0900
+++ /Users/zari/Dropbox/hello.txt	2012-10-06 13:06:31.000000000 +0900
@@ -1 +1 @@
-hello
+hello world!!

以上が必要最小限のコードのべた書き

無駄な処理、繰り返しを削除する
  • login_cookie.txtを手に入れれば、それを手土産にDropboxに自由にアクセスできる。
  • よって、毎回ログインするのは無駄なので、必要なときだけログインする仕組みに変更。
  • 具体的には、バージョン履歴のページにアクセスしたとき、リダイレクトされたらログインし直すようにした。
    • (ログインが必要な時は、ログインページへリダイレクトするはず)
    • -w "%{url_effective}" = 最後にアクセスしたURLを返すオプション設定。
    • -Lと組み合わせることで、リダイレクト先のURLが取得できるのだ。
  • 呪文のような長いコードは、機能別に関数にまとめた。
#!/bin/bash

FILE_PATH=$1
FILE_NAME="${FILE_PATH##*/}"
REVISION_FILE_PATH="https://www.dropbox.com/revisions/$FILE_NAME"




dropbox_login() {
  read -p 'email-adress: ' EMAIL_ADRESS
  read -s -p 'password: ' PASSWORD; echo
  
  curl -L -c cookie.txt -o output.html https://www.dropbox.com/login
  TOKEN=`cat output.html | grep -e '<input type="hidden" name="t" value=".*" />' | grep -o 'value=".*"' | grep -o '".*"' | grep -o '[^"].*[^"]'`
  curl -L -b cookie.txt -c login_cookie.txt -o output.html \
       --data-urlencode "t=$TOKEN" \
       --data-urlencode "login_email=$EMAIL_ADRESS" \
       --data-urlencode "login_password=$PASSWORD" \
       https://www.dropbox.com/login
}

revision_files_page() {
  curl -L -w "%{url_effective}" -b login_cookie.txt -o output.html $REVISION_FILE_PATH
}

extract_file_urls() {
  cat output.html|grep -o '<a href="https://dl-web.dropbox.com/get/.*</a>'|grep -o '"https://.*"'|grep -o '[^"].*[^"]'
}

download_revision_file() {
  curl -s -b login_cookie.txt ${URLS[$1]}
}




# リダイレクトした時だけログインし直す
RES=`revision_files_page`
if [ $RES != $REVISION_FILE_PATH ]; then
  dropbox_login
  revision_files_page
fi

echo
URLS=(`extract_file_urls`)
echo ${URLS[@]}
CONTENTS=`download_revision_file 1`
echo -en $CONTENTS|diff -u - $FILE_PATH
任意のバージョンを指定してdiff
  • 上記までは、最新のバージョンとその一つ前の差分しかとれなかった。
  • ここでは対話的に操作して、任意のバージョンを指定できるようにした。
  • whileブロック内が対話的に任意のバージョンを指定する部分。
    • 何も入力しない場合は、最新バージョンとその一つ前のdiff。
    • バージョン番号を一つだけ入力した場合は、バージョン番号とその一つ前のdiff。
    • バージョン番号をスペースで区切って二つ入力した場合は、二つのバージョン番号のdiff。
  • バージョン番号の範囲をチェックしたり、補助コマンドの処理などで長くなっているが、やっていることは必死に条件判断をしているだけ。
  • ついでに、Dropbox直下のファイルしか正常に動かなかった状態を修正した。
  • Dropbox内に「Dropbox」というフォルダが存在しない限り、正常の動く予定。
    • FILE_NAME="${FILE_PATH##*/}" → DROPBOX_PATH="${FILE_PATH##*/Dropbox/}" に変更
    • REVISION_FILE_PATH → REVISION_FILE_URL に変更
#!/bin/bash

FILE_PATH=$1
DROPBOX_PATH="${FILE_PATH##*/Dropbox/}"
REVISION_FILE_URL="https://www.dropbox.com/revisions/$DROPBOX_PATH"



# Dropboxへログインする
dropbox_login() {
  read -p 'email-adress: ' EMAIL_ADRESS
  read -s -p 'password: ' PASSWORD; echo
  
  curl -L -c cookie.txt -o output.html https://www.dropbox.com/login
  TOKEN=`cat output.html | grep -e '<input type="hidden" name="t" value=".*" />' | grep -o 'value=".*"' | grep -o '".*"' | grep -o '[^"].*[^"]'`
  curl -L -b cookie.txt -c login_cookie.txt -o output.html \
       --data-urlencode "t=$TOKEN" \
       --data-urlencode "login_email=$EMAIL_ADRESS" \
       --data-urlencode "login_password=$PASSWORD" \
       https://www.dropbox.com/login
}

# ファイルのバージョン管理のページを取得する
revision_files_page() {
  curl -L -w "%{url_effective}" -b login_cookie.txt -o output.html $REVISION_FILE_URL
}

# バージョンごとのファイルのURLを抜き出す
extract_file_urls() {
  cat output.html|grep -o '<a href="https://dl-web.dropbox.com/get/.*</a>'|grep -o '"https://.*"'|grep -o '[^"].*[^"]'
}

# 指定したバージョンのファイルをダウンロードする
download_revision_file() {
  curl -s -b login_cookie.txt ${URLS[$(($MAX_VERSION - $1))]}
}




# リダイレクトした時だけログインし直す
RES=`revision_files_page`
if [ $RES != $REVISION_FILE_URL ]; then
  dropbox_login
  revision_files_page
fi

# ファイルのURLを配列にして、バージョンの個数を取得する
URLS=(`extract_file_urls`)
MAX_VERSION=${#URLS[@]}

# 対話的に操作する
while :
do
  # バージョンリストを表示
  echo
  echo '*** Version list(Top is newest) ***'
  for (( i = $MAX_VERSION; i > 0 ; --i ))
  do
    echo -e "    $i: Version$i"
  done
  
  # 入力待ち
  read -p 'Select( number  [o]pen  [h]elp  [q]uit )> ' VER1 VER2
  
  # 入力コマンドの処理、バージョン番号を取得、入力値のエラー処理
  if [[ $VER1 = "q" ]]; then
    echo 'quit'
    exit
  elif [[ $VER1 = "o" ]]; then
    echo 'open'
    continue
  elif [[ $VER1 = "h" ]]; then
    echo 'help'
    continue
  elif [[ $VER1 =~ ^[0-9]*$ ]] && [[ $VER2 =~ ^[0-9]*$ ]]; then
    if [[ -z "$VER1" ]]; then
      VER1=$MAX_VERSION
    fi
    if [[ -z "$VER2" ]]; then
      VER2=$VER1
      VER1=`expr $VER1 - 1`
    fi
    if [[ $VER1 -gt 0 ]] && [[ $VER1 -le $MAX_VERSION ]] && [[ $VER2 -gt 0 ]] && [[ $VER2 -le $MAX_VERSION ]]; then
      break
    fi
  fi
  echo 'error!'
  exit
done
echo "\`diff Version$VER1 Version$VER2\`"
echo

# 指定バージョンをダウンロードする
if [[ $(($MAX_VERSION - $VER1)) = 0 ]]; then
  CONTENTS_1=`cat $FILE_PATH`
else
  CONTENTS_1=`download_revision_file $VER1`
fi
if [[ $(($MAX_VERSION - $VER2)) = 0 ]]; then
  CONTENTS_2=`cat $FILE_PATH`
else
  CONTENTS_2=`download_revision_file $VER2`
fi

# diff出力
diff -u <(echo "$CONTENTS_1") <(echo "$CONTENTS_2")
echo
  • 指定バージョンをダウンロードするとき...
    • 最新のバージョンは、OSファイルシステムDropbox内に存在するので、それを利用する。
    • わざわざ余分なダウンロードしないことで、処理の高速化につなげるのだ。
仕上げ

対話的な操作のopenコマンド・helpコマンドの処理を追加して、ひとまず完了。

課題
  • cookie.txt、login_cookie.txtの管理をどうするべきか?
  • output.htmlは作らないように変更したい。
  • dropbox_diff.shの引数オプションを追加したい。

閃いたときに、修正して行く予定。

開発環境