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 callback) { var dict = new Dictionary(); 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 } } }