-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathmagic.go
115 lines (98 loc) · 3.1 KB
/
magic.go
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
107
108
109
110
111
112
113
114
115
package magic
import (
"context"
"fmt"
"runtime"
)
type job struct {
input []byte
reference FileType
resultChan chan *FileType
}
// LookupConfig is a struct that contains configuration details to modify the default Lookup behavior
type LookupConfig struct {
ConcurrencyEnabled bool // the search will be performed concurrently by multiple worker goroutines when this field is set to true. The search will be carried out by the calling goroutine if set to false.
WorkerCount int // number of worker goroutines to be spawned if concurrency is set to true. If set to -1, workerCount will be set to use all the available cores.
}
// ErrUnknown infers the file type cannot be determined by the provided magic bytes
var ErrUnknown = fmt.Errorf("unknown file type")
// Lookup looks up the file type based on the provided magic bytes. You should provide at least the first 1024 bytes of the file in this slice.
// A magic.ErrUnknown will be returned if the file type is not known.
func Lookup(bytes []byte) (*FileType, error) {
return lookup(bytes, true, -1)
}
// LookupWithConfig looks up the file type based on the provided magic bytes, and a given configuration. You should provide at least the first 1024 bytes of the file in this slice.
// A magic.ErrUnknown will be returned if the file type is not known.
func LookupWithConfig(bytes []byte, config LookupConfig) (*FileType, error) {
return lookup(bytes, config.ConcurrencyEnabled, config.WorkerCount)
}
// LookupSync lookups up the file type based on the provided magic bytes without spawning any additional goroutines. You should provide at least the first 1024 bytes of the file in this slice.
// A magic.ErrUnknown will be returned if the file type is not known.
func LookupSync(bytes []byte) (*FileType, error) {
return lookup(bytes, false, 0)
}
func lookup(bytes []byte, concurrent bool, workers int) (*FileType, error) {
// additional worker count check: avoid deadlock when worker count is set to zero
if !concurrent || workers == 0 {
for _, t := range Types {
ft := t.check(bytes, 0)
if ft != nil {
return ft, nil
}
}
return nil, ErrUnknown
}
// use all available cores
workerCount := runtime.GOMAXPROCS(0)
if workers > -1 && workers < workerCount {
workerCount = workers
}
workChan := make(chan job)
resultChan := make(chan *FileType)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// spawn workers
for i := 0; i < workerCount; i++ {
go worker(ctx, workChan)
}
awaiting := len(Types)
// queue work
go func() {
for _, t := range Types {
select {
case <-ctx.Done():
return
case workChan <- job{
input: bytes,
reference: t,
resultChan: resultChan,
}:
}
}
}()
for {
result := <-resultChan
if result != nil {
return result, nil
}
awaiting--
if awaiting <= 0 {
break
}
}
return nil, ErrUnknown
}
func worker(ctx context.Context, work chan job) {
for {
select {
case <-ctx.Done():
return
case job := <-work:
select {
case <-ctx.Done():
return
case job.resultChan <- job.reference.check(job.input, job.reference.Offset):
}
}
}
}