Google Drive #4

  • 前回の記事で Google Drive 上のファイルを読み出すために 3 回のアクセスが必要と書いたが、パスの構築時に basename も含めることでこれを 2 回に抑えることができた。
  • あるファイルがその親ディレクトリを複数持てるというのは、そのままの通りの意味のようだ。ハードリンクみたいな感じか。ちょっとこれは面倒なのでちゃんと対応するのは後回し。現状では、パスの構築時は親は 1 つだけの前提にしている。
  • ファイルの実体を読み出す際、ファイルのメタデータに含まれる downloadUrl が指すアドレス(たいてい https://*.googleuserconent.com/*)を参照することになる。一方で、OAuth 2.0 による承認を行う際、スコープと呼ばれる識別子を送る。[cci]https://www.googleapis.com/auth/drive[/cci] が最も強い権限、[cci]~/drive.file[/cci] がファイルの操作、[cci]~/drive.readonly.metadata[/cci] がメタデータの読み込み、みたいな。

    このうち最も強い権限を持つ drive スコープはほんとうに必要な場合のみ使ってね! とドキュメントにも明示してあるのだが、どうも downloadUrl からの読み込みは drive スコープじゃないと行えないような。drive.file だと 404 が帰ってくる。これは……バグじゃないのかな。というわけで drive スコープを使わざるを得ない。そんなような問いと答えが stackoverflow にもあった。

残るのはファイルの書き込み。

Google Drive #3

Google Drive 上の、あるファイルへのパスが与えられたときそれを読み込む手順は以下のとおりとした:

  • パスを dirname と basename に分割する
  • dirname をディレクトリ名ごとに分割し、各フラグメントを [cci]title=’ディレクトリ名'[/cci] に変換し、[cci]or[/cci] で結合してクエリ文字列を得る。[cci]/foo/bar[/cci] は [cci]title=’foo’ or title=’bar'[/cci] となる
  • File:list API を呼び出す。このとき q パラメータは上のクエリ文字列を与える
  • API の結果から、目的のパスに合致するメタデータの並びを得る。上の例で言うと、まずディレクトリ bar があり、その親 ID を持ち、かつタイトルが foo であるものを抜き出す。その処理をルートに向かって再帰的に行い、ルートに達した(分割したディレクトリの個数 == 抜き出したメタデータの個数となった)らパスが決定する
  • File:list API を呼び出す。このとき q パラメータは dirname の最後のフラグメントの fileID を親に持つファイル、とする。つまり [cci]’fileID’ in parents[/cci] とする。これは Children:list API でもよい気がするが、Children:list は指定のファイルの子供の ID しか返さない。
  • API の結果から、basename に一致するものがあれば、その downloadUrl を用いてファイルの内容を読み込む。なければ新しいファイルの編集とみなし、空文字列を wasavi へ返す

つまり 1 つのファイルを読み込むのに 3 回のリクエストが必要になる……それにしてもイケてないなー! もっとうまい方法はないのかしら。

あと、ひとつ気になる点がある。あるファイルのメタデータ内に、その親ファイルの ID が格納されているのだが、名前が parents で、型は配列なのである。あるファイルが複数の親を持つことができる? どうやって?

Google Drive #2

そんなわけで、OAuth 2.0 で認証したりする Google Drive 対応部を書いている。だいたいここここを見ればすべきことは書いてある。

ところで API のリファレンスを眺めてみると、なんか任意のファイルへアクセスするのにパス形式の文字列を使用する機能がないような? つまり、My Drive -> folder A -> folder B -> folder C -> foo.txt みたいな位置にあるファイルを読み書きするためには、dropbox の API であれば
GET /1/files/dropbox/folder_a/folder_b/folder_c/foo.txt
みたいなリクエストを素直に投げればいいのだが、Google Drive だとルートフォルダから 1 階層ごとに
GET /drive/v2/files/root/children/
みたいなリクエストを投げて、フォルダの中身のメタデータを見て掘り進んでいくようなことをしなければいけない。当然、パス階層が深ければその分リクエストの回数も多くなる。

えー……? 天下の Google 様がそんなアホみたいな仕様にしてるなんて嘘でしょ? なんか方法があるんでしょ? と思ったけど特にないようだ。

どういうことなの。なにか見落としてるんだろうか。

* * *

Files:list において、q パラメータに対して title=’folder A’ OR title=’folder B’ OR title=’folder C’ OR title=’foo.txt’ みたいな物を与えて、帰ってきたデータを元にルートから目的のファイルまでのパスを自前で構築し、目的のファイルの ID を特定すれば、とりあえず読む分においては、リクエスト回数は常にパス情報の取得 + 読み込みの 2 回で済むことに気がついた。

なんかイケてないなー。

Google Drive

0.5.320 をリリースした

ところで wasavi が対応しているオンラインストレージは今のところ dropbox だけであるが、Google Drive と Microsoft Skydrive に対応する気がないこともない。対応しないままずるずる来ているのは、つまり 1 年ほど前、これらのサイトは試験的に、認証・承認に OAuth 2.0 を使っていたわけなのだが、その当時は OAuth 2.0 はまだ仕様が流動的だったのだ。

現在、OAuth 2.0 自体の規格はまだ正式に固まっているわけではないみたいだけど、google drive、skydrive ともに OAuth 2.0 での認証・承認が普通に行われるようになって久しいようなので、ぼちぼち対応し始めてもいい頃合いかもしれない。

OAuth 1.0 では、jsOAuth というライブラリを使わせていただいたのだが、2.0 はもうちょっとシンプルな仕様になっている。そこで、もっと薄いライブラリを自前で用意するか、あるいは極端に言えば、直接 xhr をいじる方向でもいけるような気がする。

ちなみに dropbox はいつ OAuth 2.0 に対応するのだろうか。1 年前から「将来は 2.0 に対応するかも!」みたいなアナウンスはあったけど未だに実際の行動がなされていないまま今に至っているような。

completion #4

ファイル名の補完もできるようにした。ファイル名は dropbox へのネットワークアクセスが発生するため、「間」が発生するのは避けられない。補完が非同期になる場合はこんな感じで「補完してますよー」的なものが出る:
completion1
補完が完了すると何項目のうちの何番目ですよ的なものが出る:
completion2

completion #3

set コマンドについて、オプション名を補完できるようにもした。また、オプション名の略語を展開するようにもした。つまり set ai と打って tab を押すと、set autoindent に展開される。また、shift+tab で補完リストを逆方向に辿れるようにした。

これで、補完対象となる 3 種のうち 2 つまでを作り終えた。残るのは dropbox のようなストレージ上のファイル名の補完だ。

この手の補完は、他の 2 つとは決定的に違う点があって、つまり補完リストを得るためにネットワークアクセスが発生するのである。そうすると、アクセスの頻度とか、遅延とか、ワーストケースを考えつつ組まなければいけない。

まずアクセス頻度を抑えるために補完リストは最低数分間はキャッシュするようにしよう。また遅延は、たとえばアクセスして 15 秒経過しても反応がない場合は中断するような監視タイマを設け、中断時は古いキャッシュを使いまわすような仕組みにすればいいんじゃないかな!

completion #2

まずは ex コマンド名の補完に絞って作ってみる。

基本的な考え方は、入力行が ^\s*([^”\S]*).*$ にマッチし、カーソルが \1 の中にある場合、つまり ex コマンド名の入力途中であるとみなされる場合、tab の押下により \1 の範囲内を補完し置き換えるようにすればいい。

しかしながら、これは本当に核の部分の基本的な仕組みであって、いろいろと考えることはある。

  • この正規表現はアドレスの指定を考慮していない。アドレス指定の文法はかなり複雑 なので、補完機能の中にパーサを入れるのはちょっと無駄だ。ex コマンド実行部のパーサを流用できるようにしたい。
  • ex コマンドは | で連結することができる。したがって、補完は行頭の場合のほか、| の直後にも行われないといけない。ただし、/ の引数やアドレス指定内のリテラル内での | 、および ” 以降のコメント内の | は ex コマンドの区切りではないので、補完の開始トリガーとみなしてはならない。これもやはり、ex コマンドパーサの結果を使用しないと正しい動作を行うことができない。

というわけで、ex コマンド実行部にまず拡張を施し、純粋にパースだけを行うことができるようにした。その結果を元に補完を行うかを判断することになる。

また、補完処理は補完の準備が整っていることを確認した後に非同期的に行うようにしないといけない。コマンド名のリストであれば同期的な処理で構わないが、dropbox 上のファイルパスの補完などを行うことを考えると非同期にならざるを得ない。

というわけで作ってみた。

  • 補完対象を置き換える範囲をどうするか? 上記の正規表現の \1 の部分を置き換えるようにすると、
    ver#foo
    みたいなのがあったとき(# はカーソルを示す)、補完後の文字列は例えば version とかになる。つまりカーソルの後ろの領域も置き換えられる。一方、vim では置き換えるのはカーソル位置までなのである。つまり versionfoo とかになる。どちらがいいか。

completion

: で ex コマンドの入力モードに入るが、この時にさまざまな補完機能を作るのを後回しにしていた。それを作る。

補完はいくつかのタイプに分けられる。

  • ex コマンド名の補完
  • file, edit, write, read コマンドの引数としてのファイル名の補完
  • set 時のオプション名の補完

これらの区別は、入力済みの ex コマンド文字列とカーソルの位置がそれぞれの形式にマッチするかということになるのだけど、ex コマンドは | で連結できる上、コマンドによっては | がその効力を失うものもある。その辺の判定もちゃんとやる必要がある。

また、ファイル名の補完というのはたとえば dropbox をバックエンドにするとして、バックグラウンド側に処理を投げ、dropbox の API を呼んで指定のパスのファイル一覧を得て…みたいなことをする必要がある。このへんの動きは非同期になる。

Chromebook

wasaviについての評価とか、要望とか、バグ報告というものは

  • github の issues
  • Chrome Web Store
  • Opera Addons
  • Mozilla Addons
  • ここのサーバのフォーラム
  • メール

辺りの窓口がある。それぞれぽつぽつとコンタクトがある。

その内のバグ報告に y/d/c 辺りのオペレーションが . コマンドの繰り返し対象にならない、というのがあって直したことがある。といってもそのバグ自体は別によくて、興味深いのは、報告してくれた方が Chromebook のユーザーのようであることなのだ。

むう… Chromebook …聞いたことがある……。

聞いたことはあるが、全体像がよくわからない代物ではある。そもそも日本国内では発売されていない。また Chrome OS は基本的にブラウザしか動かせず、すべてのアプリケーションは html と javascript(と flash と NaCL?)で作られるとかなんとか。ベースは Ubuntu らしいけど、ターミナルとかも開けないのかな? ぜんぜんわからないことばかりだ。

わからないが、本当にブラウザしか動かないのだとしたら、テキストエディタを使いたいけどメモ帳的なそれは使いにくいよー! vi とかemacs とか使いたいよー! といった向きには確かに wasavi は向いているかもしれない(そこで vi 云々を発想する人はそもそも Chrome OS の対象外のような気もするが)。

content-editable #2

次に書き込む場合の問題を考える。

wasavi からの書き込みがリクエストされた時点で、対象の要素が input か textarea であれば、value 属性を通してテキストを書き込むだけでいい。しかしそれ以外の要素ならば、DOM のメソッドを使ってツリー構造を構築しないといけない。

input/textarea 以外の要素に対して wasavi が起動したならば、wasavi 側もそれを認識して、出力は改行で区切られた文字列の配列の形式になっている。ツリーがどういう構造であるべきかはサイトに依存するような気もするけど、とりあえずそれを単に取り出して p 要素の内容にしていくことにする。