Tumblr

pixiv RSS

googlecode のSVNをMavenリポジトリとして使ってみる

自鯖にもMavenリポジトリはあるけれども、多分GoogleCodeの方が色々と信頼出来るので。
以下を参考にGoogleCodeにデプロイしてみた。
Maven2DistributionManagementOnGooglecode - raisercostin - How to configure maven2 distributionManagement to upload release on googlecode.com . - Project Hosting on Google Code

現状以下のものをデプロイしている。
・gaej-quickstart
・gaej-wicket-quickstart
・datastore-map

gaej-quickstart、gaej-wicket-quickstartはshin1さんのgaejtoolsを使って作った自分用Archetype。
自分はMavenを依存解決にしか活用していないので、それだけ出来るようにしたシンプルな構成。
Mavenでごりごり開発したい場合はshin1さんのArchetypeを使いましょう。自分はなんでか上手く動いてくれません。
gaejsimplequickstart - appengine-hackathon-ja - Project Hosting on Google Code

datastore-mapは前回記事にしたもの。少し修正している。

修正した点
・Mapインタフェースを実装するのでは無く独自のインタフェースを用意し実装した
 → わざわざエラーを投げるぐらいなら最初からメソッドを見せなければ良い
・エンティティグループを分割した
 → トランザクションによる排他が頻発しないように(これは使い方による)
・CachedDatastoreMapの修正
 → キャッシュの仕方に問題があり異常動作していたため修正

以上。 引越しに伴いしばらく自鯖が使えなくなるので、開発に必要なデータだけバックアップしなければ…

DatastoreMapを修正してみた

DatastoreMap とは以下の記事で公開された GAE/J の Datastore を Map で扱うものです。
Google App EngineのDatastoreをMapでラップする - $koherent->diary
Google App Engine for Javaで、ちょっとしたデータを永続化するためだけにDatastoreを触るのは面倒です。そこで、Datastoreをjava.util.Mapでラッピングしたクラスを作ってみました。

簡単なコードを書くのに、low-level-api だとか JDO だとか slim3 なんかだと、煩雑だったり仰々しかったりするので、
Map程度だと扱いやすくて丁度良いのかもねぇ、などと思ってちょこちょこ触っていたのですが。

実際に中を見ると中々どうして、トランザクションの処理が怪しい。

@Override
public V put(K key, V value) throws ConcurrentModificationException {
ConcurrentModificationException exception;
int retryCount = 0;

do {
Transaction transaction = service.beginTransaction();

V oldValue = get(key);

try {
service.put(createEntity(key, value));
transaction.commit();

return oldValue;
} catch (ConcurrentModificationException e) {
if (transaction.isActive()) {
transaction.rollback();
}

exception = e;
}
} while (retryCount++ < numberOfRetries);

throw exception;
}
正しくは、[12行目] service.put(transaction, createEntity(key, value)); となっていなければならないハズ。

しかし、そのまま transaction を突っ込んでもダメだったり。
データはルートエンティティに保存されているので、トランザクション処理しようとすると怒られる。

後これは仕様なので仕方ないのだけれど、1メガ制限があるので1メガ超えるデータを突っ込むと当然怒られる。

ということで、トランザクションと1メガ制限超えの2点の修正をしてみた。mix3-appengine-map-1.0.zip

使い方は以下のような感じ
[実行]

CachedDatastoreMap<String, byte[]> map = new CachedDatastoreMap<String, byte[]>("ByteArray");
byte[] b = new byte[1024 * 1024 + 1];
byte[] c = new byte[1024 * 1024 * 3 + 1];
map.put("key1", b);
map.put("key2", c);
System.out.println(map.toString());
[結果]

<Entity [ByteArray("ByteArray")]:
>
<Entity [ByteArray("ByteArray")/ByteArray("key1[0]")]:
num = 2
name = key1
>
<Entity [ByteArray("ByteArray")/ByteArray("key1[1]")]:
value = <Blob: 1024000 bytes> (unindexed)
>
<Entity [ByteArray("ByteArray")/ByteArray("key1[2]")]:
value = <Blob: 24604 bytes> (unindexed)
>
<Entity [ByteArray("ByteArray")/ByteArray("key2[0]")]:
num = 4
name = key2
>
<Entity [ByteArray("ByteArray")/ByteArray("key2[1]")]:
value = <Blob: 1024000 bytes> (unindexed)
>
<Entity [ByteArray("ByteArray")/ByteArray("key2[2]")]:
value = <Blob: 1024000 bytes> (unindexed)
>
<Entity [ByteArray("ByteArray")/ByteArray("key2[3]")]:
value = <Blob: 1024000 bytes> (unindexed)
>
<Entity [ByteArray("ByteArray")/ByteArray("key2[4]")]:
value = <Blob: 73756 bytes> (unindexed)
>
Kind名を使って親エンティティを作って、その下にデータを保存していく。
データは子エンティティの0番目にKey名と分割数を保存して、実際のデータはそれ以降連番で分割して保存する。
取り出すときは子エンティティの0番目をまず取得し、Key名と分割数を取得。それを用いてKeyを作成しデータを取得、復元する。

ちなみに、子エンティティのKey名を決めうちで付けてるので、子エンティティ同士で衝突はしませんが、
親エンティティと子エンティティのKey名が衝突することはあるのでご注意を。
まあ、子エンティティのKey名を『Kind名+Key名+連番』にしておけば良い話ですが。…後で修正しよう。

とりあえず至る所でトランザクション使ってるような感じなのでパフォーマンスは激烈に悪い気がする。
面倒だったので Set を返すようなメソッドは実装してません。UnsupportedOperationException をスローします。

入れ子も可能にしたら面白そうじゃね?とかふと思った。誰か作ってくだしあ。

キムヨナと浅田真央

真央ちゃんの演技の方が良かった(キリッ

『真央の演技は途切れ途切れでダメだわ』とか言ってる人いるけど知ったこっちゃねぇ。
そしてまずトリプルアクセルを褒めろと言いたい。五輪女子SPで初なんだから。凄いことなんだから。
『溜めて溜めて溜めてどっこいしょって跳ぶとか無いわぁ』とか言ってる場合じゃないでしょ。
伊藤みどりみたいとか言ってる場合じゃないでしょ。てか伊藤みどりを馬鹿にするな。
久々に伊藤みどりの動画見たけど、あの高さはハンパ無い。あれ見て茶化すとか無いわ。

と言う事で、フィギュアスケートについて詳しく知らないから点数がどうのとか言えないけど、
とりあえず浅田真央の演技は良かったと思うし、トリプルアクセルは凄かったと思います。

別にキムヨナの演技を下に見てるとかそういうことではなく。
ただ、もっと真央ちゃんを褒めてあげて欲しいなと、身近な人の声だけを聞いてるとそう思ってしまったというお話。
後、伊藤みどりは凄いです。『溜めが長い』とか茶化してる奴はもう一回演技を見て悔い改めなさい。


で、キムヨナについてちょっと面白かったこと。
男子メダリストとキムヨナの採点を比較してみると、なんと男子よりも優れているらしい。
採点基準は男女で変わらないので、普通は体力もあり難度の高いジャンプを跳ぶ男子の方が点数が高くなるのですが。
-----
左キムヨナ 右ライサチェック
・スケート技術
8.60 > 8.20
・技のつなぎ
7.90 < 7.95
・表現力
8.60 = 8.60
・振り付け
8.40 < 8.50
・音楽の調和
8.75 = 8.75
[42.25] > [42.00]
-----
左キムヨナ 右プルシェンコ
・スケート技術
8.60 > 8.20
・技のつなぎ
7.90 > 6.80
・表現力
8.60 < 8.65
・振り付け
8.40 > 7.85
・音楽の調和
8.75 > 8.25
[42.25] > [39.75]
-----
左キムヨナ 右高橋
・スケート技術
8.60 > 8.30
・技のつなぎ
7.90 > 7.50
・表現力
8.60 > 8.55
・振り付け
8.60 > 8.55
・音楽の調和
8.75 > 8.70
[42.25] > [41.35]

採点についてネットでは色々と物議を醸してるようですね。「キムヨナ 不正」で否定肯定色々出てきます。
ちなみに、五輪だけの話でなくて以前からずっと言われ続けているようです。
キムヨナの母国があそこなだけにさもありなんと言った感じですが、さて。
テレビで全くそういうことが流れてこないのはやっぱり配慮してるからなのかなぁ。

以下回転数不足検証の動画(の生き残り かなり強烈な削除が行われているらしい)

FlashDevelopによるAIRアプリ開発 導入メモ

AIRはFlexと併せて覚えて行きたいなぁということで、唐突にやってみた。

IDEはフリーでEclipse以外という制限の元、FlashDevelopに決定。
minibuilderも良いなあと思ったものの、mxmlの編集には対応していないので除外。
(今後、バージョンアップして対応した場合は乗り換えるかも)

FlashDevelopで開発するには以下の環境が必要。
java runtime: 1.6以上
.NET Framework runtime: .NET Framework 2.0以上
コンパイラ: Flex3 SDK(3.4推奨?)
AIRランタイム: Adobe AIR 1.5以上

環境が用意出来たら以下からFlashDevelopをゲット。お好みで日本語化ファイルも。
以下の
FlashDevelop.org - View forum - Releases
logicalyze::blog

FlashDevelopの初期設定は以下のような感じで。
1.FlashDevelopを起動する。
2.メニューから「Tools」→「Program Settings」を選ぶ。
3.左のリストの中から「FlashDevelop」を選び、右のリストの「Fallback CodePage」を「UTF8」にする。
4.左のリストの中から「AS3Context」を選び、右のリストの「Flex SDK Location」を「C:\flex」にする。
5.「Close」ボタンを押して閉じる。

(参考:FlashDevelopとFlex 3 SDKでAdobe AIR - 今日覚えたこと

とりあえず動かす


[Project]→[New Project...]から新規プロジェクトを作成する。設定は適当に。NewProject


この状態で、[F5]もしくは右上青色の三角ボタンを押すと、コンパイルされ、実行結果が表示される。
(何も弄っていないので、何にも無いただの窓が表示される)


ビューとロジックを分けて書く


mxmlファイルがビューとなり、この中にActionScriptでロジックを書くことも出来る。
が、多分そんなことをしたら後で後悔することになるので、
「mxml=ビュー」「as=ロジック」と分離して書く方法を先に学ぶ。

AIR開発用のフレームワークを使わないでそのように書くとなると、
・IMXMLObjectを使う
・ビューを継承する

の2つがあるらしい。「mxml as 分離」でググると沢山出てくる。
実際にそのようにして書いた例が以下。

[src/Sample.mxml]






[src/views/MyView.mxml]






[src/logics/MyLogic.as]

package logics {
import flash.events.MouseEvent;

import mx.controls.Alert;
import mx.events.FlexEvent

import views.MyView;

public class MyLogic extends MyView {
public function MyLogic() {
super();
addEventListener(FlexEvent.CREATION_COMPLETE, createCompleteHandler);
}
private function createCompleteHandler(event:FlexEvent):void {
btn.addEventListener(MouseEvent.CLICK, button_clickHandler);
}

private function button_clickHandler(event:MouseEvent):void {
Alert.show("ボタンがクリックされたよ");
}
}
}


MyViewにはビューだけ書き、MyLogicにてビューの対するロジックを書いている。
そしてそれをSample.mxmlから呼び出している感じ。

左から右へ受け流す


AIRLife.net: view(MXML)とlogic(ActionScript)の分離

[src/views/MyView.mxml]













[src/logics/MyLogic.as]

package logics {
import flash.events.MouseEvent;

import mx.controls.Alert;
import mx.events.FlexEvent

import views.MyView;

public class MyLogic extends MyView {
public function MyLogic() {
super();
addEventListener(FlexEvent.CREATION_COMPLETE, createCompleteHandler);
}

private function createCompleteHandler(event:FlexEvent):void {
copyButton.addEventListener(MouseEvent.CLICK, copy);
clearButton.addEventListener(MouseEvent.CLICK, clear);
}

private function copy(event:MouseEvent):void{
rightField.text = leftField.text;
}

private function clear(event:MouseEvent):void {
leftField.text = "";
rightField.text = "";
}
}
}

あけおめ

ことよろ

虎丸


色塗る元気はありませんでした。

pixiv 用 SITEINFO (AutoPagerize on Greasemonkey)

pixiv を見ていると、あるユーザの絵を順に見たいときが結構あります。
以前、そういった用途に AutoPagerize の SITEINFO が追加されたことがあるのですが、
一瞬で無くなりました。苦情があったか、書き換えられたかしたのでしょう。
でも、個人的には便利だったので、自分で SITEINFO を追加しています。

AutoPagerize の SITEINFO は wedata に追加してグローバルに適用するか、
スクリプトを直接書き換えて、ローカルに適用することが出来ます。
前者の方法だと、いろんな人に迷惑が掛かる場合があるので、後者の方法で適用します。

Firefox から [ツール]>[Greasemonkey]>[ユーザスクリプトの管理] を開いて
AutoPagerize 選択し、編集ボタンを押します。エディタを適当に選ぶと編集画面が開きます。

上部に SITEINFO の変数があるので、以下のように編集します。
(コメントは初期のものです。当然無視して構いません)
var SITEINFO = [
/* sample
{
url: 'http://(.*).google.+/(search).+',
nextLink: 'id("navbar")//td[last()]/a',
pageElement: '//div[@id="res"]/div',
exampleUrl: 'http://www.google.com/search?q=nsIObserver',
},
*/
/* template
{
url: '',
nextLink: '',
pageElement: '',
exampleUrl: '',
},
*/
{
url: 'http://www.pixiv.net/member_illust.php\\?mode=medium&illust_id=',
nextLink: '//div[@id="content2"]//a[contains(text(), "»")]',
pageElement: '//div[@id="pixiv"]',
},
]
以上で、pixiv 絵を順に見る AutoPagerize の設定は完了です。

※ 余談 ※
AutoPagerize の SITEINFO は誰でも編集が可能です。
『このページは AutoPagerize 化すると便利だろうな』と思ったら一度編集してみると良いでしょう。
迷惑に思う人がいれば削除されますし、間違いがあれば XPATH に強い人が修正してくれます。
恐れずどんどん追加編集して、AutoPagerize をより便利に使えるようにしていきましょう。

ちなみに自分は、このサイトを AutoPagerize 化したいがために忍びブログの AutoPagerize を編集したことがあります。
drry氏に編集されたりして、結構面白かったです。(どうやらdrry氏は正規表現や、XPATH などで有名な人のようです)

Tumblr とか、各種ニュースサイトで流れている株価のグラフ

i0030306-1258634735


どうしてこうなった

諸事情により wicket-rome による RSS 出力コードの抜粋


【追記】
ここを参照してコードを書いた』的な記事を過去に書いていたw


wicket-rome を使ってRSSを出力する場合、以下のような方法が見つかる。
http://jroller.com/wireframe/entry/wicket_feedpage
public abstract class FeedPage extends WebPage {
@Override
public String getMarkupType() {
return "xml";
}

@Override
protected final void onRender(MarkupStream markupStream) {
PrintWriter writer = new PrintWriter(getResponse().getOutputStream());
SyndFeedOutput output = new SyndFeedOutput();
try {
output.output(getFeed(), writer);
} catch (IOException e) {
throw new RuntimeException("Error streaming feed.", e);
} catch (FeedException e) {
throw new RuntimeException("Error streaming feed.", e);
}
}

protected abstract SyndFeed getFeed();
}

これだと理由は不明だが日本語が文字化けする問題が発生する。
微妙に文字コードだけの問題でも無いような雰囲気。多分どうにか出来る気がするが。

でも、別の方法を取れば一応文字化けしないようなので以下抜粋。
[WicketApplication.java]
	@Override
protected void init() {
mountSharedResource("/rss", new ResourceReference("myFeed"){
@Override
protected Resource newResource() {
return new MyFeedResource(Guice.createInjector(new Module()).getInstance(Service.class));
}
}.getSharedResourceKey());
}

[RSSFeedPage.java]
public class MyFeedResource extends FeedResource{
private Service service;

public MyFeedResource(){}
public MyFeedResource(Service service){
this.service = service;
}

@Override
protected SyndFeed getFeed() {
SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0");
feed.setEncoding("UTF-8");
feed.setTitle(service.getSetting().getBlogname());
feed.setLink(service.getSetting().getBlogurl());
feed.setDescription(service.getSetting().getDescription());
feed.setPublishedDate(new Date());

List<SyndEntry> entries = new ArrayList<SyndEntry>();
try {
for(ArticleModel articleModel : service.getArticleList(service.getSetting().getListnum(), 0)){
SyndEntry entry = new SyndEntryImpl();
entry.setTitle(articleModel.getTitle());
entry.setLink(service.getSetting().getBlogurl()+"detail/"+articleModel.getId()+"/");
entry.setPublishedDate(articleModel.getDate());
SyndContent description = new SyndContentImpl();
description.setType("text/plain");
description.setValue(articleModel.getContents());
entry.setDescription(description);
entries.add(entry);
}
} catch (SQLException e) {
e.printStackTrace();
}
feed.setEntries(entries);
return feed;
}
}

1.wicket-romeのFeedResourceを継承してgetFeedを実装したものを作成(MyFeedResource)。
2.WicketApplication#init()内にで、ResourceReferenceを実装して(MyFeedResource)を返すようにして、mountする。

ローカルだとこれで一応文字化けせずに表示できている。
どっか海外のサイトで解説されていたwicket-romeのRSS出力方法。どこだったかは失念。

WicketStuffのほうでも、Resourceとして出力するような例が示されているので、
多分Resourceとして出力するのが良いのかなぁという感じ。

誰得サービス Taggimblr

Taggimblr(たぎんぶらー)とは、Tumblrのpixiv絵のポストに自動でタグを付けるサービスです。
http://taggimblr.appspot.com/

いずれは、汎用的にタグ付け出来るようなサービスにするつもりです。
(URLとxpathをペアで設定できるようにするなどして、タグ付け対象を後から増やせるようにするなど)

HTTPURLConnection のラッパー

GAE/JのURLFetchが上手く行かなかったので、HTTPURLConnectionを使うようにしたが、
そのままだと煩雑過ぎるので、適当にラップしてみた。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;

import com.google.appengine.repackaged.com.google.common.base.StringUtil;

public class URLFetch {
private String url;
private String method;
private String cookie;
private String response;
private Integer responseCode;
private boolean isConnected = false;

public URLFetch(String url, String method){
this.url = url;
this.method = method;
}

public String getCookie(){
if(!isConnected){
try {
throw new IOException("まだ接続していません");
} catch (IOException e) {
e.printStackTrace();
}
}
return this.cookie;
}
public String getResponse(){
if(!isConnected){
try {
throw new IOException("まだ接続していません");
} catch (IOException e) {
e.printStackTrace();
}
}
return this.response;
}
public Integer getResponseCode(){
if(!isConnected){
try {
throw new IOException("まだ接続していません");
} catch (IOException e) {
e.printStackTrace();
}
}
return this.responseCode;
}

public void newConnection(String url, String method){
this.url = url;
this.method = method;
this.cookie = null;
this.response = null;
this.responseCode = null;
this.isConnected = false;
}

public void doConnect(Map params, String cookie) throws IOException{
URL url = new URL(this.url);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod(this.method);
if(method.toLowerCase().equals("get")){
con.setDoInput(true);
if(cookie != null){
con.setInstanceFollowRedirects(false);
con.setRequestProperty("Cookie", cookie);
}
con.connect();
}else if(method.toLowerCase().equals("post")){
if(params != null){
con.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
}
if(cookie != null){
con.setInstanceFollowRedirects(false);
con.setRequestProperty("Cookie", cookie);
}
con.setDoInput(true);
con.setDoOutput(true);

con.connect();
if(params != null){
OutputStreamWriter osw = new OutputStreamWriter(con.getOutputStream());
osw.write(makeParams(params));
osw.flush();
osw.close();
}
}
this.cookie = con.getHeaderField("Set-Cookie");
this.responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));
String result = "";
String line;
while ((line = in.readLine()) != null) {
result += line;
}
this.response = result;
this.isConnected = true;
con.disconnect();
}

private String makeParams(Map params){
if(params.isEmpty())
return null;
String result = "";
for(Entry e: params.entrySet()){
result += e.getKey()+"="+e.getValue()+"&";
}
result = StringUtil.stripSuffix(result, "&");
return result;
}
}

使い方は以下のような感じ。GETはURLに直接パラメータを指定する形。修正する予定。
// コンストラクタでURLとMethodを指定します。
URLFetch uf = new URLFetch("http://mix3.tumblr.com/api/read", "GET");
URLFetch uf = new URLFetch("http://www.tumblr.com/api/authenticate", "POST");

// URLFetch#doConnectで、パラメータをHashで、クッキーを文字列で指定します。
uf.doConnect(null, null);
uf.doConnect(new HashMap(){{
put("key1", value1);
put("key2", value2);
}}, "");

// URLFetch#doConnect実行後、レスポンス結果を受け取れます。
uf.getResponseCode() // レスポンスコード
uf.getResponse() // レスポンス(文字列)
uf.getCookie() // クッキー
<<  2010年3月  
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Profile
mix3
主にTwitterに生息

絵を描くマのエンジニア死亡。
Twitter
pixiv blogparts - Daily Ranking
読書メーター
mix3の最近読んだ本