heroku shared dbのバックアップ

  • OSX 10.7.2
  • ruby 1.8.7 (2010-01-10 patchlevel 249) [universal-darwin11.0]

herokuの共有DBのバックアップをとろうと「heroku db:pull」を実行したらエラーになった。

$ heroku db:pull sqlite://20111126.sqlite

Taps Load Error: no such file to load -- taps/operation
You may need to install or update the taps gem to use db commands.
On most systems this will be:

sudo gem install taps

tapsをインストールしろとのことなので、インストールする。

$ sudo gem install taps

インストールしたのに、まったく同じエラーが表示される。
sqliteをアップグレードしてみる。

$ sudo gem update sqlite3 sqlite-ruby

まだエラー。
railsもインストールしてみる。

$ sudo gem install rails

このコマンドを実行したところ返答がまったくない。
一度キャンセルし、gemをアップデート

$ sudo gem update --system

再度「sudo gem install rails」を実行したら、すぐに返答があって無事インストール完了。
しかし、まだ「db:pull」するとエラーはでる。
herokuコマンドはheroku-toolbelt.pkgをインストールしたやつだったので、
gemでherokuをインストールしてみる。

$ sudo gem install heroku

ようやくバックアップが成功した。

$ heroku db:pull sqlite://20111126.sqlite
Loaded Taps v0.3.23
Warning: Data in the database 'sqlite://20111126.sqlite' will be overwritten and will not be recoverable.

 !    WARNING: Potentially Destructive Action
...

jquery mobile を使ってるときのpost後のredirect

jquery mobileを使ったページでフォームデータのpost -> redirect後にリロードすると、再度postされる。
formに「data-ajax=false」を指定することで回避。

play view

#{form @Foo.save(hoge.id), 'data-ajax':'false'}
...
#{/form}

play controller

public static void save(Long hogeId) {
    ...
    redirect("Foo.list");
}

aタグにはほぼ「rel="external"」つけてるし、jquery mobileを有効に使えてんのかな?

heroku上のアプリの状態確認

アプリのプロセス確認

$ heroku ps

Process       State               Command
------------  ------------------  ------------------------------
web.1         up for 50m          play run --http.port=$PORT $PLAY_O..

アプリ情報

$ heroku info

=== **************
Web URL:        http://************.herokuapp.com/
Git Repo:       git@heroku.com:**********.git
Repo size:      117M
Slug size:      26M
Stack:          cedar
Data size:      320k  ← 使用中DBサイズ
Addons:         Basic Logging, Basic Release Management, Shared Database 5MB
Owner:          ********@****.com

ダウンロード終了時の判定

ダウンロード開始時に画面の操作をできないようにし、ダウンロード終了後に画面操作できるようにするのに、いいアイデアをみつけた。

http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx

処理のフローとしては、
1.画面を操作できないようにする。
2.クライアントjsで、トークンを生成する。
3.クライアントjsで、一定時間ごとに、「1.」のトークンがクッキーに入っているか確認する処理を実行。
4.「2.」のトークンをサーバーに送信する。
5.サーバーで、受信したトークンをクッキーに設定する。
6.サーバーで、ダウンロード処理を開始する。
7.「2.」の処理が「4.」でサーバーから設定されたトークンを発見する。
8.画面を操作できるようにする。

処理内容はなんてことないけど、思いつかなかった。すばらしい。

元記事をちょっと変更してjavaで実装してみた。
変更点

  • トークンはcookieで送信する。
  • エラーが発生した場合に備えてトークンを確認する回数を制限する。


クライアント javascript

var Demo = {};

/**
 * ダウンロード終了管理
 * トークンをクッキーにつけて送信し、
 * レスポンスの受信クッキーで終了を判定する。
 * @constructor
 */
Demo.downloadManager = function() {
    this.fileDownloadCheckTimer = undefined;
    this.tokenName = Demo.downloadManager.cookieToken;
    this.successTokenName = Demo.downloadManager.cookieSuccessToken;
    this.maxLoopCount = 10;
    this.loopCount = 0;
};
/** 送信トークン名(サーバーで受信)*/
Demo.downloadManager.cookieToken = 'download-token';
/** 受信トークン名(サーバーから送信)*/
Demo.downloadManager.cookieSuccessToken = 'download-success-token';
/** 画面フリーズ */
Demo.downloadManager.prototype.blockUIForDownload = function() {
	Demo.blockUI(); // 画面を操作できないようにする。
    this.loopCount = 0;
    var token = new Date().getTime();
    // 現在時刻でトークンを生成
    Demo.setCookie(this.tokenName, token, '/');
    Demo.setCookie(this.successTokenName, 'START', '/');
    var self = this;
    this.fileDownloadCheckTimer = window.setInterval(function() {
        // 一定時間ごとにクッキーの値を確認しダウンロードが終わったか判定
        var cookieValue = Demo.getCookie(self.successTokenName);
        //console.log(self.loopCount + ':' + cookieValue);
        if (cookieValue == token) {
            self.finishDownload();
        }
        self.loopCount++;
        if (self.loopCount > self.maxLoopCount) {
            self.finishDownload();
        }
    }, 1000);
};
/** 画面フリーズ解除 */
Demo.downloadManager.prototype.finishDownload = function() {
    // ダウンロードが終わった後に後始末を行う
    window.clearInterval(this.fileDownloadCheckTimer);
    Demo.setCookie(this.tokenName, 'END', '/');
    Demo.setCookie(this.successTokenName, 'END', '/');
	
    Demo.unBlockUI(); // 画面を操作できるようにする。
};
Demo.setCookie = function(name, value, path) {
	// クッキー設定処理
};
Demo.getCookie =  function(name) {
	// クッキー返却処理
};
Demo.blockUI = function(){
	// 画面操作を抑制する処理
};
Demo.unBlockUI = function(){
	// 画面操作抑制を解除する処理
};


HTML

<input type="button" value="test" onclick="new Demo.downloadManager().blockUIForDownload()" />

Server処理 java

    public static final String COOKIE_NAME_DOWNLOAD_TOKEN = "download-token";
    public static final String COOKIE_NAME_DOWNLOAD_SUCCESS_TOKEN = "download-success-token";

    …
       // ダウンロード処理の前にクッキーを設定する
        String downloadToken = "";
        for (Cookie c : request.getCookies()) {
            if (c.getName().equals(COOKIE_NAME_DOWNLOAD_TOKEN)) {
                downloadToken = c.getValue();
            }
        }
        log.debug("Download Token:" + downloadToken);

        Cookie resultCookie = new Cookie(COOKIE_NAME_DOWNLOAD_SUCCESS_TOKEN,
                downloadToken);
        resultCookie.setPath("/");
        response.addCookie(resultCookie);
        // ダウンロード処理

CSVをソート (複数列指定)

ComparatorChain便利だった。

適当なライブラリを使ってCSVを読み込んでフィールドを文字列のリストにして、
それをさらに行数分リストにしたのを用意してソートする。

package demo;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.comparators.ComparatorChain;

/** CSVデータソート用クラス */
public final class CSVSort {
	/**
	 * CSVをソートします
	 * @param csv csvデータ
	 * @param metaDatas ソート情報
	 */
	@SuppressWarnings("unchecked")
	public static void sort(List<List<String>> csv, List<SortMetaData> metaDatas) {
		if (metaDatas.isEmpty()) {
			return;
		}
		// ソート情報をキー順に並べ替え
		Collections.sort(metaDatas, new Comparator<SortMetaData>() {
			@Override
			public int compare(SortMetaData o1, SortMetaData o2) {
				return (o1.order < o2.order ? -1 : (o1.order == o2.order ? 0
						: 1));
			}
		});
		ComparatorChain cc = new ComparatorChain();
		for (final SortMetaData meta : metaDatas) {
			cc.addComparator(new Comparator<List<String>>() {
				@Override
				public int compare(List<String> o1, List<String> o2) {
					return o1.get(meta.colNo).compareTo(o2.get(meta.colNo));
				}
			}, !meta.isAscending);
		}
		// ソート
		Collections.sort(csv, cc);
	}

	/** ソート情報 */
	public static class SortMetaData {
		/** 列番号 */
		public final int colNo;
		/** キー順(何番目に有効にするか) */
		public final long order;
		/** 昇順の場合{@code true} */
		public final boolean isAscending;

		/**
		 * コンストラクタ
		 * @param colNos 列番号
		 * @param order キー順
		 * @param isAccending 昇順の場合{@code true}
		 */
		public SortMetaData(int colNos, long order, boolean isAccending) {
			this.colNo = colNos;
			this.order = order;
			this.isAscending = isAccending;
		}
	}
}

testコード

package demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import junit.framework.TestCase;
import demo.CSVSort.SortMetaData;

public class CSVSortTest extends TestCase {
	private static List<String> r(String... strings) {
		return Arrays.asList(strings);
	}

	private static String d(List<List<String>> csv) {
		StringBuilder sb = new StringBuilder();
		for (List<String> line : csv) {
			for (String field : line) {
				sb.append(field);
				sb.append(',');
			}
			sb.deleteCharAt(sb.length() - 1);
			sb.append('\n');
		}
		return sb.toString();
	}

	/** sort test */
	public void testSort() {
		List<List<String>> act = new ArrayList<List<String>>();
		// r()は文字列配列をリストにするメソッド
		act.add(r("100", "120", "110"));
		act.add(r("100", "110", "120"));
		act.add(r("200", "100", "200"));
		List<SortMetaData> metaList = new ArrayList<SortMetaData>();
		metaList.add(new SortMetaData(0, 1, false));
		metaList.add(new SortMetaData(1, 1, true));
		CSVSort.sort(act, metaList);
		List<List<String>> exp = new ArrayList<List<String>>();
		exp.add(r("200", "100", "200"));
		exp.add(r("100", "110", "120"));
		exp.add(r("100", "120", "110"));
		// d()はオブジェクトをダンプするメソッド
		assertEquals(d(exp), d(act));
		System.out.println(d(exp));
	}
}

play gae リストのサイズ取得時のエラー

ローカルでは

${list.size}

で動いていたのに、GAEにデプロイしたらエラーになった

Execution error occured in template /app/views/xxxxxx.html. Exception raised was IllegalAccessException : Reflection is not allowed on private int java.util.ArrayList.size.

play.exceptions.TemplateExecutionException: Reflection is not allowed on private int java.util.ArrayList.size
	at play.templates.BaseTemplate.throwException(BaseTemplate.java:84)
	at play.templates.GroovyTemplate.internalRender(GroovyTemplate.java:252)
	at play.templates.Template.render(Template.java:26)

フィールドにアクセスしてエラーになっていたので、メソッドにアクセスするようにすればOK

${list.size()}