BizHawk/BizHawk.Client.Common/SevenZipWriter.cs

410 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
namespace BizHawk.Client.Common
{
public class SevenZipWriter : IZipWriter
{
private class RangBuffer
{
private const int Len = 4096;
private const int Mask = 4095;
private readonly byte[] _buff = new byte[Len];
private readonly object _sharedlock = new object();
private readonly ManualResetEvent _full = new ManualResetEvent(true);
private readonly ManualResetEvent _empty = new ManualResetEvent(false);
private int _wpos;
private int _rpos;
private bool _writeclosed;
private bool _readclosed;
public Stream W { get; }
public Stream R { get; }
public RangBuffer()
{
W = new WStream(this);
R = new RStream(this);
}
public int ReadByte()
{
// slow, but faster than using the other overload with byte[1]
while (true)
{
_empty.WaitOne();
lock (_sharedlock)
{
if (_rpos != _wpos)
{
byte ret = _buff[_rpos++];
_rpos &= Mask;
_full.Set();
return ret;
}
else if (_writeclosed)
{
return -1;
}
else
{
_empty.Reset();
}
}
}
}
public int Read(byte[] buffer, int offset, int count)
{
int ret = 0;
while (count > 0)
{
_empty.WaitOne();
lock (_sharedlock)
{
int start = _rpos;
int end = _wpos;
if (end < start) // wrap
{
end = Len;
}
if (end - start > count)
{
end = start + count;
}
int c = end - start;
if (c > 0)
{
Buffer.BlockCopy(_buff, start, buffer, offset, c);
count -= c;
ret += c;
offset += c;
_rpos = end & Mask;
_full.Set();
}
else if (_writeclosed)
{
break;
}
else
{
_empty.Reset();
}
}
}
return ret;
}
public void CloseRead()
{
lock (_sharedlock)
{
_readclosed = true;
_full.Set();
}
}
public bool WriteByte(byte value)
{
while (true)
{
_full.WaitOne();
lock (_sharedlock)
{
int next = (_wpos + 1) & Mask;
if (next != _rpos)
{
_buff[_wpos] = value;
_wpos = next;
_empty.Set();
return true;
}
if (_readclosed)
{
return false;
}
_full.Reset();
}
}
}
public int Write(byte[] buffer, int offset, int count)
{
int ret = 0;
while (count > 0)
{
_full.WaitOne();
lock (_sharedlock)
{
int start = _wpos;
int end = (_rpos - 1) & Mask;
if (end < start) // wrap
{
end = Len;
}
if (end - start > count)
{
end = start + count;
}
int c = end - start;
if (c > 0)
{
Buffer.BlockCopy(buffer, offset, _buff, start, c);
count -= c;
ret += c;
offset += c;
_wpos = end & Mask;
_empty.Set();
}
else if (_readclosed)
{
break;
}
else
{
_full.Reset();
}
}
}
return ret;
}
public void CloseWrite()
{
lock (_sharedlock)
{
_writeclosed = true;
_empty.Set();
}
}
private class WStream : Stream
{
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override void Flush() { }
public override long Length { get { throw new NotSupportedException(); } }
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
public override void SetLength(long value) { throw new NotSupportedException(); }
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
private RangBuffer _r;
private long _total; // bytes written so far
public WStream(RangBuffer r)
{
_r = r;
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
#if true
int cnt = _r.Write(buffer, offset, count);
_total += cnt;
if (cnt < count)
{
throw new IOException("broken pipe");
}
#else
int end = offset + count;
while (offset < end)
{
WriteByte(buffer[offset++]);
_total++;
}
#endif
}
public override void WriteByte(byte value)
{
if (!_r.WriteByte(value))
{
throw new IOException("broken pipe");
}
}
protected override void Dispose(bool disposing)
{
if (disposing && _r != null)
{
_r.CloseWrite();
_r = null;
}
base.Dispose(disposing);
}
}
private class RStream : Stream
{
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override void Flush() { }
public override long Length => 1; // { get { throw new NotSupportedException(); } }
public override long Seek(long offset, SeekOrigin origin) { return 0; } // { throw new NotSupportedException(); }
public override void SetLength(long value) { throw new NotSupportedException(); }
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
private RangBuffer _r;
private long _total; // bytes read so far
public RStream(RangBuffer r)
{
_r = r;
}
public override int Read(byte[] buffer, int offset, int count)
{
#if true
int cnt = _r.Read(buffer, offset, count);
_total += cnt;
return cnt;
#else
int ret = 0;
int end = offset + count;
while (offset < end)
{
int val = ReadByte();
if (val == -1)
break;
buffer[offset] = (byte)val;
offset++;
ret++;
_total++;
}
return ret;
#endif
}
public override int ReadByte()
{
return _r.ReadByte();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
protected override void Dispose(bool disposing)
{
if (disposing && _r != null)
{
_r.CloseRead();
_r = null;
}
base.Dispose(disposing);
}
}
}
private readonly SevenZip.SevenZipCompressor _svc;
private readonly string _path;
private bool _first = true;
private int _compressionlevel;
public SevenZipWriter(string path, int compressionlevel)
{
_path = path;
_compressionlevel = compressionlevel;
_svc = new SevenZip.SevenZipCompressor { ArchiveFormat = SevenZip.OutArchiveFormat.Zip };
switch (compressionlevel)
{
default:
case 0:
_svc.CompressionLevel = SevenZip.CompressionLevel.None;
break;
case 1:
case 2:
_svc.CompressionLevel = SevenZip.CompressionLevel.Fast;
break;
case 3:
case 4:
_svc.CompressionLevel = SevenZip.CompressionLevel.Low;
break;
case 5:
case 6:
_svc.CompressionLevel = SevenZip.CompressionLevel.Normal;
break;
case 7:
case 8:
_svc.CompressionLevel = SevenZip.CompressionLevel.High;
break;
case 9:
_svc.CompressionLevel = SevenZip.CompressionLevel.Ultra;
break;
}
}
public void WriteItem(string name, Action<Stream> callback)
{
var dict = new Dictionary<string, Stream>();
var r = new RangBuffer();
dict[name] = r.R;
if (_first)
{
_first = false;
_svc.CompressionMode = SevenZip.CompressionMode.Create;
}
else
{
_svc.CompressionMode = SevenZip.CompressionMode.Append;
}
var task = Task.Factory.StartNew(() =>
{
_svc.CompressStreamDictionary(dict, _path);
});
try
{
callback(r.W);
}
finally
{
r.W.Dispose();
}
task.Wait();
}
public void Dispose()
{
// nothing to do
}
}
}