C#でSharpCompress+XZ.NETを使ってtar.xz圧縮/解凍をする

非常に圧縮率の高いLZMAフォーマットを用いたファイル圧縮として[xzフォーマット](https://tukaani.org/xz/format.html)があります。LZMAフォーマットは[7zip](http://www.7-zip.org/)でも用いられていて、7zipもxzも同じぐらいファイルサイズが縮みます。xz自体はアーカイブではないので、gzipと同じようにtarで固めてから使います。

7zipと大きく違うところは、7zipがWindowsに特化したフォーマットであるのに対して、xzはLinux環境でも簡単に圧縮できます。OSコマンドからもできますし、Pythonのデフォルトのモジュールのtarfileを用いれば、バージョン3.3以降lzma圧縮ができるようになっているので、tar.gz、tar.bz2と同様にtar.xzも解凍できます（7zipはPythonで探しましたが、libarchiveのラッパーしかなくちょっと面倒くさい）。**デフォルトのモジュールなので、Pythonさえ使えればルート権限なく高圧縮率のtar.xzが使えるはず**です（レンタルサーバーで便利かもしれません）。

どのぐらい縮むのかというと、63MBのテキストが1.9MBまで縮みました（おおよそ1/33）。

C#から圧縮・解凍する場合として、tar用のライブラリとして[SharpCompress](https://github.com/adamhathcock/sharpcompress)と、XZ用のライブラリとして[XZ.NET](https://github.com/RomanBelkov/XZ.NET)を用いる例を紹介します。他に~~[LZMA SDK](http://7-zip.org/sdk.html)~~**訂正：[XZ Utils](https://tukaani.org/xz/)からxz-5.2.3-windows.zipをダウンロードして、その中にあるliblzma.dll**が必須になります。解凍するとアーキテクチャーが複数出てきますが、x86ビルドの場合は「bin_i686-sse2」のliblzma.dllを使いました。**liblzma.dllをDebugやReleaseフォルダにコピーしておきます**（参照の追加は不要です）。

環境：.NET Framework 4.5.2 / SharpCompress v0.19.2(2017/12/6) / XZ.NET v1.2.2(2015/10/28) / LZMA SDK v18.01(2018/1/28)

##圧縮
```csharp
using System.IO;
using SharpCompress.Writers;
using XZ.NET;

public static void Compress(string sourceDir, string destArchivePath)
{
    using (var outFileStream = new FileStream(destArchivePath, FileMode.Create, FileAccess.Write))
    using (var ms = new MemoryStream())
    {
        using (var tarWriter = WriterFactory.Open(ms, SharpCompress.Common.ArchiveType.Tar,
            new WriterOptions(SharpCompress.Common.CompressionType.None) { LeaveStreamOpen = true }))
        {
            tarWriter.WriteAll(sourceDir, "*", SearchOption.AllDirectories);
        }

        ms.Position = 0;
        using (var xzStream = new XZOutputStream(outFileStream))
        {
            var buf = new byte[1 * 1024 * 1024];
            var bytesRead = (long)0;

            while (bytesRead < ms.Length)
            {
                var count = ms.Read(buf, 0, buf.Length);
                xzStream.Write(buf, 0, count);
                bytesRead += count;
            }
        }
    }
}
```
バッファーを固定長ではなく、ms.ToArray()してバイナリをxzStreamにWriteさせるとエラーになったのでご注意ください。WinRARでもPythonでも読めたので特に問題ないかと思います。

##フォルダに解凍
```csharp
using System.IO;
using SharpCompress.Readers;
using XZ.NET;

public static void DecompressToDirectory(string sourceArchive, string destDirectory)
{
    using (var inFileStream = new FileStream(sourceArchive, FileMode.Open, FileAccess.Read))
    using (var xzStream = new XZInputStream(inFileStream))
    using (var ms = new MemoryStream())
    {
        xzStream.CopyTo(ms);
        ms.Position = 0;

        using (var tarReader = ReaderFactory.Open(ms))
        {
            while (tarReader.MoveToNextEntry())
            {
                if (!tarReader.Entry.IsDirectory)
                {
                    tarReader.WriteEntryToDirectory(destDirectory, new ExtractionOptions()
                    {
                        ExtractFullPath = true,
                        Overwrite = true
                    });
                }
            }
        }
    }
}
```
ごく普通の解凍です。

##メモリ上に解凍
```csharp
using System.IO;
using SharpCompress.Readers;
using XZ.NET;

public static void DecompressToMemory(string sourceArchive)
{
    using (var inFileStream = new FileStream(sourceArchive, FileMode.Open, FileAccess.Read))
    using (var xzStream = new XZInputStream(inFileStream))
    using (var tarMs = new MemoryStream())
    {
        xzStream.CopyTo(tarMs);
        tarMs.Position = 0;

        using (var tarReader = ReaderFactory.Open(tarMs))
        {
            while (tarReader.MoveToNextEntry())
            {
                if (!tarReader.Entry.IsDirectory)
                {
                    using(var entryMs = new MemoryStream())
                    using (var entryStream = tarReader.OpenEntryStream())
                    {
                        entryStream.CopyTo(entryMs);
                        var binary = entryMs.ToArray();
                        var str = Encoding.UTF8.GetString(binary);
                        //Console.WriteLine(str);
                    }
                }
            }
        }
    }
}
```
ダイレクトに変数に格納したいときなど。

##その他注意
SharpCompressのドキュメント読んでたらこんなことが書いてありました。

> XZ has known holes explained here: (http://www.nongnu.org/lzip/xz_inadequate.html) Use Tar/LZip for LZMA compression instead.

要するにXZは長期間の保存には不適切だ、LZIPをかわりに使えと。ただLZIPはPythonのデフォルトではインストールされていないので、モジュールのインストールにルート権限必要なのですよね。

tar.xzは転送量をケチってダウンロードさせたいときに結構便利なフォーマットだと思います。