-
Notifications
You must be signed in to change notification settings - Fork 569
Expand file tree
/
Copy pathAssemblyCompression.cs
More file actions
106 lines (87 loc) · 3.39 KB
/
AssemblyCompression.cs
File metadata and controls
106 lines (87 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System;
using System.Buffers;
using System.IO;
using K4os.Compression.LZ4;
namespace Xamarin.Android.Tasks
{
class AssemblyCompression
{
public enum CompressionResult
{
Success,
InputTooBig,
EncodingFailed,
}
public sealed class AssemblyData
{
public string SourcePath { get; internal set; }
public uint DescriptorIndex { get; internal set; }
public string DestinationPath;
public uint SourceSize;
public uint DestinationSize;
public AssemblyData (string sourcePath, uint descriptorIndex)
{
SetData (sourcePath, descriptorIndex);
}
public void SetData (string sourcePath, uint descriptorIndex)
{
if (String.IsNullOrEmpty (sourcePath))
throw new ArgumentException ("must not be null or empty", nameof (sourcePath));
SourcePath = sourcePath;
DescriptorIndex = descriptorIndex;
}
}
const uint CompressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
// TODO: consider making it configurable via an MSBuild property, would be more flexible this way
//
// Arbitrary limit of the input assembly size, to clamp down on memory allocation. Our unlinked Mono.Android.dll
// assembly (the biggest one we have) is currently (May 2020) around 27MB, so let's bump the value to 30MB times
// two - it should be more than enough for most needs.
//public const ulong InputAssemblySizeLimit = 60 * 1024 * 1024;
static readonly ArrayPool<byte> bytePool = ArrayPool<byte>.Shared;
public static CompressionResult Compress (AssemblyData data, string outputDirectory)
{
if (data == null)
throw new ArgumentNullException (nameof (data));
if (String.IsNullOrEmpty (outputDirectory))
throw new ArgumentException ("must not be null or empty", nameof (outputDirectory));
Directory.CreateDirectory (outputDirectory);
var fi = new FileInfo (data.SourcePath);
if (!fi.Exists)
throw new InvalidOperationException ($"File '{data.SourcePath}' does not exist");
// if ((ulong)fi.Length > InputAssemblySizeLimit) {
// return CompressionResult.InputTooBig;
// }
data.DestinationPath = Path.Combine (outputDirectory, $"{Path.GetFileName (data.SourcePath)}.lz4");
data.SourceSize = (uint)fi.Length;
byte[] sourceBytes = null;
byte[] destBytes = null;
try {
sourceBytes = bytePool.Rent (checked((int)fi.Length));
using (var fs = File.Open (data.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
fs.Read (sourceBytes, 0, (int)fi.Length);
}
destBytes = bytePool.Rent (LZ4Codec.MaximumOutputSize (sourceBytes.Length));
int encodedLength = LZ4Codec.Encode (sourceBytes, 0, checked((int)fi.Length), destBytes, 0, destBytes.Length, LZ4Level.L12_MAX);
if (encodedLength < 0)
return CompressionResult.EncodingFailed;
data.DestinationSize = (uint)encodedLength;
using (var fs = File.Open (data.DestinationPath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
using (var bw = new BinaryWriter (fs)) {
bw.Write (CompressedDataMagic); // magic
bw.Write (data.DescriptorIndex); // index into runtime array of descriptors
bw.Write (checked((uint)fi.Length)); // file size before compression
bw.Write (destBytes, 0, encodedLength);
bw.Flush ();
}
}
} finally {
if (sourceBytes != null)
bytePool.Return (sourceBytes);
if (destBytes != null)
bytePool.Return (destBytes);
}
return CompressionResult.Success;
}
}
}