09

SVNKit(3)

さて、前回は雑にSVNRepository#updateしてみたわけだけど、どうもコンテンツのDiffが流れてくるので、ワーキングコピーに対してpatchしなければならなさそう、というところまでだった。

このDiffのコンテンツはISVNEditortextDaltaChunkというメソッドの引数に渡される SVNDiffWindow 型のオブジェクトの内部に格納されている。

diffをいわゆるdiffテキスト的なものから自分でpatch処理をする必要があるのかというと、Javadocを見るにSVNDiffWindowがよろしくやってくれるためのメソッドを持っているように見える。

apply(byte[] sourceBuffer, byte[] targetBuffer) 

これはいかにもsourceBufferがワーキングコピーで、targetBufferが適用後のバッファとなるようだ。 もう一つこんなのがあるが

apply(SVNDiffWindowApplyBaton applyBaton) 

SVNDiffWindowApplyBaton インスタンスを作るためにはDiff適用後のファイルのMD5をチェックサムとして渡す必要があるようだが、このMD5をどこから取得すればよいのかわからない。ISVNEditorの一連の呼び出しの中でチェックサムが受け取れるが、それは closeFileのタイミングなので、ここでは間に合わない。別途リポジトリに問い合わせるのだろうか。

仕方ないので、とりあえず前者でやってみることにする。

現在テストのために使用しているリポジトリの内容は前回前々回に詳細を記載しているのでそちらを参照していただきたい。今回とりあえずrevision2からrevision3(最新)でfile1.txtが変更になっているので、その差分を適用することを考えていく。

とりあえずrevision2の時点でfile1.txtの内容は以下のようになっている。

commit1

末尾には改行がない。

これがrevision3(最新)では以下のように変更なっている。

commit1
commit2

なので差分は

(改行)
commit2

が来ることになる。

この差分を適用するために、textDaltaChunk に以下のような内容を書いた。

    @Override
    public OutputStream textDeltaChunk(final String path, final SVNDiffWindow diffWindow) throws SVNException {
        logStart(path + " " + diffWindow.toString());

        final Map<String, String> pathContentsMap = new HashMap<>();
        pathContentsMap.put("trunk/file1.txt", "commit1");

        final String content = pathContentsMap.get(path);
        byte[] souceBuffer = new byte[0];
        if (content != null) {
            souceBuffer = content.getBytes();
        }

        final byte[] targetBuffer = new byte[diffWindow.getTargetViewLength()];
        diffWindow.apply(souceBuffer, targetBuffer);

        System.out.println(new String(targetBuffer));

        logEnd();
        return null;
    }

file1.txtに対して呼び出されたときのみ、"commit1"という内容をワーキングコピーのソースとして与えて、それ以外の場合はソースは空のバッファにしている。 で、ターゲットの長さ分をtargetBufferとして用意して、その内容をapply実行後に標準出力へ出力してみている。

logStartはメソッド名と適当なメッセージを出力している。 logEndは最初終了のメッセージを出力していたのだけど、対して意味なかったので現状何もしていない。

戻りがnullになっているのはJavadocの説明にそうしろとあったため。戻りがOutputStreamになっているのは過去の互換性のためらしい。ここで出力ストリームを戻すと戻した先で閉じられてしまう。

さて、これを実行した結果は以下の通り。

#targetRevision start > [rev.3]
#openRoot start > [rev.2]
#openDir start > trunk[rev.2]
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#openFile start > trunk/file1.txt[rev.2]
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
commit1
commit2
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
commit2
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
#closeDir start > 
#closeDir start > 
#closeEdit start > 

今回のコードが出力している部分を抜粋すると

#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
commit1
commit2

ちゃんと最新版と一致しているようだ。 まあ目視の上ではこれでいいんだけど、一応データのチェックサムを確認するべきではある。 上記で少し触れているけど、ISVNEditor#closeFileの引数にチェックサムが与えられている。 型はただのString。Javadocによれば、これはMD5らしい。ExpEditorのインスタンス変数にファイルパスとMD5の結果を持つMap型の変数を定義して、textDeltaChunkの処理後にMD5を記録し、closeFileで渡される値を比較してみることにしてみる。

textDeltaChunkを以下のように修正。上記までと同様の処理の後に、MD5を計算する処理を追加している。

    @Override
    public OutputStream textDeltaChunk(final String path, final SVNDiffWindow diffWindow) throws SVNException {
        logStart(path + " " + diffWindow.toString());

        try {
            final Map<String, String> pathContentsMap = new HashMap<>();
            pathContentsMap.put("trunk/file1.txt", "commit1");

            final String content = pathContentsMap.get(path);
            byte[] souceBuffer = new byte[0];
            if (content != null) {
                souceBuffer = content.getBytes();
            }

            final byte[] targetBuffer = new byte[diffWindow.getTargetViewLength()];
            diffWindow.apply(souceBuffer, targetBuffer);

            System.out.println(new String(targetBuffer));

            final MessageDigest instance = MessageDigest.getInstance("MD5");
            final byte[] digest = instance.digest(targetBuffer);
            final String hexBinary = DatatypeConverter.printHexBinary(digest);
            pathMD5Map.put(path, hexBinary);
            System.out.println("[MD5] " + path + " -> " + hexBinary);
        } catch (final NoSuchAlgorithmException e) {
            throw new SVNException(SVNErrorMessage.UNKNOWN_ERROR_MESSAGE, e);
        }

        logEnd();
        return null;
    }

加えて、closeFileで以下のように検証してみる。

    @Override
    public void closeFile(final String path, final String textChecksum) throws SVNException {
        logStart(path);
        // do nothing
        if (!textChecksum.equalsIgnoreCase(pathMD5Map.get(path))) {
            System.err.println("[MD5] UNmatch for " + path + " / " + textChecksum + " != " + String.valueOf(pathMD5Map.get(path)));
        } else {
            System.out.println("[MD5] match for " + path + " / " + textChecksum + " == " + pathMD5Map.get(path));
        }

        logEnd();
    }

すると以下のような出力が得られた。

#targetRevision start > [rev.3]
#openRoot start > [rev.2]
#openDir start > trunk[rev.2]
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#openFile start > trunk/file1.txt[rev.2]
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
commit1
commit2
[MD5] trunk/file1.txt -> CAE91EA5B6E3F99FE23E6C4F5089A91D
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
[MD5] match for trunk/file1.txt / cae91ea5b6e3f99fe23e6c4f5089a91d == CAE91EA5B6E3F99FE23E6C4F5089A91D
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
commit2
[MD5] trunk/file2.txt -> 8AFDB541A011CC47D258C4962EC19D90
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
[MD5] match for trunk/file2.txt / 8afdb541a011cc47d258c4962ec19d90 == 8AFDB541A011CC47D258C4962EC19D90
#closeDir start > 
#closeDir start > 
#closeEdit start > 

どうやら問題なさそうだ。

SVNKit(2)

前回の続き。

前回はとりあえずISVNRepositoryISVNEditorISVNDiffWindowを使ってリポジトリの内容にアクセスできそう、というところまでやった。

一応注記しておくと、前回取得できた情報を何とかすればSVNクライアントとして動けるのかもしれないが、前回の内容ではいわゆる .svnフォルダ配下の情報を作ることについては何も気にしていないので、まっとうなSVNクライアントとして動作させるにはいろいろと足りないかもしれない。

ここの記事では今後もまっとうなSVNクライアントとして動かすことは考えていないことはここでお断りしておく。たぶん、SVNクライアントを実装したいのなら、high-level APIを使うべきだろう。

で、今回は定期または不定期的にリポジトリから最新の情報を取得することを考える。その場合、前回取得した内容から現時点での最新までの差分のみを取得したい。前回はcheckoutを呼んでみたので、差分となるとそこからのupdateということになるだろう。diffというメソッドもあるようなのでリビジョンを指定してDiffを取るのでもいいのかもしれないが、とりあえず今回はupdateを調べてみる。

ISVNRepository#update

セットアップと接続までは前回と同じ。updateメソッドには(にも)いくつかオーバライドがあるが、以下のように呼びだしてみる。

        repository.update(-1, "trunk", true, new ExpReporter(), new ExpEditor());

前回は端折ったが、引数について順に。

  • -1
    • アップデートする先のリビジョン。APIドキュメントによるとデフォルトはHEADだ、と書いてあるが、型がlongなのでデフォルトって何のことだと思ったが、どうも-1(というかたぶん0より小さい値だろう)っぽい。
  • "trunk"
    • APIドキュメントではこれはリポジトリのインスタンスを生成する際に指定するURLの次の1レベルのパス、見たいな説明がされている。今回、trunkの上までを指定してリポジトリのインスタンスを生成しているので、こんな感じになる。optionalと書かれているので、リポジトリオブジェクトを生成する際に trunk を含めている場合、なくてもいいのかもしれない。
    • ブランチを指定したい場合にどうなるのかよくわからないが branches/branch-name的な書き方でいいんだろうか
  • true
    • 再帰的に処理するかどうかフラグ。SVNクライアントの画面なんかでもチェックボックスを見ることがある
  • new ExpReporter()
    • ISVNReporterBatonをimplementsした適当実装。後述。
  • new ExpEditor()
    • 前回若干説明したやつ
    • 今回も呼ばれたメソッド名と引数を適当に結合して出力する適当実装
ISVNReporterBaton

前回のISVNEditorに加えてISVNReporterBatonというinterfaceの実装を要求されている。 APIドキュメントに加えて若干の説明がここにあるが、要はワーキングコピーに存在するディレクトリやファイルについて個別にどんな状態か(チェックアウトされたものか、どのリビジョンか、削除するつもりかどうかなど)個別のパス情報としてリポジトリに伝えてやる必要があるらしい。

  • 上記のインタフェースを実装すると引数にISVNReporterインスタンスを引数に受け取れるので、それに対してsetPathというメソッドを呼ぶことで個別のディレクトリ、ファイルについての状態を伝えていけということのようだ。
  • ユーザの操作によってチェックアウト時とurlが変わってしまう場合(ファイルの移動などだろうか)は代わりにlinkPathを呼べ、とか同様にしてファイルをリポジトリから削除するつもりなら、deletePathを呼べとも書かれている。
  • そして一通りの報告処理が終わったらfinishReportを呼ぶ必要があるようだ。なんでそんな必要があるのかよくわからないが、個別にパスを調べる処理はそれなりに時間がかかることが想像されるのでマルチスレッド化されて処理されることを見越してのことかな?

ということで、ワーキングコピーに何もないことにして以下のような報告をするコードを書いてみる。

public class ExpReporter implements ISVNReporterBaton {

    @Override
    public void report(final ISVNReporter reporter) throws SVNException {
        reporter.setPath("", null, 0, SVNDepth.INFINITY, false);
        reporter.finishReport();
        return;
    }

}

reporter.setPathの引数について

  • ワーキングコピーには""しか存在してない
  • 特にロックは使用していないので ロックトークンは null
  • ""のリビジョンは0(最初)
  • 対象にするサブエントリ(配下のファイルとかフォルダ)の階層は無限(SVNDepth.INFINITY)
  • このパス(この場合"")がディレクトリでかつ前回のupdateがエラーで失敗した場合はtrueである必要があるが、そうでないので false

setPathの第一引数は"/"ではないかと思ったのだが、それだとよくわからないエラーが返ってきた。というかapacheのSVNモジュールがassertで死んでた。ドキュメントでは絶対パスで書かれているように見えるが…。

で以下のような出力を得た

#targetRevision start > [rev.3]
#openRoot start > [rev.0]
#addDir start > trunk
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
#addFile start > trunk/.project
#changeFileProperty start > trunk/.project svnkit:entry:sha1-checksum 4d0a7da34b1a6f2e1416d746bf268c1137453e14
#changeFileProperty start > trunk/.project svn:wc:ra_dav:version-url /svn/test/!svn/ver/2/trunk/.project
#changeFileProperty start > trunk/.project svn:entry:committed-rev 2
#changeFileProperty start > trunk/.project svn:entry:committed-date 2016-03-03T18:14:34.687289Z
#changeFileProperty start > trunk/.project svn:entry:last-author test
#changeFileProperty start > trunk/.project svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/.project svn:mime-type text/plain
#applyTextDelta start > trunk/.project
#textDeltaChunk start > trunk/.project 0:0:208:3:208:211:0
#textDeltaEnd start > trunk/.project
#closeFile start > trunk/.project
#addFile start > trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svnkit:entry:sha1-checksum 994c650e0954fd76b6859dae6d87caf2fad19e35
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file1.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:0:16:1:16:17:0
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
#closeDir start > 
#closeDir start > 
#closeEdit start > 

ファイルを順に追加しているのがわかる。

リポジトリの詳細な内容は前回 svn log --diff した結果を貼っているが、ざっくりおさらいすると

  • rev.0 : このリポジトリの最初
  • rev.1 : プロジェクト構造(trunkとかbranchesとか)つくった
  • rev.2 : /.projectと /file1.txtを追加
  • rev.3 : /file1.txtを更新、/file2.txtを追加

といった履歴になっている。

この状態でワーキングコピー""のリビジョンが2とだけ報告したらどうなるのだろうか。 つまり本来ワーキングコピー全体がリビジョン2であるならば/.projectと /file1.txtが存在するはずなのでそれらについても報告する必要があるが、意図的にそれをしない。

public class ExpReporter implements ISVNReporterBaton {

    @Override
    public void report(final ISVNReporter reporter) throws SVNException {
        reporter.setPath("", null, 2, SVNDepth.INFINITY, false);
        reporter.finishReport();
        return;
    }

}

得られた出力は以下

#targetRevision start > [rev.3]
#openRoot start > [rev.2]
#openDir start > trunk[rev.2]
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
#openFile start > trunk/file1.txt[rev.2]
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:7:16:3:9:12:0
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
#closeDir start > 
#closeDir start > 
#closeEdit start > 

/file2.txt がaddFileで /file1.txt はopenFileから始まっている。単純に差分になるようだ。

別途ISVNEditor#textDeltaChunkで受け取れるSVNDiffWindowオブジェクトのインスタンスから、ファイルの内容としてどういったものが取得できるのか見てみると、rev2->HEADでファイルの追加になる /file2.txt はファイルの内容すべて、内容の変更のみだった /file1.txt は変更差分のみ取得できた。

となると、ワーキングコピーとこれらの内容からどうにかして本来(というか、最新の全体)の内容を得るためには、ワーキングコピーに対してpatchしなければならない?

続きはまた次回。

SVNKit(1)

今時SVNのAPI触ってるなんて人に聞かれたら袋叩きにされるのだろうか。 とにかくRepositoryにある内容を取得したい。

ドキュメントを見るとAPIが2段階あるらしいことがわかる。

  • high-level api (working copy api)
    • org.tmatesoft.svn.core.wc および org.tmatesoft.svn.core.wc2 パッケージ
    • たぶん、実際SVNを使ったツールを開発するにあたっては当然必要になるワーキングコピーをベースにしたユースケースをカバーするAPI
  • low-level api
    • org.tmatesoft.svn.core.io パッケージ
    • リポジトリとプロトコルを直接的にインタフェースするとある
    • 上記を構成するために使用される低レイヤーのAPIなんだろうか?
    • 個人的にはこのレベルでもだいぶユースケースよりな設計の印象を受ける
SVNRepository

APIドキュメントを眺めているとどうやらリポジトリオブジェクトを作ってそこから操作をするらしい。

SVNRepositoryオブジェクトを作るにはSVNRespositoryFactoryというファクトリクラスを使うらしいが、その前に接続先のプロトコル(http: か、svn:またはsvn+hoge:か、file:か)によってセットアップをしてやる必要があるらしい。

        if (url.toLowerCase().startsWith("http")) {
            DAVRepositoryFactory.setup();
        } else if (url.toLowerCase().startsWith("svn")) {
            SVNRepositoryFactoryImpl.setup();
        } else if (url.toLowerCase().startsWith("file")) {
            FSRepositoryFactory.setup();
        } else {
            fail();
        }

こんな感じか。urlは普通にsubversionの接続先として使用するURLの文字列。

なんでsvnの場合だけImplがついているのか。ってSVNRepositoryFactoryにそれをさせるわけにいかなかったってことか。しかしファクトリのセットアップを別のクラスに切り出して、そっちを呼ばせるというのも、なんだか妙な設計に思えるが・・・まあ大した実害はないかもしれない。

続いてリポジトリオブジェクトを作成

        final SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
        final ISVNAuthenticationManager authenticationManager = SVNWCUtil.createDefaultAuthenticationManager(user, password.toCharArray());
        repository.setAuthenticationManager(authenticationManager);

ISVNAuthenticationManagerはその名のとおり接続時の認証情報を管理してくれるものらしい。この場合は単純にhttpのベーシック認証に渡すユーザ名とパスワードをSVNWUtilというユーティリティに渡してインスタンスを作ってもらっている。なんでパスワードがchar配列なのかよくわからないが、これは単にパスワードの平文文字列。たぶんSSH経由のSVNとかだとこの辺のやり方は認証方式に応じて変わるんだろう。

ISVNEditor

でとりあえずチェックアウトはこんな感じになるが

        repository.checkout(-1, "trunk", true, new ExpEditor());

最後の引数に要求されているExpEditorISVNEditorを実装したオブジェクトで、どうもチェックアウト実行中にはリポジトリ中の変更情報が順に流れてくるのでそれを処理するハンドラというか、Visitorのようなものを実装する必要があるようだ。ExpEditor自体は今回動きを確認するために適当に作った実装。

とりあえずどんな動きをするのか、ということでこんな構造のリポジトリを作った

[carrotsword trunk]$ ls -la
total 20
drwxrwxr-x 2 carrotsword carrotsword 4096 Mar  6 01:55 .
drwxrwxr-x 6 carrotsword carrotsword 4096 Mar  6 01:55 ..
-rw-rw-r-- 1 carrotsword carrotsword  208 Mar  6 01:55 .project
-rw-rw-r-- 1 carrotsword carrotsword   16 Mar  6 01:55 file1.txt
-rw-rw-r-- 1 carrotsword carrotsword    7 Mar  6 01:55 file2.txt

一応コミットは何度かに分けてある

[carrotsword trunk]$ svn log --diff

  ... 略 ...

------------------------------------------------------------------------
r3 | test | 2016-03-04 03:15:19 +0900 (Fri, 04 Mar 2016) | 1 line

commit2

Index: file2.txt
===================================================================
--- file2.txt   (revision 0)
+++ file2.txt   (revision 3)
@@ -0,0 +1 @@
+commit2
\ No newline at end of file

Property changes on: file2.txt
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: file1.txt
===================================================================
--- file1.txt   (revision 2)
+++ file1.txt   (revision 3)
@@ -1 +1,2 @@
-commit1
\ No newline at end of file
+commit1
+commit2
\ No newline at end of file

------------------------------------------------------------------------
r2 | test | 2016-03-04 03:14:34 +0900 (Fri, 04 Mar 2016) | 1 line

commit1

Index: .project
===================================================================
--- .project    (revision 0)
+++ .project    (revision 2)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>test</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+       </buildSpec>
+       <natures>
+       </natures>
+</projectDescription>

Property changes on: .project
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: file1.txt
===================================================================
--- file1.txt   (revision 0)
+++ file1.txt   (revision 2)
@@ -0,0 +1 @@
+commit1
\ No newline at end of file

Property changes on: file1.txt
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property

------------------------------------------------------------------------
r1 | test | 2016-03-04 03:12:54 +0900 (Fri, 04 Mar 2016) | 1 line




------------------------------------------------------------------------

これに対して、ISVNEditorがどんな呼ばれ方をするのか。 各メソッドに呼ばれたメソッド名とパラメータを簡単に出力するだけの実装をして実行してみたところ、以下のような出力が得られた。

#targetRevision start > [rev.3]
#openRoot start > [rev.3]
#openDir start > trunk[rev.3]
#changeDirProperty start > svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk
#changeDirProperty start > svn:entry:committed-rev 3
#changeDirProperty start > svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeDirProperty start > svn:entry:last-author test
#changeDirProperty start > svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#addFile start > trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svnkit:entry:sha1-checksum 54563f95fefa691baa82a522156322c21f7d6df3
#changeFileProperty start > trunk/file2.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file2.txt
#changeFileProperty start > trunk/file2.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file2.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file2.txt svn:entry:last-author test
#changeFileProperty start > trunk/file2.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file2.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file2.txt
#textDeltaChunk start > trunk/file2.txt 0:0:7:1:7:8:0
#textDeltaEnd start > trunk/file2.txt
#closeFile start > trunk/file2.txt
#addFile start > trunk/.project
#changeFileProperty start > trunk/.project svnkit:entry:sha1-checksum 4d0a7da34b1a6f2e1416d746bf268c1137453e14
#changeFileProperty start > trunk/.project svn:wc:ra_dav:version-url /svn/test/!svn/ver/2/trunk/.project
#changeFileProperty start > trunk/.project svn:entry:committed-rev 2
#changeFileProperty start > trunk/.project svn:entry:committed-date 2016-03-03T18:14:34.687289Z
#changeFileProperty start > trunk/.project svn:entry:last-author test
#changeFileProperty start > trunk/.project svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/.project svn:mime-type text/plain
#applyTextDelta start > trunk/.project
#textDeltaChunk start > trunk/.project 0:0:208:3:208:211:0
#textDeltaEnd start > trunk/.project
#closeFile start > trunk/.project
#addFile start > trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svnkit:entry:sha1-checksum 994c650e0954fd76b6859dae6d87caf2fad19e35
#changeFileProperty start > trunk/file1.txt svn:wc:ra_dav:version-url /svn/test/!svn/ver/3/trunk/file1.txt
#changeFileProperty start > trunk/file1.txt svn:entry:committed-rev 3
#changeFileProperty start > trunk/file1.txt svn:entry:committed-date 2016-03-03T18:15:19.061137Z
#changeFileProperty start > trunk/file1.txt svn:entry:last-author test
#changeFileProperty start > trunk/file1.txt svn:entry:uuid 5025b694-87bc-4570-9e45-8a2bcb52a122
#changeFileProperty start > trunk/file1.txt svn:mime-type text/plain
#applyTextDelta start > trunk/file1.txt
#textDeltaChunk start > trunk/file1.txt 0:0:16:1:16:17:0
#textDeltaEnd start > trunk/file1.txt
#closeFile start > trunk/file1.txt
#closeDir start > 
#closeDir start > 
#closeEdit start > 

これは面倒そう!

ざっと見る限り、コミットされているファイルの内容が見えない。 これはtextDeltaChunkの引数に渡されるSVNDiffWindowオブジェクトがその情報を持っている。 適当に吐き出してみたところSVNヘッダ+ファイルの内容のバイナリストリームを持っているっぽい。 ということは、その内容を指定のパスに吐き出してやれば(checkoutだけど)export相当のことはできるようになるのかも。

今回はこの辺で。

teslogger-server

少し前に以下で発表されたやつ

www.slideshare.net

試してみたかったけどClojure力が足りない

なんとか動かしたメモ

Leiningen はあらかじめ入れておく。公式ではbatファイルをお勧めされるけど、Windows10ではなんだったか忘れたがエラーになったと思うので、インストーラ使った気がする。

  1. teslogger-servergit clone して持ってくる。
  2. axebomber-clj が必要だが Clojars にないようなのでこれも git clone して持ってくる
    • lein uberjar して axebomber-clj-0.1.1-standalone.jar をビルド
  3. teslogger-server/lib/ に axebomber-clj-0.1.1-standalone.jar を配置
  4. teslogger-server/project.clj を修正した
    • :dependencies
      • [net.unit8/axebomber-clj "0.1.0-SNAPSHOT"] をコメントアウト
      • [net.unit8/ulon-colon "0.2.3"] はSNAPSHOTじゃない
    • :resource-paths ["lib/axebomber-clj-0.1.1-standalone.jar" "resources"] を追加
  5. clojure script をコンパイルする
    • lein cljsbuild once
  6. サーバを起動する
    • lein ring server
  7. ブラウザが起動して 【START TO EVIDENCE】が表示されたら teslogger を起動すればいい。

日付のパース

iAP上で日付を固定書式でパースしたいのだが。

DateTimeFormatter: intra-mart Accel Platform SSJS API Documentation

とある既存コードを眺めていて気になったので試してみた

Debug.browse(
   DateTimeFormatter.parseToDate('yyyy/M/d', '2015/9/9'),
   DateTimeFormatter.parseToDate('yyyy/MM/dd', '2015/9/9'),
   DateTimeFormatter.parseToDate('yyyy/M/d', '2015/09/09'),
   DateTimeFormatter.parseToDate('yyyy/MM/dd', '2015/09/09')
);

結果は

1   Wed Sep 09 00:00:00 JST 2015    Date
2   Wed Sep 09 00:00:00 JST 2015    Date
3   Wed Sep 09 00:00:00 JST 2015    Date
4   Wed Sep 09 00:00:00 JST 2015    Date

そうだよねえ。

ちなみにパースに失敗すると例外が飛ぶ。javacript 側の try-catch で捕まえられるから、それで何とかするしかないねえ。

嵌り実況

  1. ファイルをアップロードする画面が必要になった。intra-mart Accel Platform 上なので当然 imuiFileUpload を使うことを考える。
  2. ただアップロードするだけじゃなく、アップロードに際していくつか入力する項目が付属するので、ファイルは imuiFileUpload に任せて、 imuiFileUpload がアップロードしたファイルについての情報と付属項目を一緒に <form> にいれて imuiAjaxSend で送信するのが楽そう、と考える。
  3. 画面全体を <form> で囲ったら imuiFileUpload のスタイルが剥がれて、おそらくまともに動作していない状態になった。
    • ファイルをアップロードする性質上フォームとは相性が悪いのか?
  4. 選択肢としてありそうなのは imuiFileUploadouterFormId 属性の指定。
    • でもこれって単にマルチパートとして送信してしまうのでは?
    • 別にそれでもいいんだけど、imuiFileUpload がサーバサイドで Session Scope Storage に自動的に保存してくれる機能を活かしたい
  5. とりあえず、全ての値を hidden で保持するフォームを作成して、入力された値はそこに転記して imuiAjaxSend することにしようと考える。
  6. でも入力値はバリデーションしたいので、上記の転記先のフォームに対して imuiValidate のルールを定義して呼び出してみる。
    • エラーメッセージ表示は関数を与えることでコントロールできるので、バリデーション対象が hidden でもいけると踏んだ
  7. 何を入力してもまったくバリデーションエラーにならない(入力値に何を入れても imuiValidateの戻り値が true
  8. jQuery validation plugin hidden でGoogle検索したらデフォルトでは hidden 項目はバリデーションの対象にしないらしいことが分かる。これを無効にするにはバリデーションを無視する項目を指定する引数に [] (空配列)を渡せばよいらしいことが書いてある。
  9. 与えてみるが何も変わらない。
  10. DOMエレメントツリーを見ていると上記の転記先の <form>novalidate なる属性が付与されていることを発見する。
  11. 調べてみると html5 の機能でバリデーションを無効にする指定らしい。StackOverFlowにてjQueryのvalidation pluginも何らかの条件下でこの属性を付与するようで困るというようなポストを見つけた。
  12. StackOverFlowの同スレッドにて $("#form").removeAttr("novalidate") せよとのポストが見つかったので試してみるも novalidate はなくなるが(あたりまえ)動作には変化ない。
  13. ここまで書いたところでふと思い当たってバリデーション無視対象を [] でなく '' にしてみる

・・・やっと imuiValidate がエラーを返してきた。[-_-]

敗因は?

ファイルアップロードが中途半端に混ざってる画面設計か、もしくは4で outerFormId 使うほうを選んでたらもっと簡単に解決したのだろうか(試してない)。 あとはバリデーション無視対象はString型だよ、と一応公式ドキュメントには書いてあるので、それを見逃した点かな…。

あの人は今

消息が気になったので調べている。

Applet ってまだつかえるの?

たぶん・・・。

セキュリティ状況については正直もうわからない。恒常的に使い続けるものではなくなってそう。

Applet と Java Web Start どっちが良いのか?

AppletはHTMLに埋め込みのものだが、Java Web Startはそうではない?基本的には独立型のアプリケーションという形態のよう。デスクトップにショートカットを配置したりできる。

なので、カバーするユースケースはまったく同じというわけではないみたい。

参考: https://docs.oracle.com/javase/jp/6/technotes/guides/jweb/decision_guide.html

Applet は ローカルファイルにアクセスできないのでは?

上記の参考URLにあるが、Java Network Launching Protocol (JNLP) を使用して起動されたアプレットはローカルファイルへのアクセスが可能らしい。

Java Network Launching Protocol (JNLP) を使用してアプレットを起動するとはどういうことか?

参考: http://itpro.nikkeibp.co.jp/article/COLUMN/20090423/328953/

JNLPファイルがあればいい? でも現在動作しているアプレットを調べてみたが、特にそんなものはない様に見える。

Java FXは使えるのか?

使えそう。大分古い記事なので最新では事情が異なるかも。

参考: http://skrb.hatenablog.com/entry/20081031/p1

JavaFXを使うとして、プラットフォームはどう選べばよいのか

たとえば、多くのサーバ運用環境はJDK 7以前だろうが、 クライアントサイドで動くのでクライアントのバージョンが新しければよいのかな?

開発環境どうする

Eclipse + e(fx)clipse

参考: http://www.coppermine.jp/docs/programming/2013/12/efxclipse.html

Net BeansだったらデフォルトでJavaFXのための環境が付属するようだ。

JavaFX の Scene Builder の配布元が見つからない

オープンソース化してOracleからは配布していない?

参考: http://qiita.com/sh-ogawa/items/16dc36996626775d5c4c