BizHawk/BizHawk.Client.Common/SevenZipWriter.cs

409 lines
8.2 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
{
const int LEN = 4096;
const int MASK = 4095;
byte[] buff = new byte[LEN];
int wpos = 0;
int rpos = 0;
bool writeclosed;
bool readclosed;
object sharedlock = new object();
ManualResetEvent full = new ManualResetEvent(true);
ManualResetEvent empty = new ManualResetEvent(false);
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)
{
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
2017-05-17 18:18:26 +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)
{
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();
}
}
}
2017-05-17 18:18:26 +00:00
return ret;
}
public void CloseRead()
{
lock (sharedlock)
{
readclosed = true;
full.Set();
}
}
2014-10-13 05:08:34 +00:00
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;
}
else if (readclosed)
{
return false;
}
else
{
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
2017-05-17 18:18:26 +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)
{
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();
}
}
}
2017-05-17 18:18:26 +00:00
return ret;
}
public void CloseWrite()
{
lock (sharedlock)
{
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
{
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return false; } }
public override void Flush() { }
public override long Length { get { return 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);
}
}
}
private SevenZip.SevenZipCompressor svc;
private bool first = true;
private string path;
private int compressionlevel;
public SevenZipWriter(string path, int compressionlevel)
{
this.path = path;
this.compressionlevel = compressionlevel;
2017-05-18 16:36:38 +00:00
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();
}
2017-05-17 18:18:26 +00:00
task.Wait();
}
public void Dispose()
{
// nothing to do
}
}
}