2010-11-20

GData Objective-C Client Library を組み込む (MacBloggerGlass)

Blogger Glass を Cocoa アプリとして作り直すにあたり、UI を考える前に、まずは GData Objective-C Client Library (以下、GData ライブラリ)を使って Blogger で作ったブログのデータ(Atom フィード)を取得できるようにしておきたい。Blogger Glass を作り始めたときも、最初は(GData ライブラリは使っていなかったが)そこから始めたのだった(→「Blogger で作ったブログの記事一覧を作成する」)。

GData ライブラリを Cocoa アプリ(または Cocoa Touch アプリ)に組み込むには、フレームワークとして(動的に)リンクする方法と、静的ライブラリとしてリンクする方法の二通りがある(→「Adding the Google Data APIs to a Project」)。Cocoa アプリの場合は主に前者を使い、Cocoa Touch アプリなら後者を使う。

では、GData ライブラリをフレームワークとして動的にアプリに組み込むには、Xcode プロジェクトをどう構成すれば良いのだろうか?

1 つの方法は、アプリとは別個にフレームワークをビルドして ~/Library/Frameworks 等に置くことだ。別の方法として、アプリのプロジェクトから GData ライブラリのプロジェクトを参照する方法がある。こちらが、GData ライブラリの配布パッケージにふくまれているサンプルアプリの使っている方法だ。

Cocoa 版 Blogger Glass (MacBloggerGlass と呼ぶことにした)でも、プロジェクトを参照する方法を使うことにした。で、今回の記事では、GData ライブラリのプロジェクトを(サブプロジェクトとして)ふくむ Cocoa アプリプロジェクトの構成手順を説明する。

ただし、以下に述べる手順は試行錯誤の末にたどりついたものなので、この先、アプリの開発を進めていくと何か不都合が出てこないとも限らない。現状、確認できているのは (a) アプリをビルドすれば同時に GData.framework がアプリのビルド構成(Debug または Release)に合わせてビルドされること、(b) アプリのコードから GData.framework の機能を使うことができること、(3) アプリのデバッグ中にステップ実行でフレームワークのソースに入っていけること、の 3 点になる。

手順

概略をまず述べる。GData ライブラリのプロジェクト一式をアプリのプロジェクトにコピーする。その後、Xcode でアプリのプロジェクトに GData プロジェクトを追加し、依存関係の追加やら、ビルドスクリプトの追加やらの調整を行う。お手本にしたのは、GData ライブラリ配布パッケージのサンプル。ビルドスクリプトもそこから拝借してきた。

MacBloggerGlass/
+-- MacBloggerGlass.xcodeproj
|   ...
+-- GData/
    +-- GData.xcodeproj
    |   ...

以下に、ステップごとの手順を示す。

  1. GData Library の配布パッケージを展開する。
  2. Srouce ディレクトリをアプリプロジェクトのディレクトリに GData という名前で移動する。
  3. Xcode でアプリプロジェクトを開く。
  4. メニューから「プロジェクト」>「プロジェクトに追加…」を実行する。
  5. ファイル選択パネルからアプリプロジェクトフォルダ内にある GData フォルダを選び、GData.xcodeproj を選択し、「追加」ボタンを押す。
  6. 「追加したフォルダにフォルダ参照を作成する」を選び、「追加」ボタンを押す。
  7. 「ターゲット」からアプリを選択し、メニューから「ファイル」>「情報を見る」を実行する(⌘ + I でも良いし、ツールバーに「情報」ボタンがあるならそれでも良し)。
  8. 「"ターゲット "(アプリ名)""の情報」パネルが開くので、「一般」タブを開き「直接依存関係」に「GData.xcodeproj」の「GDataFramework」を追加する。
  9. buildCopyScipt を用意する。GData パッケージに付属のものを修正して使う。
  10. メニューから「プロジェクト」>「新規ビルドフェーズ」>「新規スクリプトを実行」を実行する。
  11. 「""(アプリ名)"のスクリプトを実行するフェーズ"の情報」パネルが開くので、「一般」タブで「スクリプト」に「./buildCopyScript」と書き込む。
  12. buildStripHeaders を用意する。GData パッケージに付属のものを修正して使う。
  13. buildStripHeaders スクリプトについても buildCopyScript と同様の手順で、ビルドフェーズとして追加する。
  14. 追加した 2 つのスクリプト実行フェーズの順序を調整する。buildCopyScript は「バンドルリソースをコピー」の前、buildStripHeaders は「バイナリをライブラリにリンク」の後に移動する。
  15. グループとファイルの「GData.xcodeproj」を開き「GData.framework」を、ターゲットの「バイナリをライブラリにリンク」までドラッグしてそこのドロップ。Xcode のウィンドウレイアウトとして「コンデンス」を選んでいる環境なら、この操作は「詳細」ウィンドウを使うとやりやすい(メニューから「表示」>「詳細」を実行すると表示される)。
  16. GData.xcodeproj をダブルクリックするなどして、GData プロジェクトを開き、ビルドオプション等を調整する。とくに、ビルドするアーキテクチャを調整しておかないとリンク時にエラーが出る。双方のアーキテクチャを一致させておけば間違いはない(64-bit intel とかに)。
  17. ビルドを実行。エラーが出ていないことを確認する。
  18. アプリプロジェクトフォルダ内の build/Debug あるいは build/Release に 「アプリ名.app」と「GData.framework」が出来ていれば OK。

正直メンドウだったので、雛形になるプロジェクトを(Cocoa アプリ用、Cocoa Touch アプリ用それぞれに)作っておこうか、と考えている。いっそのこと Xcode のプロジェクトテンプレートにできないかなとも思うが、そのためにはフレームワークを別途ビルドしてシステムにインストールする必要がある(ように思う)。雛形プロジェクトの方が簡単かな。やり方もわかっていることだし。

関連リンク

関連記事

追記@2010-11-30

Google App Engine 用アプリに Python 版 Google Data Client Library を組み込むことについては以下の記事を参照のこと。

2010-11-19

#pragma mark - Xcode のテキストエディタの使い方 #1

Xcode で Mac 用の Cocoa アプリや iOS 用の Cocoa Touch アプリのプロジェクトを作成すると、いくつかの Objective-C のソースファイルが生成される。これらのソースを開くと #pragma mark という記述が書かれていることがある。これは何なのか?

調べて(ググって)みたところ、Xcode のエディタで使われる情報だとわかった。具体的には、Xcode のエディタにある Function Menu の表示を見易くするためのものだった。この Function Menu はエディタの(ウィンドウでもペインでも)上部に置かれた一群の便利機能(Navigation Bar と呼ばれている)の一つで、ソースコード中からクラスや関数、メソッドの宣言、定義を見つけ出し、一覧にしてくれるものだ。メニューの項目を選ぶとエディタで該当する場所に飛ぶことができる。簡易クラスブラウザと言ったところか。

以下は、iPhone アプリのプロジェクトを作った際に生成されるソースファイルから、#pragma mark 部分だけを抜粋したものだ。

@implementation RootViewController


#pragma mark -
#pragma mark View lifecycle

[...snip...]

#pragma mark -
#pragma mark Table view data source

[...snip...]

#pragma mark -
#pragma mark Memory management

[...snip...]

@end

このファイルを Xcode のエディタで開き、Function Menu を表示させたスクショが→になる。元のソースと見比べてみると、#pragma mark がどういう風に使われるのかがおおよそわかる。

  • #pragma mark に「-」を付けると、Function Menu 中の区切り線になる。メニューから選択できない(該当する場所に飛べない)。
  • #pragma mark の後に付けたテキストが Function Menu 中のラベルになる。メニューから選択できる(飛べる)。

Xcode Workspace Guide: The Text Editor」によれば、この Function Menu で項目として表示できるのはこれだけじゃない。以下のようなコメントも表示させることができる。もちろん、メニューから選んで飛べる。

  • MARK:
  • TODO:
  • FIXME:
  • !!!:
  • ???:

「!!!:」と「???:」はこのマークだけで項目として有効になるが、他の 3 つはマークの後にテキストを付けた場合のみ有効になる。つまり「TODO:」や「FIXME:」だけでは Function Menu に表示されない。「TODO: put code to fetch the URL.」のように書く必要がある。

先に挙げたドキュメントによれば、Function Menu が機能するのは C、Objective-C、C++、Java、Perl、Python、および Ruby に対してのみだとのこと。

近頃の IDE はなんとなく使っていてもそれなりに使えてしまうから、何か困ったことにでも陥らない限りヘルプやドキュメントを読もうと思わない。けれど、それだと便利な機能のことを知らないままになってしまうのだろう。今回の #pragma mark と Function Menu のことも、調べて(ググって)みて初めて知ったわけだし。

ともあれ、Xcode で開発をするなら、エディタに関する「Xcode Workspace Guide: The Text Editor」は、ぜひとも読むべきだ。

関連リンク

関連記事

twitter より (2010-11-18)

  • 19:38  そろそろかな。iOS アップデート。iTunes のアップデートが来たし。→
Powered by twtr2src.

2010-11-18

欲しいのはオフライン機能 - Cocoa アプリとして作り直す #0 (Blogger Glass)

Blogger Glass も、内部リンクの置き換えができるようになったことで、目標であった「Blogger で作ったブログを Blogger とは独立した表示システムで見る」に到達したと言える。もちろん、あれこれ気になる点は残っているし、実現したいアイデアもある。けれど、ここらでちょっと別方向に進んでみようかと考えている。それは、Cocoa アプリとして作り直すこと、つまり Objective-C で書く。

Blogger Glass を Cocoa アプリにすることで実現したいことは、何よりオフラインで記事を読めるようにすることだ。(iMac のような)デスクトップ型の Mac で動かすアプリならオフライン機能にほとんど意味はない。しかし、MacBook や iPhone のように外に持ち出すことの多い機器で動かすなら、オフライン状態に対応することはユーザ体験を向上させる。

まずは Mac 版を作り、その後 iPhone アプリに作り直す。ユーザ体験やデザイン(意匠)はともかく、設計と実装は共通化したいものだけど、作る前から欲張るのはやめておこう。まずは動くモノを作ることだ。

という思いのもと、今日から Xcode との格闘を始めたのだけど、以前、ヒレガス本で勉強したことをすっかり忘れてしまっていることがわかった。もう、どこに何を書けば良いのかがさっぱりわからなくなっている。

GAE 版の進化の跡をたどるようにして作るかな。つまり、こんな感じ。

  1. 一覧表示
  2. Google Data API Objective-C Client Library の組み込み
  3. 記事表示
  4. ラベル検索
  5. 内部リンクの置き換え

オフライン機能(つまり取得したフィードの保存)はどこで実現するか。「内部リンクの置き換え」の前あたりが適当だろう。「内部リンクの置き換え」ではひもづけ情報の保存が必要なのだから。

今日の感じだと、「一覧表示」を曲がりなりにでも Cocoa アプリとして実現する最初のステップが、実は一番時間がかかるかもな。とりあえずリクエストハンドラ(だけ)を書けば、一応のアプリとして動かすことのできる GAE とは違うわ。

ま、今日は予告だけ。

関連リンク

関連記事

twitter より (2010-11-17)

Powered by twtr2src.

2010-11-17

タスクキューを使う - 内部リンクを置き換える #3 (Blogger Glass)

内部リンクの置き換えをデータストアを使って単純に実装すると、「一覧画面」表示リクエストの処理に時間がかかるようになってしまい(データストアへの書き込みが発生するときのみ)、GAE の管理コンソール(の Current Load)に赤文字が出るようになってしまった(→「CPU 使い過ぎ? - GAE 管理コンソール上の警告」参照)。

今回は、GAE のタスクキュー API を使って「一覧画面」リクエストの処理時間を短くすることに挑戦してみた。

タスクキュー API の使い方

GAE でタスクキュー API を利用して何らかの処理を行う際には以下の 3 つが必要になる。

  1. タスクを登録するキューの設定
  2. タスクを処理するハンドラの定義
  3. タスクを登録するコードの追加

以下で、これらを順番に今回の実装を例に説明する。ただし、公式ドキュメントにもあるように、タスクキュー API はまだ experimental 扱いなので、この先、仕様変更の可能性は高い。ここに書かれている内容もいつまでも正しいとは限らない。

タスクを登録するキューの設定

GAE のタスクキュー API で使用するキューには、名前(name)、処理速度(rate)、実行量(bucket_size) を設定することができる。詳細については「Python Task Queue Configuration」を参照。設定は queue.yaml という名前の YAML ファイルに記述する。1 つの GAE アプリが持つことのできるキューは 10 までとなっている。

タスクキューにはデフォルトキューが用意されており、これを(デフォルトの設定のままで)使う分には特に設定しなくて良い(つまり、queue.yaml を作らなくても良い)。以下にデフォルトキューの(デフォルトの)設定を示す。

Default Queue Settings
項目設定値
name default
rate 5/s
bucket_size 5

簡単に言うと、デフォルトキューでは 1 秒間に 5 つのタスクを処理するようになっている。

今回の実装では、デフォルトキューをデフォルトのまま使っている。このため queue.yaml は作っていない。

タスクを処理するハンドラの定義

タスクを処理するコードは、通常のリクエストハンドラと同様に定義する(google.appengine.ext.webapp.RequestHandler を継承)。タスク登録時に指定しない限り、タスクは HTTP の POST メソッドで処理される。よって、タスクのハンドラでは最低限 post() 関数を定義することになる。

また、他のリクエストハンドラと同様に app.yaml にも登録する。以下は今回の実装で追加した定義だ。login: admin はリクエスト実行にアプリ管理者の権限を要求する設定。こうすることで、ユーザがタスクハンドラを直接実行することを防ぐことができる(タスクハンドラを呼び出すタスクキューは管理者権限を持っている)。

- url: /store_pol
  script: store_pol.py
  login: admin

今回、実装したタスクハンドラは以下の通り。リクエストパラメータとして permalink と Blog ID を受け取り、さらにひもづけられた Post ID を memcache から取り出して、データストアに保存している。また、データストアへの書き込みはトランザクションとして実行するようになっている。これはタスクが並列で処理されるからだ。

model.bind_post_id_with_permalink() 関数は以前に示したものから変わっていない。

# store_pol.py: process tasks to store a pair of links to the data store.

from google.appengine.api import memcache
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

import model
import util

class StorePolHandler(webapp.RequestHandler):
    def post(self):
        permalink = self.request.get('permalink')
        blog_id = self.request.get('blog_id')
        post_id = util.get_post_id_from_memcache(permalink, blog_id)
        if post_id:
            def txn():
                model.bind_post_id_with_permalink(permalink, blog_id, post_id)
            db.run_in_transaction(txn)

def main():
    application = webapp.WSGIApplication([('/store_pol', StorePolHandler)],
                                         debug=True)
    run_wsgi_app(application)


if __name__ == '__main__':
    main()
タスクを登録するコードの追加

タスクを登録するには google.appengine.api.labs.taskqueue.add() 関数を使う(デフォルトキューの場合)。今回の実装では、permalink と Post ID の「ひもづけ情報」を登録する関数にこれを組み込んでいる(src/util.py 185 〜 186行目)。

def register_pair_of_links(entry):
    """
    Register a binding of a permalink and a post_id.
    The registration won't change, since the binding must be permanent.
    """
    permalink = entry.get_html_link().href
    blog_id = entry.get_blog_id()
    post_id = entry.get_post_id()
    # save the pair into the memcache, if it hasn't been stored.
    if memcache.get(permalink) is None:
        memcache.add(permalink, (blog_id, post_id))
    # issue a task, if the pair hasn't been stored yet in the data store.
    if model.get_post_id(permalink, blog_id) is None:
        taskqueue.add(url='/store_pol',
                      params={'permalink': permalink, 'blog_id': blog_id})

引数 url がタスクを処理するハンドラの URL 、params はハンドラに渡されるパラメータだ。また、URL は通常のリクエスト URL と同様、app.yaml に定義したタスクハンドラの URL と一致していなければならない。

結果はどう?

上記のバージョンを GAE に配備して動かしてみたところ、「一覧画面」の表示にかかる時間は確かに改善された。管理コンソールの赤文字も消えた。実装を(少し)複雑にしただけの効果はあったようだ。

結局、GAE で実用的なアプリを作るには、memcache - taskqueue - datastore の三段構えは必須なのだ。だからこそ、標準で用意されているのだろう(taskqueue はまだ experimental となっているけど)。

参考文献

Programming Google App Engine
Dan Sanderson
Oreilly & Associates Inc ( 2009-11-15 )
ISBN: 9780596522728

関連リンク

関連記事

twitter より (2010-11-16)

  • 09:42  へえ〜。ついに解禁か。 RT @touch_lab: アップル、今晩24時にiTunesに関する特別な発表を予告〜ついに「ビートルズ」の配信を開始!? - http://bit.ly/bS6EQe
Powered by twtr2src.

2010-11-16

CPU 使い過ぎ? - GAE 管理コンソール上の警告 (Blogger Glass)

管理コンソールに赤文字が!

前回(→「内部リンクを置き換える #2」)の実装を配備した後、管理コンソールを見に行くと、Dasshboard の Current Load のところに以下のスクショのように赤文字が出ていた。

GAE Admin Console
Dashboard の Current Load
「Avg. CUP (API)」が赤文字で表示されている。 黄色三角上にマウスカーソルを持っていくと「This URI uses a high amount of CPU and may soon exceed its quota.」という警告が出てくる。
Logs
Current Load で赤字になった URI をクリックすると Logs が開く。 cpu_ms と api_cpu_ms を使い過ぎているということらしい。

「/」というリクエスト URI の処理に時間がかかりすぎているようだ。この「/」は、「一覧」画面を表示するためのもので、つまりは「内部リンクを置き換える」機能の実装で変更を加えた部分でもある。「一覧」を作るために取得したフィードから、permalink と Post ID を取り出し、そのひもづけ情報をデータストアに書き出すという処理が付け加えられている。この処理に時間がかかっているようだ。

Quota Details で確認すると……

管理コンソールの「Quota Details」を開きリソースごとの「Daily Quota」を確認すると、どの項目も 0% のまま。「Rate」もすべて Okay となっている。リソースの割り当てを使い切りそうということではない。

困ったときはググってみる

何度かググってみて、Google グループの Google-App-Engine-Japan で関連しそうな情報を見つけた(→「ダッシュボードの「Current Load」の「Avg CPU (API)」が赤文字になる」)。これによれば、この赤文字はさほど気にする必要はないようだ。ただ、平均リクエスト処理に 1 秒以上かかるアプリには同時リクエスト数が 30 までという上限が科せられる、という点は覚えておいた方が良いかも。

GAE ではアプリへのリクエストがしばらく途絶えるとアプリのインスタンスが消される。その後にリクエストが届くと、アプリインスタンスを再生成することになり、このときは(インスタンスが存在するときにくらべると)処理時間がかかる。ログを見ていると、このアプリインスタンス再生成をともなったリクエスト処理のときにも警告が出ている。気付いていなかっただけで、単発の警告はこれまでも出ていたってことだ。これだけでどうにかなるなら、とっくになっている。

設計変更が必要か?

Current Load で赤文字が出るのは、「一覧」画面のリクエストで permalink と Post ID のひもづけ情報をデータストアに保存したときだけだ。一度保存してしまえば 2 回目以降のリクエストでは出なくなる。つまり、赤文字の警告が出るような状況は頻繁には起こらない。

仮に、「設定」画面等に「ひもづけ情報の保存」のためのボタンを設け、ユーザが明示的に指示するようにしたとしても、そのリクエストの処理ではやはり赤文字が出ることになるだろう。

データストアの呼び出しを減らすのは効果があるだろうか? 今の設計/実装では、「一覧」画面のリクエストでは一回につき最大 25 回のデータストアの書き込みが起きる(ページごとの記事数が 25 なので)。データモデルが以下のようになっているから(src/model.py より)、記事の数だけデータストアに書き込むことになる。

class PairOfLinks(db.Model):
    """
    PairOfLinks binds a permalink to a post_id of a blog specified
    with a blog_id.
    Each entity of PairOfLinks must be created specifying a permalink
    as its key.
    """
    blog_id = db.StringProperty()
    post_id = db.StringProperty()

これを例えば一ヶ月単位でまとめて書き込むことにすれば、1 度のリクエストでせいぜい 2 回の書き込みで済む。書き込むデータの総量は変わらないものの、API 呼び出しの回数が 25 回から 2 回に減れば効果はあるかもしれない。その分、前処理やら後処理が複雑になるけれど。

保存先をデータストアから memcache に変えるのは有効だろうか? 処理時間は確実に短くなるはず。その代わりに「ひもづけ情報」が揮発性になってしまう。

そこで登場するのが Task Queue だ。

Programming Google App Engine」、13. Task Queues and Scheduled Tasks より
Enqueueing a task is fast, about three times faster than writing to the datestore. [...snip...] For example, an app can write a value to the memcache, then enqueue a task to persist that value to the datastore. This saves time during the user request, [...snip...]

ここに挙げられている例がぴったり当てはまる。ユーザが「一覧」と「記事」画面を行ったり来たりしている間は memcache が有効だし、セッションが終了または長く中断する間に Task で「ひもづけ情報」がデータストアに書き出されるなら、次回のセッションにはデータストアから情報が取り出せるはず。

memcache、Task、データストア、という三段構えで対応するように実装してみようか。そのためには、Task と Queue について調べないと。

追記@2010-11-30

GAE の Task と Queue の使い方および、それを使った Blogger Glass の変更については以下の記事を参照のこと。

参考文献

Programming Google App Engine
Dan Sanderson
Oreilly & Associates Inc ( 2009-11-15 )
ISBN: 9780596522728

関連リンク

関連記事

twitter より (2010-11-15)

Powered by twtr2src.

2010-11-15

内部リンクを置き換える #2 (Blogger Glass)

#1 のデザイン(設計)にしたがい、最小限の機能を実装できた。

実装

permalink と Post ID をひもづける

ひもづけ情報としてデータストアに保存するのは、(1) permalink、(2) Blog ID、(3) Post ID の 3 つ。このうち、(1) をデータ実体へのキーに使う。というのも、この情報を利用するときには permalink に一致する Post ID を引き出すことになるからだ。permalink をキーにしておけばデータストアに問い合わせるクエリを組み立てる必要がない。

以下が、「ひもづけ情報」を保存するデータ実体の定義と、それを利用するための関数だ(src/model.py)。

class PairOfLinks(db.Model):
    """
    PairOfLinks binds a permalink to a post_id of a blog specified
    with a blog_id.
    Each entity of PairOfLinks must be created specifying a permalink
    as its key.
    """
    blog_id = db.StringProperty()
    post_id = db.StringProperty()

def get_post_id(permalink):
    post_id = None
    pol = db.get(db.Key.from_path('PairOfLinks', permalink))
    if pol:
        post_id = pol.post_id
    return post_id

def bind_post_id_with_permalink(permalink, blog_id, post_id):
    pol = db.get(db.Key.from_path('PairOfLinks', permalink))
    if not pol:                 # new bind
        pol = PairOfLinks(key_name=permalink)
        pol.blog_id = blog_id
        pol.post_id = post_id
        pol.put()

get_post_id 関数は permalink にひもづけた Post ID を取り出すためのもの。記事のテキストを置換する際に用いることを想定している。permalink に対してひもづけられた Post ID がないときには None を返す。

bind_post_id_with_permalink 関数は permalink と Post ID のひもづけを保存するもの。「一覧」系の画面を作る際に用いることを想定している。指定された permalink に対して、すでにひもづけが存在した場合には何もしない。これは、permalink と Post ID の組み合わせが変わることはない、という前提に立つもの。Blogger の場合、記事をポストした後にこの 2 つを変更する手段がないから、この前提が成り立つ。

ひもづけを生成する部分のコードは以下のようになる(src/listview.py)。

        feed = util.get_posts(q)
        if feed:
            [...snip...]
            for entry in feed.entry:
                util.regist_pair_of_links(entry)

実際に、ひもづけ情報を保存する regist_pair_of_links 関数は util モジュールで定義している。上述の bind_post_id_with_permalink 関数を呼び出すだけのものだ。

def regist_pair_of_links(entry):
    model.bind_post_id_with_permalink(entry.get_html_link().href,
                                      entry.get_blog_id(),
                                      entry.get_post_id())

permalink へのリンクを置き換える

記事テキスト内のリンクの置換は以下の関数で行う(src/util.py)。

def replace_permalinks(original):
    pat = re.compile('<a[^>]*href=\"([^"]*)\"[^>]*>')
    new_text = original
    pos = 0
    while pos < len(new_text):
        m = pat.search(new_text, pos)
        if not m:
            break
        permalink = m.group(1)
        logging.info("Permlink: %s" % permalink)
        post_id = model.get_post_id(permalink)
        if post_id:
            postlink = "/post/?id=%s" % post_id
            new_text = new_text.replace(permalink, postlink)
            pos = m.end() - len(permalink) + len(postlink) + 1
        else:
            pos = m.end() + 1
    return new_text

正規表現を使い href 属性を持った a 要素を探し、href 属性の値を取り出す。その値を permalink としてひもづけられた Post ID が記録されていれば、それを Blogger Glass の「記事」画面へのリクエスト URL に置き換える。この処理はテキスト中から a 要素が見つからなくなるまで行う。

テキスト中を(正規表現で)探す際には、開始位置を pos で指定しているが、置換を実行した場合は次の開始位置がずれるため 185 行目で調整している。

これを呼び出す部分が src/postview.py 中にある。

    def fill_view_attrs(self, post_id):
        [...snip...]
        entry = client.get_one_post(settings.get('blog_id'), post_id)
        if entry:
            [...snip...]
            self.view.content = util.replace_permalinks(entry.content.text)

これで完成か?

#1 でも書いたように、データストアに保存したひもづけ情報を「いつ消すか」という問題が残っている。経過時間で切るなら保存した実体の中に記録した日時を保管しなければならない。加えて、「消す」作業を開始するトリガーをどうするかという問題もある。「設定」画面でユーザに明示的に消させるか。その場合、リンクの置き換えが機能するにはログインが前提となる。それでも構わないかな。

関連リンク

関連記事

twitter より (2010-11-14)

Powered by twtr2src.

2010-11-14

Apple TV、届いた!

届いたのは昨日(2010-11-13)の昼すぎ。ちょっと放置してしまっていたが、本日、夕方に設置完了。簡単に使ってみたので第一印象などを書き留めておく。

コンパクトなボディ

すでにあちこちで実際の写真が公開されているから十分想像はついたことだけど、実際、自分の手に取ってみると改めて実感する。コンパクトっていうより小さい。これで電源内蔵だからね。

(多少かぶる部分があるとはいえ)使う目的が同一ではないから比較することに意味はないけど、PS3 と比べると驚愕の一言。価格も大きさも(多分消費電力も)。

設置もカンタン

つなぐのは 2 ヶ所。TV との間を HDMI ケーブル(別売)で、コンセントの間を電源ケーブル(付属)で。ネットワークへの接続は有線または無線で。ウチでは TV のところまでネットワークケーブルを引いてハブを置いてあるから有線接続にした。

Time Capsule と同様電源スイッチはない。電源ケーブルをつなぐことがそのまま電源オンになる。電源オフしたければ電源ケーブルを抜くしかない。使わないときはスリープ状態にしておく(再生やその他操作をせずに一定時間が経過すると自動でスリープになる)。これ、知っていないと戸惑うかもね。

最初に電源を入れると TV 画面に言語選択の画面が表示される。「日本語」を選択すればメイン画面に移る(途中、Apple TV の動作に関するデータを Apple に送信しても良いかという選択画面が出る)。メイン画面のメニューは以下のようになっている。

Apple TV: Main Menu
項目名 機能
ムービー iTunes Store (映画)を開く。「上映中」は US の情報っぽい。
インターネット YouTube、Podcast、MobileMe、Flickr、ラジオ。
コンピュータ ホームシェアリングを有効にした Mac (や PC) の iTunes ライブラリを開き、音楽や動画を再生する。
設定 各種設定と「今すぐスリープ」

使ってみる (機能/サービス編)

設定

何はなくとも初期設定。設定の項目は以下の通り。AirPlay は iPhone や iPad 上のコンテンツを Apple TV (経由で TV) に映す機能だが、まだ iOS アプリがないので使えない。

  • 一般
  • スクリーンセーバ
  • オーディオとビデオ
  • AirPlay
  • コンピュータ
  • 今すぐスリープ

最小限の設定は「一般」>「iTunes Store」と進み、Apple ID で iTunes Store にサインインすること。これがなくては「ムービー」でレンタルできない。無線 LAN 接続の場合は、「一般」>「ネットワーク」で設定できる(付属「設定ガイド」p.16)。

さて、ときどきおかしな翻訳でユーザを困惑させてくれる Apple だが(→「すべての無効化を許可、ってどういう意味?」参照)、ここでもやってくれた。「一般」>「ペアレンタルコントロール」と進むと、初期状態で画面に表示されるのは「ペアレンタルコントロールは入」という表現だ。これを読んでどう思う? ペアレンタルコントロールは有効なのか、それとも無効なのか。

「ペアレンタルコントロールは入」という表現だと、これはペアレンタルコントロール機能の状態を表示しているのだ、と思うのではないだろうか? 少なくともわたしはそう思った。しかし、それは間違い。「……は入」と表示されているとき、この機能は無効になっている。実際、その下にあるペアレンタルコントロールの設定項目はグレーになっていて選ぶことができない。で、この項目を選んで押すと「……は切」に変わり(パスコードを設定させられる)、設定項目を選べるようになる。表記と機能が合っていないと思わないか?

英語環境にして同じ項目を表示させると、初期状態は「Turn On Parental Control」。これを押すと「Turn Off Parental Control」になる。そう、日本語の翻訳が悪いってことだ。この場合の「Turn On」と「Turn Off」は「入りにする」「切りにする」と動詞として訳さないと意味が通らない。もちろん「入りにする」「切りにする」という表現も普段目にするものではないから、ここは意訳で「有効にする」「無効にする」とでもすべきところだろう。

販売開始前にアップルジャパンの社員は一人も使ってみなかったのか?

ホームシェアリング

メインメニューのところにも書いたように、Apple TV では、ホームシェアリング(iTunes の機能の一つ)を有効にした Mac の iTunes ライブラリのコンテンツを再生できる(たぶん PC でも可)。LAN 内に Mac があるとメインメニューのコンピュータに表示され、それを選ぶことで、音楽はもちろん動画もポッドキャストも iTunes U も再生できる。もちろん、コンテンツを入れた(そしてホームシェアリングを有効にした) Mac が起動していなければ使えない(スリープもダメ)。

Mac mini Server にコンテンツを入れようかな。

ムービー(予告編)を観る

Apple TV を買った理由(の一つ)に「予告編(プレビュー)が長い(3 分)」ことを挙げたが(→「Apple TV、買った」)。早速、あれこれ予告編を観てみた。Mac 上の iTunes で観る場合と違って、Apple TV では TV 画面一杯にフルスクリーンで表示される。良い感じ。予告編をザッピングするだけでオモシロイよ。

ちょっと残念だったのは、すべての映画で 3 分表示されるわけじゃないってこと。1 分のものもあれば、1.5 分のものもある。

レンタルしてみる

これはまだ試していない。この記事をポストしたら何か一つレンタルしてみよう。

使ってみる (操作編)

本体付属のリモコン

付属のリモコンは製品版の Remote と同じように見える。ということは、Mac の操作(Front Row とか iTunes とか)もこれで可能なはず。実際、iMac でリモコンを有効にしてみると(普段は無効にしてある; 「システム環境設定」>「セキュリティ」>「一般」>「リモートコントロール赤外線レシーバーを無効にする」)、「Menu」ボタンで Front Row が起動するし、iTunes の操作もできた。

また、古いタイプの Remote (→) でも Apple TV の操作ができた。

このリモコンは普段の操作に限れば問題はないんだけれど、設定のときなどにアカウント名やパスワードを入力する際にはやはり使いにくいと感じる。上下左右で文字を選ぶのはシンドイよ。ただ、キー入力画面(ソフトウェアキーボードの一種かな)で、小文字のアルファベットだけでなく、同時に大文字のアルファベットも表示されているのは評価できる。下手に QWERTY 型の配列を表示してシフトで大文字小文字を切り替えるっていう方式にしたら使いにくさを増すばかりだからね。

iPhone アプリ(Remote)

おもしろいところでは、iOS アプリにも「Remote」があること。ただ、純粋にリモコンをエミュレートするだけのようで、設定の入力なんかはできない。文字入力に iPhone (や iPad) のソフトウェアキーボードが使えたら良かったのに。

訂正@2010-11-16:
iPhone/iPad アプリの Remote で文字入力にソフトウェアキーボードが使えないというのは勘違いだった。Remote を使って操作すると、アカウントやパスワードの入力に加えて、検索画面でも iPhone (や iPad) 上のソフトウェアキーボードで入力できる。これを使うと検索画面で日本語も指定できるようになる。
コメントで指摘してくださった gakkey さんに感謝!

関連リンク

関連記事