Rss to PocketというWEBサービスを公開しました

Rss to Pocket
http://rsspocket.appspot.com/

出来ること

  1. RSSを登録しておくと、Pocketに自動的に記事が追加されます
  2. RSS毎に「タグ」を付けられるので、Pocket内で整理しやすい
  3. Google Readerからエクスポートしたファイルを読み込める

チュートリアル

  1. Googleのアカウントでログインします
  2. Pocketの認証を行います
  3. RSSを登録します
  4. あとは勝手にPocketに追加されていきます

雑記

本当は、Google Readerが停止する前に公開したかったんですが、気力が足りずに遅れてしまいました。
現状はタグの編集など、必須と言っていいぐらい機能すら実装出来てないので、随時更新していきます。

NeoBundleを有効にすると、シンタックスハイライトが無効になる

環境
Mac OS X 10.8.4
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Jun 20 2012 13:16:02)


最初は、下記のように書いていた。

syntax on

set nocompatible
if has('vim_starting')
   set runtimepath+=~/.vim/bundle/neobundle.vim/
endif

call neobundle#rc(expand('~/.vim/bundle/'))


で、最終行をコメントアウトすると、シンタックスハイライトが有効になるのに、
最終行を有効にすると、色がつかない。


結論:syntax on を最後に持ってくる
下記のように変更したら、色がついた。

set nocompatible
if has('vim_starting')
   set runtimepath+=~/.vim/bundle/neobundle.vim/
endif

call neobundle#rc(expand('~/.vim/bundle/'))

syntax on

主キーを任意のカラムに変更する

slim3のデータクラスは、Keyクラスでなければならないようです。
https://sites.google.com/site/slim3documentja/documents/slim3-datastore/defining-data-classes

でも、ユーザの情報を格納するデータクラスはemailアドレスにしたいですし、emailアドレスで検索したいです。
いろいろやり方はあるようです。
http://d.hatena.ne.jp/higayasuo/20091111/1257905482
http://www.tdtsh.com/blog/archives/759
が、今回はユーザに紐づく情報(同一タイミングで別の更新が入ることを考慮しない)こともあり、てきとーに組んでみました。

package hm.orz.chaos114.gae.rsspocket.dao;

import hm.orz.chaos114.gae.rsspocket.model.UserInfo;

import java.util.List;
import java.util.concurrent.Future;

import org.slim3.datastore.DaoBase;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.EntityNotFoundRuntimeException;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;

public class UserInfoDao extends DaoBase<UserInfo>{

    @Override
    public Key put(final UserInfo model) {
        setKeyIfNull(model);
        return super.put(model);
    }

    @Override
    public List<Key> put(final List<UserInfo> models) {
        // 利用しないため未実装
        throw new UnsupportedOperationException("未実装");
    }

    @Override
    public Future<Key> putAsync(final UserInfo model) {
        setKeyIfNull(model);
        return super.putAsync(model);
    }

    @Override
    public Future<List<Key>> putAsync(final List<UserInfo> models) {
        // 利用しないため未実装
        throw new UnsupportedOperationException("未実装");
    }

    /**
     * Userが一致するUserInfoオブジェクトを取得する。
     * 
     * @param user ユーザ情報
     * @return UserInfoオブジェクト
     */
    public UserInfo getByUser(final User user) {
        final Key key = createKey(user);
        try {
            return get(key);
        } catch (final EntityNotFoundRuntimeException e) {
            return null;
        }
    }

    /**
     * Keyが設定されていない場合は、設定されているUserからKeyを生成し、設定する。
     * 
     * @param userInfo ユーザ情報モデル
     */
    private void setKeyIfNull(final UserInfo userInfo) {
        if (userInfo.getKey() == null) {
            userInfo.setKey(createKey(userInfo.getUser()));
        }
    }

    private Key createKey(final User user) {
        return Datastore.createKey(UserInfo.class, user.getEmail());
    }
}

put系のメソッドをオーバーライドし、Userのemailより作成したKeyを設定しています。
また、getByUserにて、指定されたUserオブジェクトのemailよりKeyを作成し、取得しています。
(put系のListを引数にとるメソッドを利用する予定が無かったので、実行時に例外となるようにしてあります。使うことがあれば、実装する予定です。)


現在開発中のものより抜粋(https://github.com/noboru-i/rsspocket

Virtual BoxでJenkinsを動かす

前提:
Vagrant: 1.1.2 (公式サイトからダウンロード)
Chef: 11.4.0 (rubygemでインストール)
knife-solo: 0.3.0 (githubからcloneしてインストール)
Opscode Communityに登録済み・鍵を設定済み


※コンソールの出力結果を見ながら、思い出しながら書いているので、抜けがある可能性あり


※大問題:下記を実行後に、"vagrant halt"→"vagrant up"しても、Jenkinsにアクセス出来ない。
chefで実行されたが、起動時に実行していないことがある?


適当なフォルダでVagrant公式にあるスクリプトを実行

mkdir vm
cd vm
vagrant box add base http://files.vagrantup.com/lucid32.box
vagrant init


Vagrantファイルを少し修正し下記のようにする。(コメントアウト以外の箇所を抜粋)

Vagrant.configure("2") do |config|
  config.vm.box = "base"
  config.vm.network :private_network, ip: "192.168.50.11"
  config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--memory", "1024"]
  end
end

やっていることは、
・先ほどダウンロードしたubuntu(baseとラベリング)を利用する
・固定IPを設定
・メモリ使用量を1024MByteに設定


VMを起動する。また、sshで入れるようにしておく(ここでは"vm"という名前を設定)

vagrant up
vagrant ssh-config --host vm >> ~/.ssh/config


Chefリポジトリを作成する。

knife solo init chef-repo
cd chef-repo
git init
git add .
git commit


vm上にchefを準備する。

knife solo prepare vm

nodes/vm.json が作成されるので、gitにcommitしておく。


jenkinsのcookbookを追加して、"run_list"に"jenkins"を追加し、実行する。(途中gitのcommitが複数回走るが、とりあえず全部":wq"でそのまま続行)

knife cookbook site vendor jenkins
vim nodes/vm.json
knife solo cook vm

エラーになる。

Cookbook apt not found. If you're loading apt from another cookbook, make sure you configure the dependency in your metadata

include_recipe "apt" ってところでエラーになってるっぽいので、"apt"を落としてくる。

knife cookbook site vendor apt

そのままやると、vm.jsonを変更しているので

You have uncommitted changes to your cookbook repo

といわれる。一旦変更を破棄して、再度実行する。(commit or stashしてもよい)

knife cookbook site vendor apt


無事、"apt"が取り込めたので、再度cook。

knife solo cook vm

再度エラー。

Cannot find a resource for apt_repository on ubuntu version 10.04

今度は、"apt_repository"というResourceが見つからないと言っている。
とりあえず、エラーメッセージでググり、 https://github.com/mdxp/nodejs-cookbook/issues/16 を見つける。
コミットログを見ると、"metadata.rb"に「depends "apt"」と追記しているみたい。やってみる。

cookbooks/jenkins/metadata.rb に1行追加

--- a/cookbooks/jenkins/metadata.rb
+++ b/cookbooks/jenkins/metadata.rb
@@ -5,5 +5,7 @@ description      "Installs and configures Jenkins CI server & slaves"
 long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
 version          "0.6.3"
 
+depends "apt"
+
 %w(runit java).each { |cb| depends cb }
 %w(iptables yum apt).each { |cb| recommends cb }


再度実行。

knife solo cook vm

やっと成功。
最初に決めたIPで8080ポートにアクセス。
http://192.168.50.11:8080/
Jenkinsおじさんが見えれば完了。


Vagrant, chef solo, knife soloなどを利用して、Jenkinsサーバが作れました。
普通に構築したことが無いので、比較は出来ないのですが、思ったより簡単に出来ました。(罠はありましたが)
dependsを記述する場所が間違っている気がしますが、ホントはどこに書くものなんでしょう。。。
あと、最初に書いた大問題(再起動後に見えない)はどうしたらいいやら。。。

gruntを"-g"でインストールしたのに"command not found"

ブログ記事などを参考に、npmよりgruntをインストールしようとしました。
gruntコマンドを使いたかったので、"-g"を付け、下記のように実行しました。

npm install -g grunt

で、"grunt"と実行しても、"-bash: grunt: command not found"と表示されてしまう。
sudoでやっても結果は同じ。


結論:公式ページを見ましょう。
http://gruntjs.com/getting-started を見ると、パッケージ名が変更されていたようです。
なので、一旦アンインストールし(sudoでやっちゃってたので、sudoで)、"grunt-cli"をインストール。

sudo npm uninstall -g grunt
npm install -g grunt-cli


gruntをインストールするように書かれているブログ記事も多いので、覚え書き。

モデルのプロパティのAttributeに(cipher = true)をつけるとテストが失敗する現象への対応

gen-modelで生成したモデルオブジェクトに、

@Attribute(cipher = true)
private String accessToken;

を付け、appengine-web.xml

<system-properties>
	<property name="slim3.cipherGlobalKey" value="hogehogehogehoge"/>
</system-properties>

を追記すると、ブラウザから動作させた場合は普通に動作し、Datastoreにも暗号化されて保存されるが、EclipseよりRun as->JUnit Testとすると、下記の例外が発生した。

java.lang.IllegalStateException: A cipher key is required.

appengine-web.xmlを読み込んでいない?


とりあえず、該当のテストクラスにて、setUpメソッドを実装。

@Override
@Before
public void setUp() throws Exception {
    super.setUp();

    Datastore.setGlobalCipherKey("hogehogehogehoge");
}


ただしこの方法、大量のテストクラスに対して上記の対応が必要になります。(該当のモデルを利用するController, Serviceのテストクラスなど)
もっとスマートなやり方がありそう。


ちなみに、ktrwjrであれば、上記の対応は必要ないみたいです。
でも、Eclipse上で完結させたいなー。

checkoutしたタイミングで任意の処理を実行する

前置き

仕事場では、gruntが導入されており、その生成物はバージョン管理の対象外となっています。
そのため、他の人がjsを修正した際に、ブランチを切り替えたあと、"grunt build"を実行しないと、古いjsのまま動かしてしまうことが何度かありました。*1
基本的に、自分ではjsを触らないこともあり、チェックアウトしたタイミングで、自動的に再生成されるようにしました。

手順

まず、チェックアウトされたタイミングで実行したい処理を記述します。
今回は kyouen-python にあるcoffeescriptを自動的にjsに変換する処理を書いてみます。
フォルダ構成としては、
kyouen-python/src/coffee:coffeescriptが保存してある
kyouen-python/src/js:生成されたjsを保存する
といった感じです。
なので、スクリプトは下記のようになります。

cd src/coffee
coffee -c -o ../js list.coffee


これを、"post-checkout"として保存します。(カレントディレクトリは"kyouen-python"の想定)

cd .git/hooks/
echo "cd src/coffee" >> post-checkout
echo "coffee -c -o ../js list.coffee" >> post-checkout
chmod +x post-checkout

あとは、通常通りチェックアウトすると、自動的にlist.jsが生成されます。*2

git checkout feature-XXXX


参考にしたのは下記です。
http://git-scm.com/book/ja/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

雑感

"post-checkout"だけでなく、いろいろなフックがあるようです。
"pre-"とついたフックを書くことで、commitなどの行動に制約を課すことができるようです。(lintを実行し、エラーだったら拒否するとか)
チームで作業する際に、てきとーなコミットを許さない、とか出来そうですね。*3


.gitディレクトリ以下って、バージョン管理対象外ですよね?
この"post-checkout"ファイルはどうやって管理するのが普通なんだろう。。。

*1:本番・ステージング環境にデプロイするときはJenkinsのタスクに登録されています

*2:coffeeコマンドって、標準出力になにも出さないんですね。動いてるのかわからなかったorz

*3:でも、とりあえずコミットさせて、Jenkinsとかがフックして、違反があったらパトランプが回る、とかの方が面白いかなー

[Redis]Redisで作る更新通知

facebookの地球(?)のアイコンを押した時に表示されるようなものをイメージ。
自分の投稿に対して、いいねがついたことを通知する。

※コード書くのがめんどうだったので、redisのコマンドで記述してあります。

前提条件・要件

  • 元となるデータ(投稿、ユーザ、いいね)はRDBに管理されている
  • 新着件数、新着一覧を表示できる(ソート順は更新日時の降順)
  • 保持できる上限件数を99件とする(Redisのデータ容量を見積もることが可能)
  • 1つの投稿に対して、複数人のアクションをまとめて表示できる(○○さん他X人に・・・のような)

登録処理

  • user_id = 123の、

article_id = 234に、
user_id = 345が
2013/01/01 00:00:00(1356966000)にいいねを押したとき
→まず、既に存在しているかを確認する

HGET like:values:123 234

→emptyが返ってくるので、JSON文字列("{\"user_ids\":[345]}")を作成し、下記を実行

ZADD like:keys:123 1356966000 234
HSET like:values:123 234 "{\"user_ids\":[345]}"
ZADD like:new:123 1356966000 234
  • 同じ投稿に、

user_id = 456が、
2013/01/01 00:00:20(1356966020)にいいねを押したとき
→まず、存在確認

HGET like:values:123 234

→さっきのJSONが返却されるので、パースし、今回のuser_id(456)を追加する。
→そして、下記のコマンドを実行

ZADD like:keys:123 1356966020 234
HSET like:values:123 234 "{\"user_ids\":[345,456]}"
ZADD like:new:123 1356966020 234

同じユーザの、
article_id = 235に、
user_id = 345が
2013/01/01 00:00:40(1356966040)にいいねを押したとき
→まず、存在確認

HGET like:values:123 235

→emptyが返ってくるので、JSON文字列("{\"user_ids\":[345]}")を作成し、下記を実行

ZADD like:keys:123 1356966040 235
HSET like:values:123 235 "{\"user_ids\":[345]}"
ZADD like:new:123 1356966040 235

参照処理

  • 新着の件数を取得する(user_id=123)
ZCARD like:new:123
  • 更新通知を10件表示する(user_id=123)

→まず、キー一覧を取得する

ZREVRANGE like:keys:123 0 9

→取得できたキー(235, 234)について、値を取得する

HGET like:values:123 235
HGET like:values:123 234

RDBより、投稿・ユーザの情報を取得する

SELECT * FROM article WHERE id = 235;
SELECT * FROM user WHERE id = 456;
などなど
  • 新着更新通知を10件表示する(user_id=123)

→取得するキーが keys -> new を変えて、更新通知を10件表示するを実行する。

追記が必要なこと

  • 上限件数を越えようとした場合

ざっくり書くと、下記のような感じ。

    • 100件目以降のkeysを取得(WITHSCORES)
    • そのkeyで、valuesを削除する
    • keysの100件目のscore以下のnewを、ZREMRANGEBYSCOREを利用して削除する

multi -> exec を利用するべきかと。

書いた後気づいた

公式にtwitter cloneのcase studyがあった。
こっちを見たほうがいいんじゃなかろうか。
http://redis.io/topics/twitter-clone

CocoaPodsをインストールしてみた。

Mac OS X 10.8.2
Ruby関連は、アップデートはしてないはず。


基本的には下記を参考。
http://d.hatena.ne.jp/Watson/20111204/mac_dev_jp_advent_calendar_cocoapods01
なので、下記コマンドを実行。

sudo gem install cocoapods
略
Installing ri documentation for i18n-0.6.4...
Installing ri documentation for multi_json-1.6.1...
Installing ri documentation for activesupport-3.2.12...

unrecognized option `--encoding'

For help on options, try 'rdoc --help'

とか出てましたが、とりあえず気にせずに下記を実行。

pod --help

結果:

Your RubyGems version (1.3.6) is too old, please update with: `gem update --system`

ということで、言われた通り、下記を実行。

sudo gem update --system

すると、大量にログが出力されましたが、気にせず再度実行。

pod --help

なんか、それっぽいヘルプが出てきたので、下記を実行。

pod setup

すると、下記の出力。

Setting up CocoaPods master repo
Cloning spec repo `master' from `https://github.com/CocoaPods/Specs.git' (branch `master')

Cocoapods 0.17.0.rc4 is available.

Setup completed (read-only access)

とりあえず、セットアップは出来たらしい。

なので、TumeKyouen.xcodeproj が存在するディレクトリで

vim Podfile

で、内容に下記を記載。

platform :ios
dependency 'AFNetworking'


それでは、インストール

pod install TumeKyouen.xcodeproj

怒られた。

[!] Unrecognized argument: `TumeKyouen.xcodeproj'
pod install

とすると、

[DEPRECATED] `dependency' is deprecated (use `pod')
Resolving dependencies of `./Podfile'
Updating spec repositories

Cocoapods 0.17.0.rc4 is available.

Resolving dependencies for target `default' (iOS 4.3)
[!] AFNetworking (1.1.0) is not compatible with iOS 4.3.

との表示。

Podfileに

xcodeproj 'TumeKyouen.xcodeproj'

を追記し、

pod install

を実行しても、特に変化はなし。


で、PodfileのdependencyをAFNetworkingから、下記に変更。

dependency 'AdMob'

で、再度インストール

pod install

で、結果、

[DEPRECATED] `dependency' is deprecated (use `pod')
Resolving dependencies of `./Podfile'
Updating spec repositories

Cocoapods 0.17.0.rc4 is available.

Resolving dependencies for target `default' (iOS 4.3)
Downloading dependencies
Installing AdMob (6.3.0)
Generating support files

[!] From now on use `TumeKyouen.xcworkspace'.
Integrating `libPods.a' into target `TumeKyouen' of Xcode project `./TumeKyouen.xcodeproj'.

[!] The target `TumeKyouen [Debug - Release]' overrides the `OTHER_LDFLAGS' build setting defined in `Pods/Pods.xcconfig'.
    - Use the `$(inherited)' flag, or
    - Remove the build settings from the target.

あれ、通った?
とりあえず、

open TumeKyouen.xcworkspace

を実行したら、エラーになってしまって、よく考えると、TumeKyouen.xcodeproj を開いたままだったので、Xcodeを全て閉じ、再度

open TumeKyouen.xcworkspace

とすると、無事、開いて、ビルドも成功。
とりあえず、今日はここまで。

  • 3/16 追記

もともと入れていた、AdMobのリソースを削除したところ、コンパイルエラーになってしまった。
そこで、TumeKyouenプロジェクト -> Build Phases -> Link Binary With Libraries に、libGoogleAdMobAds.a を追加したところ、コンパイルが通るようになった。