Skip to content

Commit 6f5227d

Browse files
committed
Add byteforamts
1 parent de028ff commit 6f5227d

File tree

3 files changed

+348
-0
lines changed

3 files changed

+348
-0
lines changed

common/byteformats/formats.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package byteformats
2+
3+
import (
4+
"fmt"
5+
"math"
6+
)
7+
8+
var (
9+
unitNames = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
10+
iUnitNames = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
11+
)
12+
13+
func formatBytes(s uint64, base float64, sizes []string) string {
14+
if s < 10 {
15+
return fmt.Sprintf("%d B", s)
16+
}
17+
e := math.Floor(logn(float64(s), base))
18+
suffix := sizes[int(e)]
19+
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
20+
f := "%.0f %s"
21+
if val < 10 {
22+
f = "%.1f %s"
23+
}
24+
25+
return fmt.Sprintf(f, val, suffix)
26+
}
27+
28+
func logn(n, b float64) float64 {
29+
return math.Log(n) / math.Log(b)
30+
}
31+
32+
func FormatBytes(s uint64) string {
33+
return formatBytes(s, 1000, unitNames)
34+
}
35+
36+
func FormatMemoryBytes(s uint64) string {
37+
return formatBytes(s, 1024, unitNames)
38+
}
39+
40+
func FormatIBytes(s uint64) string {
41+
return formatBytes(s, 1024, iUnitNames)
42+
}

common/byteformats/json.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package byteformats
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
7+
E "github.com/sagernet/sing/common/exceptions"
8+
"github.com/sagernet/sing/common/json"
9+
)
10+
11+
const (
12+
Byte = 1 << (iota * 10)
13+
KiByte
14+
MiByte
15+
GiByte
16+
TiByte
17+
PiByte
18+
EiByte
19+
)
20+
21+
const (
22+
KByte = Byte * 1000
23+
MByte = KByte * 1000
24+
GByte = MByte * 1000
25+
TByte = GByte * 1000
26+
PByte = TByte * 1000
27+
EByte = PByte * 1000
28+
)
29+
30+
var unitValueTable = map[string]uint64{
31+
"b": Byte,
32+
"k": KByte,
33+
"kb": KByte,
34+
"ki": KiByte,
35+
"kib": KiByte,
36+
"m": MByte,
37+
"mb": MByte,
38+
"mi": MiByte,
39+
"mib": MiByte,
40+
"g": GByte,
41+
"gb": GByte,
42+
"gi": GiByte,
43+
"gib": GiByte,
44+
"t": TByte,
45+
"tb": TByte,
46+
"ti": TiByte,
47+
"tib": TiByte,
48+
"p": PByte,
49+
"pb": PByte,
50+
"pi": PiByte,
51+
"pib": PiByte,
52+
"e": EByte,
53+
"eb": EByte,
54+
"ei": EiByte,
55+
"eib": EiByte,
56+
}
57+
58+
var memoryUnitValueTable = map[string]uint64{
59+
"b": Byte,
60+
"k": KiByte,
61+
"kb": KiByte,
62+
"m": MiByte,
63+
"mb": MiByte,
64+
"g": GiByte,
65+
"gb": GiByte,
66+
"t": TiByte,
67+
"tb": TiByte,
68+
"p": PiByte,
69+
"pb": PiByte,
70+
"e": EiByte,
71+
"eb": EiByte,
72+
}
73+
74+
var networkUnitValueTable = map[string]uint64{
75+
"Bps": Byte,
76+
"Kbps": KByte / 8,
77+
"KBps": KByte,
78+
"Mbps": MByte / 8,
79+
"MBps": MByte,
80+
"Gbps": GByte / 8,
81+
"GBps": GByte,
82+
"Tbps": TByte / 8,
83+
"TBps": TByte,
84+
"Pbps": PByte / 8,
85+
"PBps": PByte,
86+
"Ebps": EByte / 8,
87+
"EBps": EByte,
88+
}
89+
90+
type rawBytes struct {
91+
value uint64
92+
unit string
93+
unitValue uint64
94+
}
95+
96+
func (b *rawBytes) Value() uint64 {
97+
if b == nil {
98+
return 0
99+
}
100+
return b.value
101+
}
102+
103+
func (b rawBytes) MarshalJSON() ([]byte, error) {
104+
if b.unit == "" {
105+
return json.Marshal(b.value)
106+
}
107+
return json.Marshal(strconv.FormatUint(b.value/b.unitValue, 10) + b.unit)
108+
}
109+
110+
func parseUnit(b *rawBytes, unitTable map[string]uint64, caseSensitive bool, bytes []byte) error {
111+
var intValue int64
112+
err := json.Unmarshal(bytes, &intValue)
113+
if err == nil {
114+
b.value = uint64(intValue)
115+
b.unit = ""
116+
b.unitValue = 1
117+
return nil
118+
}
119+
var stringValue string
120+
err = json.Unmarshal(bytes, &stringValue)
121+
if err != nil {
122+
return err
123+
}
124+
unitIndex := 0
125+
for i, c := range stringValue {
126+
if c < '0' || c > '9' {
127+
unitIndex = i
128+
break
129+
}
130+
}
131+
if unitIndex == 0 {
132+
return E.New("invalid format: ", stringValue)
133+
}
134+
value, err := strconv.ParseUint(stringValue[:unitIndex], 10, 64)
135+
if err != nil {
136+
return E.Cause(err, "parse ", stringValue[:unitIndex])
137+
}
138+
rawUnit := stringValue[unitIndex:]
139+
var unit string
140+
if caseSensitive {
141+
unit = strings.TrimSpace(rawUnit)
142+
} else {
143+
unit = strings.TrimSpace(strings.ToLower(rawUnit))
144+
}
145+
unitValue, loaded := unitTable[unit]
146+
if !loaded {
147+
return E.New("unsupported unit: ", rawUnit)
148+
}
149+
b.value = value * unitValue
150+
b.unit = rawUnit
151+
b.unitValue = unitValue
152+
return nil
153+
}
154+
155+
type Bytes struct {
156+
rawBytes
157+
}
158+
159+
func (b *Bytes) UnmarshalJSON(bytes []byte) error {
160+
return parseUnit(&b.rawBytes, unitValueTable, false, bytes)
161+
}
162+
163+
type MemoryBytes struct {
164+
rawBytes
165+
}
166+
167+
func (m *MemoryBytes) UnmarshalJSON(bytes []byte) error {
168+
return parseUnit(&m.rawBytes, memoryUnitValueTable, false, bytes)
169+
}
170+
171+
type NetworkBytes struct {
172+
rawBytes
173+
}
174+
175+
func (n *NetworkBytes) UnmarshalJSON(bytes []byte) error {
176+
return parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
177+
}
178+
179+
type NetworkBytesCompat struct {
180+
rawBytes
181+
}
182+
183+
func (n *NetworkBytesCompat) UnmarshalJSON(bytes []byte) error {
184+
err := parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
185+
if err != nil {
186+
newErr := parseUnit(&n.rawBytes, unitValueTable, false, bytes)
187+
if newErr == nil {
188+
return nil
189+
}
190+
}
191+
return err
192+
}

common/byteformats/json_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package byteformats_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/sagernet/sing/common/byteformats"
7+
"github.com/sagernet/sing/common/json"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestNetworkBytes(t *testing.T) {
13+
t.Parallel()
14+
testMap := map[string]uint64{
15+
"1 Bps": byteformats.Byte,
16+
"1 Kbps": byteformats.KByte / 8,
17+
"1 KBps": byteformats.KByte,
18+
"1 Mbps": byteformats.MByte / 8,
19+
"1 MBps": byteformats.MByte,
20+
"1 Gbps": byteformats.GByte / 8,
21+
"1 GBps": byteformats.GByte,
22+
"1 Tbps": byteformats.TByte / 8,
23+
"1 TBps": byteformats.TByte,
24+
"1 Pbps": byteformats.PByte / 8,
25+
"1 PBps": byteformats.PByte,
26+
"1k": byteformats.KByte,
27+
"1m": byteformats.MByte,
28+
}
29+
for k, v := range testMap {
30+
var nb byteformats.NetworkBytesCompat
31+
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &nb))
32+
require.Equal(t, v, nb.Value())
33+
b, err := json.Marshal(nb)
34+
require.NoError(t, err)
35+
require.Equal(t, "\""+k+"\"", string(b))
36+
}
37+
}
38+
39+
func TestMemoryBytes(t *testing.T) {
40+
t.Parallel()
41+
testMap := map[string]uint64{
42+
"1 B": byteformats.Byte,
43+
"1 KB": byteformats.KiByte,
44+
"1 MB": byteformats.MiByte,
45+
"1 GB": byteformats.GiByte,
46+
"1 TB": byteformats.TiByte,
47+
"1 PB": byteformats.PiByte,
48+
}
49+
for k, v := range testMap {
50+
var mb byteformats.MemoryBytes
51+
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
52+
require.Equal(t, v, mb.Value())
53+
b, err := json.Marshal(mb)
54+
require.NoError(t, err)
55+
require.Equal(t, "\""+k+"\"", string(b))
56+
}
57+
}
58+
59+
func TestDefaultBytes(t *testing.T) {
60+
t.Parallel()
61+
testMap := map[string]uint64{
62+
"1 B": byteformats.Byte,
63+
"1 KB": byteformats.KByte,
64+
"1 KiB": byteformats.KiByte,
65+
"1 MB": byteformats.MByte,
66+
"1 MiB": byteformats.MiByte,
67+
"1 GB": byteformats.GByte,
68+
"1 GiB": byteformats.GiByte,
69+
"1 TB": byteformats.TByte,
70+
"1 TiB": byteformats.TiByte,
71+
"1 PB": byteformats.PByte,
72+
"1 PiB": byteformats.PiByte,
73+
"1 EB": byteformats.EByte,
74+
"1 EiB": byteformats.EiByte,
75+
"1k": byteformats.KByte,
76+
"1m": byteformats.MByte,
77+
"1g": byteformats.GByte,
78+
"1t": byteformats.TByte,
79+
"1p": byteformats.PByte,
80+
"1e": byteformats.EByte,
81+
"1K": byteformats.KByte,
82+
"1M": byteformats.MByte,
83+
"1G": byteformats.GByte,
84+
"1T": byteformats.TByte,
85+
"1P": byteformats.PByte,
86+
"1E": byteformats.EByte,
87+
"1Ki": byteformats.KiByte,
88+
"1Mi": byteformats.MiByte,
89+
"1Gi": byteformats.GiByte,
90+
"1Ti": byteformats.TiByte,
91+
"1Pi": byteformats.PiByte,
92+
"1Ei": byteformats.EiByte,
93+
"1KiB": byteformats.KiByte,
94+
"1MiB": byteformats.MiByte,
95+
"1GiB": byteformats.GiByte,
96+
"1TiB": byteformats.TiByte,
97+
"1PiB": byteformats.PiByte,
98+
"1EiB": byteformats.EiByte,
99+
"1kB": byteformats.KByte,
100+
"1mB": byteformats.MByte,
101+
"1gB": byteformats.GByte,
102+
"1tB": byteformats.TByte,
103+
"1pB": byteformats.PByte,
104+
"1eB": byteformats.EByte,
105+
}
106+
for k, v := range testMap {
107+
var mb byteformats.Bytes
108+
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
109+
require.Equal(t, v, mb.Value())
110+
b, err := json.Marshal(mb)
111+
require.NoError(t, err)
112+
require.Equal(t, "\""+k+"\"", string(b))
113+
}
114+
}

0 commit comments

Comments
 (0)