Skip to content

Commit c06648c

Browse files
authored
implement compression (#16)
* implement compression * rm hyperbole * deflate vs. compress wording
1 parent 5b14963 commit c06648c

3 files changed

Lines changed: 739 additions & 0 deletions

File tree

‎src/deflate.go‎

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"compress/zlib"
6+
"io"
7+
"os"
8+
"strings"
9+
)
10+
11+
// minDeflateSize is the minimum payload size in bytes before compression is
12+
// attempted.
13+
const minDeflateSize uint32 = 512
14+
15+
// incompressibleTypes lists media types (lowercased, without parameters) that
16+
// are already compressed or otherwise unlikely to benefit from zlib-deflate.
17+
var incompressibleTypes = map[string]bool{
18+
// images
19+
"image/jpeg": true, "image/png": true, "image/gif": true,
20+
"image/webp": true, "image/heic": true, "image/avif": true,
21+
"image/apng": true,
22+
// audio
23+
"audio/aac": true, "audio/mpeg": true, "audio/ogg": true,
24+
"audio/opus": true, "audio/webm": true,
25+
// video
26+
"video/h264": true, "video/h265": true, "video/h266": true,
27+
"video/ogg": true, "video/vp8": true, "video/vp9": true,
28+
"video/webm": true,
29+
// archives / compressed containers
30+
"application/gzip": true, "application/zip": true,
31+
"application/epub+zip": true,
32+
"application/octet-stream": true,
33+
// zip-based office formats
34+
"application/vnd.oasis.opendocument.presentation": true,
35+
"application/vnd.oasis.opendocument.spreadsheet": true,
36+
"application/vnd.oasis.opendocument.text": true,
37+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": true,
38+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": true,
39+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": true,
40+
"application/vnd.amazon.ebook": true,
41+
// fonts (compressed)
42+
"font/woff": true, "font/woff2": true,
43+
// pdf (internally compressed)
44+
"application/pdf": true,
45+
// 3d models (compressed containers)
46+
"model/3mf": true, "model/gltf-binary": true,
47+
"model/vnd.usdz+zip": true,
48+
}
49+
50+
// shouldCompress reports whether compression should be attempted for a payload
51+
// with the given media type and size. It returns false for payloads that are
52+
// too small or use a media type known to be already compressed.
53+
func shouldCompress(mediaType string, dataSize uint32) bool {
54+
if dataSize < minDeflateSize {
55+
return false
56+
}
57+
t := strings.ToLower(mediaType)
58+
if i := strings.IndexByte(t, ';'); i >= 0 {
59+
t = strings.TrimRight(t[:i], " ")
60+
}
61+
return !incompressibleTypes[t]
62+
}
63+
64+
// deflateSampleSize is the number of bytes sampled from the start of a file
65+
// to estimate compressibility before committing to a full-file compression
66+
// pass. Chosen large enough for zlib to find patterns but small enough to be
67+
// fast even on very large files.
68+
const deflateSampleSize = 8192
69+
70+
// probeSample compresses up to deflateSampleSize bytes from the start of src
71+
// and reports whether the ratio looks promising (compressed < 80% of input).
72+
// src is seeked back to the start on return.
73+
func probeSample(src *os.File, srcSize uint32) (bool, error) {
74+
sampleLen := int64(deflateSampleSize)
75+
if int64(srcSize) < sampleLen {
76+
sampleLen = int64(srcSize)
77+
}
78+
79+
var buf bytes.Buffer
80+
zw := zlib.NewWriter(&buf)
81+
if _, err := io.CopyN(zw, src, sampleLen); err != nil {
82+
_ = zw.Close()
83+
return false, err
84+
}
85+
if err := zw.Close(); err != nil {
86+
return false, err
87+
}
88+
89+
if _, err := src.Seek(0, io.SeekStart); err != nil {
90+
return false, err
91+
}
92+
93+
return int64(buf.Len()) < sampleLen*8/10, nil
94+
}
95+
96+
// tryCompress compresses the file at srcPath using zlib-deflate and writes the
97+
// result to a temporary file. For files larger than deflateSampleSize it first
98+
// compresses a prefix sample to estimate compressibility, avoiding a full pass
99+
// over files that won't compress well. It returns worthwhile=true only when
100+
// the compressed output is less than 80% of the original size (at least a 20%
101+
// reduction). When not worthwhile the temporary file is removed. When
102+
// worthwhile the caller is responsible for removing the file at dstPath.
103+
func tryCompress(srcPath string, srcSize uint32) (dstPath string, compressedSize uint32, worthwhile bool, err error) {
104+
src, err := os.Open(srcPath)
105+
if err != nil {
106+
return "", 0, false, err
107+
}
108+
defer src.Close()
109+
110+
// For files larger than the sample size, probe a prefix first.
111+
if srcSize > deflateSampleSize {
112+
promising, err := probeSample(src, srcSize)
113+
if err != nil {
114+
return "", 0, false, err
115+
}
116+
if !promising {
117+
return "", 0, false, nil
118+
}
119+
}
120+
121+
dst, err := os.CreateTemp("", "fmsg-deflate-*")
122+
if err != nil {
123+
return "", 0, false, err
124+
}
125+
dstName := dst.Name()
126+
127+
zw := zlib.NewWriter(dst)
128+
if _, err := io.Copy(zw, src); err != nil {
129+
_ = zw.Close()
130+
_ = dst.Close()
131+
_ = os.Remove(dstName)
132+
return "", 0, false, err
133+
}
134+
if err := zw.Close(); err != nil {
135+
_ = dst.Close()
136+
_ = os.Remove(dstName)
137+
return "", 0, false, err
138+
}
139+
if err := dst.Close(); err != nil {
140+
_ = os.Remove(dstName)
141+
return "", 0, false, err
142+
}
143+
144+
fi, err := os.Stat(dstName)
145+
if err != nil {
146+
_ = os.Remove(dstName)
147+
return "", 0, false, err
148+
}
149+
150+
cSize := uint32(fi.Size())
151+
if cSize >= srcSize*8/10 {
152+
_ = os.Remove(dstName)
153+
return "", 0, false, nil
154+
}
155+
156+
return dstName, cSize, true, nil
157+
}

0 commit comments

Comments
 (0)