スマホのmicroSDなどでは特定のファイルなどを変更して、 あとでPCでバックアップを取る時にuitのメタ情報を更新したい、 というシチュエーションがあると思い、 そういうケースの機能を実装しようと思ったが、どうも無理っぽい、 という事に気づいたのでそのメモを。

外部でファイルを変更されると困る、という話

ファイルをツール外で変更された時の挙動を考えていたが、 結論から言うとサポート出来ないと思う。

誰かがファイルを変更した場合、 その変更前と同じハッシュ値のファイルの実体がそのファイルだけで他にリンクがある場合、 リンクのファイルの中身を復元することは不可能になる。 その場合はリンクは全部消えるくらいしか出来る事は無いが、 扱いが面倒な割には大して嬉しくない。 .uit/dirs下は更新できても.uit/hash下はうまく更新出来ない、 削除された、という状態を扱うのは途端に複雑になってしまう。

変に複雑にして大して嬉しくないくらいなら、サポートしない方が良かろう。 ファイルは追加されるだけで、変更はされないという前提で動く。

基本的にuitが管理するツリーは、他の人は変更しないようにした方が良さそう。 ただし新しいファイルを追加する分には問題無い。 また、理論上はリンクがなければ古いハッシュ値の情報を消す事は出来る。 それでほとんどのケースはカバー出来るので、必要になったらその位は頑張ってもいいかもしれない。

安全なsyncについての手順

安全にsyncをする場合、例えば一旦日付のフォルダなどにコピーして、それを移動するのはどうだろう?

uitrootがレポジトリのルートとして、以下のようにすると、

$ cd uitroot
$ uit import /somewhere/directory 2020_1220
$ uit mv 2020_1220/* ./

ハッシュ値が同じファイルがある場合はmvはsrcのファイルを削除するだけ、 ハッシュ値が違うファイルがあった時は移動せずにそのまま残す。

こうする事で、コンフリクトしたファイルだけ残る。

日付じゃなくて最初からconflictフォルダにコピーしてからmvする、 というふうに振る舞えば、残るのは自然とconflicしたファイルだけになるのでそれでいいかもしれない。 ただ最初は原始的なコマンドを組み合わせて運用していきたいな。

実装状況とか

とりあえずinitしてimportして同じハッシュのファイルを一つだけ残して他はリンク(という名の空ファイル)にする、という機能までは出来た。 importはハッシュが一致しているファイルがある場合は空ファイル作るだけなので一瞬で終わる。 ここまでで1000行ちょいくらい。短いねぇ。さすがF#。 ただ結構実装時間はかかっているし、なかなか複雑な関数も出てきた。

あとはcpとmvを実装すればとりあえずローカルでは動くようになる。

Google Play Musicから引き上げた大量の音楽ファイルのうち、microSDに入ってないファイルだけをコピーしたい、というのが一番切実な開発動機なのだが、 それに関してはcpとmvが完成すれば出来るはず(ただまだ全然テストしてないのでいきなり実践投入は怖いが)。

cpとmvが出来たら、次は2つのrepo間のsyncというかバックアップを考えたい。 全ファイルツリーをなめるよりも、 双方のハッシュ値の一覧を比較して、無いのだけコピーすれば十分なんじゃないか、という気がする。 これだと新しく作られた既存ファイルのリンクとかはコピーされない訳だが、 バックアップという用途では少なくとも1つファイルがデータとして存在する事が大切なのであって、 探せば見つかるならバックアップ的にはいいんじゃないか。

200GBくらいあるので全部のファイルのハッシュ値を計算するのは嫌なんだよねぇ。 全ディレクトリの.uit/dirs下をなめるくらいはそんな大変でも無いかもしれないが… その位はやった方がいいか? でもツリーを比較するよりも無いのを足すだけの方が絶対簡単だからなぁ。 実装が楽な方がバグも少ないし信頼性高そうな気もする。

よし、そうしよう。実装が楽な仕様にしていく。

cpとmvまで出来たらコマンドラインインターフェースを作ってドッグフードを開始しようかなぁ。