-
Notifications
You must be signed in to change notification settings - Fork 4
/
models.go
156 lines (136 loc) · 4.16 KB
/
models.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package syno // import jdel.org/go-syno/syno
import (
"net/http"
"os"
"regexp"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/robertkrimen/otto"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
)
// Model represents a Synology model
type Model struct {
Name string `json:"name,omitempty"`
CPU string `json:"cpu,omitempty"`
Cores string `json:"cores,omitempty"`
Threads string `json:"threads,omitempty"`
FPU string `json:"fpu,omitempty"`
Arch string `json:"arch,omitempty"`
RAM string `json:"ram,omitempty"`
}
// Models is a slice of Model
type Models []*Model
// Families contains families - arch mappings
var Families map[string][]string
func init() {
// https://github.com/SynologyOpenSource/pkgscripts-ng/blob/master/include/pkg_util.sh#L107
Families = make(map[string][]string)
Families["x86_64"] = []string{"x86", "bromolow", "cedarview", "avoton", "braswell", "broadwell", "dockerx64", "kvmx64", "grantley", "denverton", "apollolake"}
Families["i686"] = []string{"evansport"}
Families["armv5"] = []string{"88f6281", "88f6282", "88f5281"}
Families["armv7"] = []string{"alpine", "alpine4k", "ipq806x", "northstarplus"}
Families["armv8"] = []string{"rtd1296"}
Families["ppc"] = []string{"ppc854x", "ppc853x", "ppc824x", "powerpc", "qoriq"}
}
// GetModels returns Synology models from file or fall back to web craling
func GetModels(forceRefresh bool) (Models, error) {
if modelsFileExists() && !forceRefresh {
log.Debugf("Reading models from file %s", o.ModelsFile)
return getModelsFromModelsFile()
}
log.Debugf("Fetching models from the internet")
models, err := getModelsFromInternet()
if len(models) != 0 && err == nil {
models.SaveModelsFile()
}
return models, err
}
// FilterByName filters models by name
func (m Models) FilterByName(query string) Models {
output := Models{}
for _, synoModel := range m {
if strings.Contains(strings.ToLower(synoModel.Name), strings.ToLower(query)) {
output = append(output, synoModel)
}
}
return output
}
// SaveModelsFile saves the model file to o.ModelsFile
func (m Models) SaveModelsFile() error {
yamlModels, err := yaml.Marshal(m)
if err != nil {
return err
}
err = os.WriteFile(o.ModelsFile, yamlModels, 0755)
if err != nil {
return err
}
log.Debugf("Saved models to %s", o.ModelsFile)
return nil
}
func modelsFileExists() bool {
if _, err := os.Stat(o.ModelsFile); os.IsNotExist(err) {
return false
}
return true
}
func getModelsFromModelsFile() (Models, error) {
var models Models
bytes, err := os.ReadFile(o.ModelsFile)
if err != nil {
return nil, err
}
if yaml.Unmarshal(bytes, &models); err != nil {
return nil, err
}
return models, nil
}
// getModelsFromInternet fetches Synology models from
// The official Synology kb
func getModelsFromInternet() (Models, error) {
resp, err := http.Get("https://kb.synology.com/en-me/DSM/tutorial/What_kind_of_CPU_does_my_NAS_have")
if err != nil && resp != nil && resp.StatusCode != 200 && resp.StatusCode != 302 {
return nil, err
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, err
}
// All the html we want is embedded in the last <script> tag
var lastDeferedScript string
doc.Find("script[defer]").Each(func(i int, s *goquery.Selection) {
lastDeferedScript = s.Text()
})
// Extract the content capture group
r := regexp.MustCompile(`"content":"(.*)","tags"`)
match := r.FindStringSubmatch(lastDeferedScript)[1]
// Let otto handle the double quoted JS from content
vm := otto.New()
inlineHtml, err := vm.Run(`"` + match + `"`)
if err != nil {
return nil, err
}
// New sub document to parse inline HTML
subDoc, err := goquery.NewDocumentFromReader(strings.NewReader(inlineHtml.String()))
if err != nil {
return nil, err
}
var models Models
subDoc.Find("tr").Each(func(i int, s *goquery.Selection) {
tds := s.ChildrenFiltered("td")
if tds.Size() == 7 {
model := &Model{
Name: tds.Eq(0).Text(),
CPU: tds.Eq(1).Text(),
Cores: tds.Eq(2).Text(),
Threads: tds.Eq(3).Text(),
FPU: tds.Eq(4).Text(),
Arch: tds.Eq(5).Text(),
RAM: tds.Eq(6).Text(),
}
models = append(models, model)
}
})
return models, nil
}