mirror of
https://github.com/chylex/Brotli-Builder.git
synced 2024-12-22 16:42:46 +01:00
266 lines
12 KiB
C#
266 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using BrotliBuilder.Utils;
|
|
using BrotliLib.Brotli;
|
|
using BrotliLib.Brotli.Dictionary;
|
|
using BrotliLib.Brotli.Encode;
|
|
using BrotliLib.Brotli.Output;
|
|
using BrotliLib.Brotli.Parameters;
|
|
using BrotliLib.Markers;
|
|
using BrotliLib.Serialization;
|
|
|
|
namespace BrotliBuilder.State{
|
|
sealed class BrotliFileController{
|
|
public BrotliSerializationParameters SerializationParameters { get; set; } = BrotliSerializationParameters.Default;
|
|
public BrotliCompressionParameters CompressionParameters { get; set; } = BrotliCompressionParameters.Default;
|
|
|
|
public MarkerLevel BitMarkerLevel { get; set; } = MarkerLevel.Verbose;
|
|
|
|
public event EventHandler<StateChangedEventArgs>? StateChanged;
|
|
public BrotliFileStructure? CurrentFile => (state as BrotliFileState.Loaded)?.File;
|
|
private BrotliFileState state = new BrotliFileState.NoFile();
|
|
|
|
private readonly AsyncWorker worker;
|
|
private int currentToken;
|
|
|
|
private readonly List<BrotliFileState> replay = new List<BrotliFileState>();
|
|
|
|
public BrotliFileController(string name){
|
|
worker = new AsyncWorker(name);
|
|
}
|
|
|
|
// State handling
|
|
|
|
private void StartWorker(Action<CancellationToken, int> work){
|
|
int token = ++currentToken;
|
|
worker.Start(cancel => work((CancellationToken)cancel, token));
|
|
}
|
|
|
|
private void UpdateState(int token, BrotliFileState newState) => worker.Sync(() => {
|
|
if (token != currentToken){
|
|
Debug.WriteLine("Ignored state change due to mismatched token (" + token + " != " + currentToken + ")");
|
|
return;
|
|
}
|
|
|
|
BrotliFileState prevState = state;
|
|
state = newState;
|
|
|
|
if (state is BrotliFileState.Starting){
|
|
replay.Clear();
|
|
}
|
|
|
|
replay.Add(state);
|
|
|
|
StateChanged?.Invoke(this, new StateChangedEventArgs(prevState, newState));
|
|
});
|
|
|
|
private bool Check(CancellationToken cancel){
|
|
return cancel.IsCancellationRequested;
|
|
}
|
|
|
|
// Public triggers
|
|
|
|
public void ReplayOver(BrotliFileController target){
|
|
int token = ++target.currentToken;
|
|
|
|
foreach(var replayedState in replay){
|
|
target.UpdateState(token, replayedState);
|
|
}
|
|
}
|
|
|
|
public void ResetToNothing(){
|
|
worker.Abort();
|
|
UpdateState(++currentToken, new BrotliFileState.NoFile());
|
|
}
|
|
|
|
public void ResetToWaiting(){
|
|
worker.Abort();
|
|
UpdateState(++currentToken, new BrotliFileState.Waiting());
|
|
}
|
|
|
|
public void ResetToEmpty(){
|
|
LoadStructure(BrotliFileStructure.NewEmpty());
|
|
}
|
|
|
|
#pragma warning disable IDE0011 // Add braces
|
|
|
|
public void LoadFile(string path) => StartWorker((cancel, token) => {
|
|
UpdateState(token, new BrotliFileState.Starting());
|
|
|
|
if (!TryReadFile(token, path, out byte[] bytes) || Check(cancel)) return;
|
|
BitStream bits = new BitStream(bytes);
|
|
UpdateState(token, new BrotliFileState.HasBits(bits.ToString(), null));
|
|
|
|
if (!TryDeserialize(token, new BitStream(bytes), out BrotliFileStructure structure, out MarkerRoot markerRoot, out Stopwatch swDeserialize) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasMarkers(markerRoot));
|
|
UpdateState(token, new BrotliFileState.HasStructure(structure, swDeserialize));
|
|
|
|
if (!TryDecompress(token, structure, out BrotliOutputStored output, out Stopwatch swOutput) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasOutput(null, output.AsBytes, swOutput));
|
|
|
|
UpdateState(token, new BrotliFileState.Loaded(structure, bits, output, markerRoot));
|
|
});
|
|
|
|
public void LoadStructure(BrotliFileStructure structure, byte[]? checkAgainst = null) => StartWorker((cancel, token) => {
|
|
UpdateState(token, new BrotliFileState.Starting());
|
|
UpdateState(token, new BrotliFileState.HasStructure(structure, null));
|
|
|
|
if (!TrySerialize(token, structure, out BitStream bits, out Stopwatch swSerialization) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasBits(bits.ToString(), swSerialization));
|
|
|
|
if (!TryDeserialize(token, bits, out _, out MarkerRoot markerRoot, out _) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasMarkers(markerRoot));
|
|
|
|
if (!TryDecompress(token, structure, out BrotliOutputStored output, out Stopwatch swOutput) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasOutput(checkAgainst, output.AsBytes, swOutput));
|
|
|
|
UpdateState(token, new BrotliFileState.Loaded(structure, bits, output, markerRoot));
|
|
});
|
|
|
|
private void EncodeInternal(string path, Func<byte[], BrotliFileStructure> structureGenerator) => StartWorker((cancel, token) => {
|
|
UpdateState(token, new BrotliFileState.Starting());
|
|
|
|
if (!TryReadFile(token, path, out byte[] bytes) || Check(cancel)) return;
|
|
if (!TryEncode(token, bytes, structureGenerator, out BrotliFileStructure structure, out Stopwatch swEncode) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasStructure(structure, swEncode));
|
|
|
|
if (!TrySerialize(token, structure, out BitStream bits, out Stopwatch swSerialization) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasBits(bits.ToString(), swSerialization));
|
|
|
|
if (!TryDeserialize(token, bits, out _, out MarkerRoot markerRoot, out _) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasMarkers(markerRoot));
|
|
|
|
if (!TryDecompress(token, structure, out BrotliOutputStored output, out Stopwatch swOutput) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasOutput(bytes, output.AsBytes, swOutput));
|
|
|
|
UpdateState(token, new BrotliFileState.Loaded(structure, bits, output, markerRoot));
|
|
});
|
|
|
|
public void EncodeFile(string path, BrotliFileParameters parameters, IBrotliEncoder encoder){
|
|
EncodeInternal(path, bytes => BrotliFileStructure.FromEncoder(parameters, CompressionParameters, bytes, encoder));
|
|
}
|
|
|
|
public void EncodeFile(string path, BrotliEncodePipeline pipeline, BrotliDictionary dictionary){
|
|
EncodeInternal(path, bytes => pipeline.Apply(bytes, dictionary));
|
|
}
|
|
|
|
private void TransformInternal(BrotliFileStructure structure, IBrotliTransformer transformer) => StartWorker((cancel, token) => {
|
|
UpdateState(token, new BrotliFileState.Starting());
|
|
|
|
TryDecompress(token, structure, out BrotliOutputStored prevOutput, out _);
|
|
if (Check(cancel)) return;
|
|
|
|
if (!TryTransform(token, structure, transformer, out structure, out Stopwatch swTransform) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasStructure(structure, swTransform));
|
|
|
|
if (!TrySerialize(token, structure, out BitStream bits, out Stopwatch swSerialization) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasBits(bits.ToString(), swSerialization));
|
|
|
|
if (!TryDeserialize(token, bits, out _, out MarkerRoot markerRoot, out _) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasMarkers(markerRoot));
|
|
|
|
if (!TryDecompress(token, structure, out BrotliOutputStored output, out Stopwatch swOutput) || Check(cancel)) return;
|
|
UpdateState(token, new BrotliFileState.HasOutput(prevOutput?.AsBytes ?? Array.Empty<byte>(), output.AsBytes, swOutput));
|
|
|
|
UpdateState(token, new BrotliFileState.Loaded(structure, bits, output, markerRoot));
|
|
});
|
|
|
|
#pragma warning restore IDE0011 // Add braces
|
|
|
|
public bool Transform(IBrotliTransformer transformer){
|
|
if (state is BrotliFileState.Loaded loaded){
|
|
TransformInternal(loaded.File, transformer);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Steps
|
|
|
|
private bool OnError(int token, ErrorType source, Exception exception){
|
|
UpdateState(token, new BrotliFileState.Error(source, exception));
|
|
return false;
|
|
}
|
|
|
|
private bool TryReadFile(int token, string path, out byte[] bytes){
|
|
try{
|
|
bytes = File.ReadAllBytes(path);
|
|
return true;
|
|
}catch(Exception ex){
|
|
bytes = null!;
|
|
return OnError(token, ErrorType.ReadingFile, ex);
|
|
}
|
|
}
|
|
|
|
private bool TryDeserialize(int token, BitStream bits, out BrotliFileStructure structure, out MarkerRoot markerRoot, out Stopwatch stopwatch){
|
|
try{
|
|
stopwatch = Stopwatch.StartNew();
|
|
(structure, markerRoot) = BrotliFileStructure.FromBytes(bits, BitMarkerLevel);
|
|
stopwatch.Stop();
|
|
return true;
|
|
}catch(Exception ex){
|
|
structure = null!;
|
|
markerRoot = null!;
|
|
stopwatch = null!;
|
|
return OnError(token, ErrorType.DeserializingFile, ex);
|
|
}
|
|
}
|
|
|
|
private bool TrySerialize(int token, BrotliFileStructure structure, out BitStream bits, out Stopwatch stopwatch){
|
|
try{
|
|
stopwatch = Stopwatch.StartNew();
|
|
bits = structure.Serialize(SerializationParameters);
|
|
stopwatch.Stop();
|
|
return true;
|
|
}catch(Exception ex){
|
|
bits = null!;
|
|
stopwatch = null!;
|
|
return OnError(token, ErrorType.SerializingStructure, ex);
|
|
}
|
|
}
|
|
|
|
private bool TryEncode(int token, byte[] bytes, Func<byte[], BrotliFileStructure> structureGenerator, out BrotliFileStructure file, out Stopwatch stopwatch){
|
|
try{
|
|
stopwatch = Stopwatch.StartNew();
|
|
file = structureGenerator(bytes);
|
|
stopwatch.Stop();
|
|
return true;
|
|
}catch(Exception ex){
|
|
file = null!;
|
|
stopwatch = null!;
|
|
return OnError(token, ErrorType.EncodingBytes, ex);
|
|
}
|
|
}
|
|
|
|
private bool TryTransform(int token, BrotliFileStructure structure, IBrotliTransformer transformer, out BrotliFileStructure transformed, out Stopwatch stopwatch){
|
|
try{
|
|
stopwatch = Stopwatch.StartNew();
|
|
transformed = structure.Transform(transformer, CompressionParameters);
|
|
stopwatch.Stop();
|
|
return true;
|
|
}catch(Exception ex){
|
|
transformed = null!;
|
|
stopwatch = null!;
|
|
return OnError(token, ErrorType.TransformingStructure, ex);
|
|
}
|
|
}
|
|
|
|
private bool TryDecompress(int token, BrotliFileStructure structure, out BrotliOutputStored output, out Stopwatch stopwatch){
|
|
try{
|
|
stopwatch = Stopwatch.StartNew();
|
|
output = structure.Decompress();
|
|
stopwatch.Stop();
|
|
return true;
|
|
}catch(Exception ex){
|
|
output = null!;
|
|
stopwatch = null!;
|
|
return OnError(token, ErrorType.DecompressingStructure, ex);
|
|
}
|
|
}
|
|
}
|
|
}
|