iPhoneをUSBメモリ代わりに使う
会社と自宅の行き来で、巨大ファイルを持ち運びたくなってググッたら発見。http://www.macroplant.com/iexplorer/
自宅のネットワークがヘボくて、何度やっても829MBあるiPhoneの5.0.1アップデートがダウンロード途中でコケてた。会社でダウンロードしてiPhone内に今保存できたので、自宅に持ち帰ってからアップデートする予定。ナイスっ!
appengineのサーバー間通信で楽したいためのフレームワーク
時間がないから作れないけど頭の中のイメージを書いておく。
・appengineのアプリと別のappengineのアプリでのRPC。サーバーとサーバーの通信。
・URLFetchの10秒制限から離れて実現する。GWT-RPCのように要求側(あくまでもappengineサーバー)から見てレスポンスはAsyncCallbackする。そんなわけで10分以内であればTaskQueueを、それも超えるような場合はBackendsのTaskQueueを使うようにサーバー側が調整できる。要するにサーバー側からクライアントに返すデータはHTTPのResponseではなくRequestで「後で」返す。
・クライアント(あくまでもappengineサーバーの)側は、CPUを占有したくない。Backendsに常駐しちゃってもいいじゃん的なことではなく。単発的に起きる事象にも対応したいため。なので応答をcronでポーリングなんて無駄なのでしない。
で、これを実現するために、要求者には自身をDatastoreにシリアライズした上で「死んでもらう」。
...なんかごちゃごちゃするので、取説から書いてみよう。このヘンテコRPCのハローワールドを。名前考えてないからとりあえずHenteko。とりあえず利用者の手数が少なくなるアノテーション前提で書いてみる。ただアノテーションプロセッサ、自分で書く気が起きないけど。オーバーヘッドが少なければリフレクションでもいいかな。必要に応じてなにかをextendsしたり、その辺りはまあどうとでも。
1.まずweb.xmlにHentekoを登録(フィルタなりサーブレットなりコントローラを登録することになるんだろうけど割愛)。
2.とりあえずGWT-RPCみたいにインターフェースとAsyncインターフェースを用意する感じで。
@HentekoService public interface GreetingService { String getMessage(String myName); }
public class GreetingServiceImpl implements GreetingService { @Queue public String getMessage(String myName) { // すっげー時間が掛かってなんとか処理が終わったりすることもあれば、 // 一瞬で終わることもあってもOKな感じで。 return "Hello " + myName; } }
@Queueアノテーションでそのメソッドをどのキューで実行するかを決める。@Queue("キューの名前")のように。これによりBackendsのキューも使える(Target指定の定義をしたキューにより)。引数なしはデフォルトキュー。Queueアノテーションを書かない場合は、そもそもTaskQueueにしない?(要検討。面倒ならデフォルトキュー)。
3.クライアント側の作成。GWT-RPCのようにペアとなるAsyncインターフェースとしておきましょうか。
public interface GreetingServiceAsync { @Server("サーバーのID") void getMessage(String myName, @Queue AsyncCallback<String> callback); }
メソッドごとにサーバーを指定することも。どっかにConfigがあって識別子に対応する サーバーを指定できる。
server1=https://xxxx.appspot.com のように。設定はxmlでもJsonでもどーでも。あ、違う。運用時にサーバーを差し替えとかできるようにするために、Datastoreとハイブリッドが良い。
で、クライアントの制御クラスを作成。かならず Serializableとすること。
それと callbackにも@Queueアノテーションを付けられる。付けなければ通常リクエストだが、付けた場合は
コールバックメソッドの実行はTaskQueue内で実行となる。もちろんこちらも引数でキュー名を指定できるようにし、
長時間の処理であればBackendsも利用可能。
@Serverに複数のサーバーIDを渡してレスポンスをリストで連結して戻してもらいたいという要求もあったりするけど、
とりあえずそれはいいや。突き詰めるとHadoopになりそうだし目的が違う感じ。
@HentekoClient public class Greeting implements Serializable { private transient GreetingServiceAsync service = Henteko.get(this, GreetingServiceAsync.class); // シリアライズしたフィールドでステートを持てる public void helloWorld() { // この辺りで final なローカル変数とか使われてもコールバックに渡らないからダメ。 : serivce.getMessage("Japan!!", new AsyncCallback<String>() { public void onSuccess(String result) { // finalなローカル変数は使えないけど、Greetingクラスのフィールド(デシリアライズできたもの)は扱える。 // ここのコードでは、helloWorldを呼び出されたときのHttp requestはアテにならない(最初のリクエスト時の情報を一書に保存してエミュレートしても良いけど...)。 } }); } }
と、まあ、使い方はGWT-RPCっぽい。Henteko.get() で、要求者を把握。サービスメソッドの実装内でシリアライズ。
iTunesがiPhoneの古い内容を表示しちゃう
Apps + Document List APIで、ドメイン間でドキュメントを配布するには
Doc List APIでは、コピー元ドキュメントのキーを指定して、コピーを作成できる。APIはこちら
しかし実はいきなり直接コピーができない。過去に相手ユーザーのドキュメントを参照した経験があるユーザーであればコピーが成功し、初めて関係を持とうとしたユーザーではUnknown errorが発生した*1。
これを解決するには配布先メンバーの閲覧権限をドキュメントのACLに追加すれば良い。私の場合は両ドメイン共にアプリ(Marketplaceアプリ)の登録者であることが前提だったので、2LOでそれぞれのドメイン用のDocsServiceのインスタンスに順に操作(一方に権限設定後もう一方にコピー実行)すれば良かった。その後にACLから配布先メンバーの権限を削除しても、そのドキュメントに「リンクを知っているユーザーなら誰でもリードオンリーで開ける」権限を設定しているため、2回目以降はACL操作は行わずにコピーAPIを動作させられるようになった。
これを行えるようにするには、Appsのドキュメント設定が以下を満たしていなければならない。
1.配布先は「共有オプション」が「共有できます」にチェックがあるか、「共有できません」でも「組織の外部からドキュメントを受け取ることを許可する」にチェックが入っていること。そうでなければ最初のACL追加APIは下記の非常に分かりやすい例外を吐いて失敗する。Unknown errorじゃないので助かる。
com.google.gdata.util.InvalidEntryException: The administrator for {target domain} has disabled the ability to receive docs from outside their domain. If those you're attempting to share with would like more information, they should contact their domain administrator directly.
2.配布元は「共有できます」にチェックがある上に「ユーザーがドキュメントをウェブに公開したり、一般公開や限定公開のドキュメントとして一般に公開したりできるようにします。」もチェックが必要。
ちなみにコピー作成時間はドキュメントのサイズに比例するので、GAEでこれをやるのであればURLフェッチの10秒タイムアウトなんてすぐに到達する。タスクキューとメール通知で回避するのがヨサゲ。
*1:ひょっとしたら他に理由があるのかもしれない。ここで記述するのはあくまで少ないサンプルでの経験則。
Apps Scriptのトリガの依存関係
トリガの依存関係というかプロジェクト(スクリプト)*1の依存関係が分かりにくすぎるので整理する。
まずはTriggersメニューから。
「現在プロジェクトのトリガー」と「全てのトリガー」があるが、後者の「全てのトリガー」とは、開いているスプレッドシートに紐付いている全てのプロジェクトを指している。そのスプレッドシートにしかないものもあれば、ギャラリーから取得できる複製もある。
スプレッドシートの「コピーを作成」すると、全てのプロジェクトもコピーが作成される。なので、複数のスプレッドシートから1つのプロジェクトを参照することはできない*2。
次に、トリガー設定画面上に表示される、各トリガの話。
まずはシンプルイベント(onOpen,onEdit,onInstall)から。スプレッドシートから起動時に実行されるトリガーとしていて依存関係としてはこのトリガの所有者はプロジェクト自身になる。onOpenで言えば、「onOpenというメソッド名がある」ことが条件で自動的に追加される。現在プロジェクトのトリガだろうと、全てのプロジェクトのトリガだろうと関係なく、いくつものプロジェクトにonOpenがあっても1行だけ操作できない状態で表示される。onOpenが定義されていないプロジェクトからは「現在のプロジェクトのトリガー」でも「全てのトリガー」でも何も表示されない。実行コンテキスト上の権限は実際に開いたり編集したりインストールしたユーザーのアクセス者権限で行われる。実行者権限はスクリプトを書いたオーナーになる。しかしOAuthの承認などアクセス権をもらいたいコードの場合、スクリプトマネージャからの直接実行しないと「承認が必要です。」と言われるだけでケアしてくれない*3。
時刻主導型トリガは、ユーザーに紐付けられる。エラー時の通知先もアクセス中のユーザー宛になる。このためシンプルイベントのようにスクリプトが独立して動作できる定周期のトリガが作れない。
これもスプレッドシートの「コピーの作成」で失われる。シンプルイベント以外の手動で追加したトリガーはどれもコピーされない。残念。
スプレッドシートで本当に隠しておきたいもの付きで配布
スプレッドシートに共同編集者を設定はしたいがオーナー以外には隠しておきたい情報がある場合、スクリプトプロジェクトの共有を「ドキュメントの共同編集者にプロジェクトの編集を許可する」のチェックは付けないようにしておく。
こうするとオーナー以外はスクリプトエディタを開いて[ファイル]-[プロパティ]を開いても、「プロジェクトのプロパティ」を見ることができない。「ユーザープロパティ」しか見えない。この情報はコード内から、ScriptPropertiesサービスで参照できるのでスクリプトコードにハードコードしないようにすれば良さげ。例えば、なかじまんさんがApps Scriptで2Legged OAuthを実現させていたので、consumerKeyとconsumerSecretをScriptPropertiesに保存しておくのが良い使い方かも。
…と思ってたんだけど、複製したらプロパティの内容は消失することが判明。残念。
コピーを作成で複製すると、プロジェクトも一緒に複製されるので、リードオンリーだった共同編集者も複製したものであれば編集できるようになる*1。
サービス公開を有効にして外からプロパテイを流しこむという手もあるんだけど、「サービス公開を有効に」するAPIが無さそう。やりたいことはDocument List APIで複製したスプレッドシートに対してプログラムから操作したいので...
ちなみにサービスとして公開というのは、doGet(e) をメソッドして定義しておけば呼び出してくれるというもの。こちらが参考になった。本家はこちら。
しかし実行してみたら...オワタ
Google ドキュメントでサーバー エラーが発生しました。ページをリロードしても解決しない場合は、お問い合わせください。
この問題やその他の問題についてディスカッションするには、Google ドキュメント ヘルプフォーラムにアクセスしてください。報告されている問題のリストを確認するには、Google ドキュメントの報告されている問題ページをご覧ください。
ご不便をおかけして申し訳ありません。
- Google Docs チーム
追記(9/8)
…って、違う違う。終わってない。ScriptPropertiesサービスを触るコードを書いたら上記のエラー画面になっただけでした。そういう意味では、サーブレットとして実行する場合に利用出来るサービスが何なのかが分からないと辛いですねー。