BizHawk/BizHawk.Client.Common/SevenZipWriter.cs

410 lines
8.3 KiB
C#
Raw Normal View History

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
{
2017-05-19 18:17:07 +00:00
private const int Len = 4096;
private const int Mask = 4095;
2017-05-19 18:17:07 +00:00
private readonly byte[] _buff = new byte[Len];
2017-05-19 18:17:07 +00:00
private readonly object _sharedlock = new object();
private readonly ManualResetEvent _full = new ManualResetEvent(true);
private readonly ManualResetEvent _empty = new ManualResetEvent(false);
2017-05-19 18:17:07 +00:00
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);
}
2014-10-13 05:08:34 +00:00
public int ReadByte()
{
// slow, but faster than using the other overload with byte[1]
while (true)
{
2017-05-19 18:17:07 +00:00
_empty.WaitOne();
lock (_sharedlock)
2014-10-13 05:08:34 +00:00
{
2017-05-19 18:17:07 +00:00
if (_rpos != _wpos)
2014-10-13 05:08:34 +00:00
{
2017-05-19 18:17:07 +00:00
byte ret = _buff[_rpos++];
_rpos &= Mask;
_full.Set();
2014-10-13 05:08:34 +00:00
return ret;
}
2017-05-19 18:17:07 +00:00
else if (_writeclosed)
2014-10-13 05:08:34 +00:00
{
return -1;
}
else
{
2017-05-19 18:17:07 +00:00
_empty.Reset();
2014-10-13 05:08:34 +00:00
}
}
}
}
public int Read(byte[] buffer, int offset, int count)
{
int ret = 0;
while (count > 0)
{
2017-05-19 18:17:07 +00:00
_empty.WaitOne();
lock (_sharedlock)
{
2017-05-19 18:17:07 +00:00
int start = _rpos;
int end = _wpos;
if (end < start) // wrap
2017-05-17 18:18:26 +00:00
{
2017-05-19 18:17:07 +00:00
end = Len;
2017-05-17 18:18:26 +00:00
}
if (end - start > count)
2017-05-17 18:18:26 +00:00
{
end = start + count;
2017-05-17 18:18:26 +00:00
}
int c = end - start;
if (c > 0)
{
2017-05-19 18:17:07 +00:00
Buffer.BlockCopy(_buff, start, buffer, offset, c);
count -= c;
ret += c;
offset += c;
2017-05-19 18:17:07 +00:00
_rpos = end & Mask;
_full.Set();
}
2017-05-19 18:17:07 +00:00
else if (_writeclosed)
{
break;
}
else
{
2017-05-19 18:17:07 +00:00
_empty.Reset();
}
}
}
2017-05-17 18:18:26 +00:00
return ret;
}
public void CloseRead()
{
2017-05-19 18:17:07 +00:00
lock (_sharedlock)
{
2017-05-19 18:17:07 +00:00
_readclosed = true;
_full.Set();
}
}
2014-10-13 05:08:34 +00:00
public bool WriteByte(byte value)
{
while (true)
{
2017-05-19 18:17:07 +00:00
_full.WaitOne();
lock (_sharedlock)
2014-10-13 05:08:34 +00:00
{
2017-05-19 18:17:07 +00:00
int next = (_wpos + 1) & Mask;
if (next != _rpos)
2014-10-13 05:08:34 +00:00
{
2017-05-19 18:17:07 +00:00
_buff[_wpos] = value;
_wpos = next;
_empty.Set();
2014-10-13 05:08:34 +00:00
return true;
}
2017-05-19 18:17:07 +00:00
if (_readclosed)
2014-10-13 05:08:34 +00:00
{
return false;
}
2017-05-19 18:17:07 +00:00
_full.Reset();
2014-10-13 05:08:34 +00:00
}
}
}
public int Write(byte[] buffer, int offset, int count)
{
int ret = 0;
while (count > 0)
{
2017-05-19 18:17:07 +00:00
_full.WaitOne();
lock (_sharedlock)
{
2017-05-19 18:17:07 +00:00
int start = _wpos;
int end = (_rpos - 1) & Mask;
if (end < start) // wrap
2017-05-17 18:18:26 +00:00
{
2017-05-19 18:17:07 +00:00
end = Len;
2017-05-17 18:18:26 +00:00
}
if (end - start > count)
2017-05-17 18:18:26 +00:00
{
end = start + count;
2017-05-17 18:18:26 +00:00
}
int c = end - start;
if (c > 0)
{
2017-05-19 18:17:07 +00:00
Buffer.BlockCopy(buffer, offset, _buff, start, c);
count -= c;
ret += c;
offset += c;
2017-05-19 18:17:07 +00:00
_wpos = end & Mask;
_empty.Set();
}
2017-05-19 18:17:07 +00:00
else if (_readclosed)
{
break;
}
else
{
2017-05-19 18:17:07 +00:00
_full.Reset();
}
}
}
2017-05-17 18:18:26 +00:00
return ret;
}
public void CloseWrite()
{
2017-05-19 18:17:07 +00:00
lock (_sharedlock)
{
2017-05-19 18:17:07 +00:00
_writeclosed = true;
_empty.Set();
}
}
private class WStream : Stream
{
2017-05-17 18:18:26 +00:00
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)
{
2014-10-13 05:08:34 +00:00
#if true
int cnt = _r.Write(buffer, offset, count);
_total += cnt;
if (cnt < count)
2017-05-17 18:18:26 +00:00
{
throw new IOException("broken pipe");
2017-05-17 18:18:26 +00:00
}
2014-10-13 05:08:34 +00:00
#else
int end = offset + count;
while (offset < end)
{
WriteByte(buffer[offset++]);
_total++;
}
#endif
}
public override void WriteByte(byte value)
{
if (!_r.WriteByte(value))
2017-05-17 18:18:26 +00:00
{
2014-10-13 05:08:34 +00:00
throw new IOException("broken pipe");
2017-05-17 18:18:26 +00:00
}
}
protected override void Dispose(bool disposing)
{
if (disposing && _r != null)
{
_r.CloseWrite();
_r = null;
}
2017-05-17 18:18:26 +00:00
base.Dispose(disposing);
}
}
2017-05-17 18:18:26 +00:00
private class RStream : Stream
{
2017-05-19 18:17:07 +00:00
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override void Flush() { }
2017-05-19 18:17:07 +00:00
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)
{
2014-10-13 05:08:34 +00:00
#if true
int cnt = _r.Read(buffer, offset, count);
_total += cnt;
return cnt;
2014-10-13 05:08:34 +00:00
#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;
}
2017-05-17 18:18:26 +00:00
base.Dispose(disposing);
}
}
}
2017-05-19 18:17:07 +00:00
private readonly SevenZip.SevenZipCompressor _svc;
private readonly string _path;
2017-05-19 18:17:07 +00:00
private bool _first = true;
private int _compressionlevel;
public SevenZipWriter(string path, int compressionlevel)
{
2017-05-19 18:17:07 +00:00
_path = path;
_compressionlevel = compressionlevel;
2017-05-19 18:17:07 +00:00
_svc = new SevenZip.SevenZipCompressor { ArchiveFormat = SevenZip.OutArchiveFormat.Zip };
switch (compressionlevel)
{
default:
case 0:
2017-05-19 18:17:07 +00:00
_svc.CompressionLevel = SevenZip.CompressionLevel.None;
break;
case 1:
case 2:
2017-05-19 18:17:07 +00:00
_svc.CompressionLevel = SevenZip.CompressionLevel.Fast;
break;
case 3:
case 4:
2017-05-19 18:17:07 +00:00
_svc.CompressionLevel = SevenZip.CompressionLevel.Low;
break;
case 5:
case 6:
2017-05-19 18:17:07 +00:00
_svc.CompressionLevel = SevenZip.CompressionLevel.Normal;
break;
case 7:
case 8:
2017-05-19 18:17:07 +00:00
_svc.CompressionLevel = SevenZip.CompressionLevel.High;
break;
case 9:
2017-05-19 18:17:07 +00:00
_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;
2017-05-19 18:17:07 +00:00
if (_first)
{
2017-05-19 18:17:07 +00:00
_first = false;
_svc.CompressionMode = SevenZip.CompressionMode.Create;
}
else
{
2017-05-19 18:17:07 +00:00
_svc.CompressionMode = SevenZip.CompressionMode.Append;
}
var task = Task.Factory.StartNew(() =>
{
2017-05-19 18:17:07 +00:00
_svc.CompressStreamDictionary(dict, _path);
});
try
{
callback(r.W);
}
finally
{
r.W.Dispose();
}
2017-05-17 18:18:26 +00:00
task.Wait();
}
public void Dispose()
{
// nothing to do
}
}
}