410 lines
8.3 KiB
C#
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
|
|
}
|
|
}
|
|
}
|