-
Notifications
You must be signed in to change notification settings - Fork 17
/
collections.go
361 lines (333 loc) · 11.5 KB
/
collections.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package wallutils
import (
"image/jpeg"
"image/png"
"os"
"path/filepath"
"runtime"
"sort"
"sync"
"github.com/stretchr/powerwalk"
"github.com/xyproto/wallutils/pkg/gnometimed"
"github.com/xyproto/wallutils/pkg/simpletimed"
)
const (
// Minimum dimensions for qualifying as a "wallpaper"
minimumWidth = 640
minimumHeight = 480
)
// DefaultWallpaperDirectories lists the default locations to look for wallpapers
var DefaultWallpaperDirectories = []string{
"/usr/share/pixmaps",
"/usr/share/wallpapers",
"/usr/share/backgrounds",
"/usr/local/share/pixmaps",
"/usr/local/share/wallpapers",
"/usr/local/share/backgrounds",
"/usr/share/archlinux",
}
// SearchResults is a struct containing all found wallpaper collections, of these types:
// * wallpaper images (several in one directory, of different sizes)
// * gnome wallpapers (contains a GNOME-compatible XML file)
// * sime timed wallpapers (contains a .stw file)
type SearchResults struct {
wallpapers sync.Map // stores the full path -> *Wallpaper struct, for png + jpeg files
gnomeWallpapers sync.Map // stores the full path -> *gnometimed.Wallpaper struct, for xml files
simpleTimedWallpapers sync.Map // stores the full path -> *simpletimed.Wallpaper struct, for stw files
sortedWallpapers []*Wallpaper // holds sorted wallpapers
sortedGnomeTimedWallpapers []*gnometimed.Wallpaper // holds sorted Gnome Timed Wallpapers
sortedSimpleTimedWallpapers []*simpletimed.Wallpaper // holds sorted Simple Timed Wallpapers
}
// Find the number of available logical CPUs
var numCPU = runtime.NumCPU()
// NewSearchResults will reset the search and prepare to search again
func NewSearchResults() *SearchResults {
return &SearchResults{
wallpapers: sync.Map{},
gnomeWallpapers: sync.Map{},
simpleTimedWallpapers: sync.Map{},
sortedWallpapers: []*Wallpaper{},
sortedGnomeTimedWallpapers: []*gnometimed.Wallpaper{},
sortedSimpleTimedWallpapers: []*simpletimed.Wallpaper{},
}
}
// collectionName will strip away the last part of the path, until the remaining last word is no "pixmaps", "contents", "images", "backgrounds", or "wallpapers".
// This is usually the name of the wallpaper collection.
func collectionName(path string) string {
dir := filepath.Dir(path)
for {
switch filepath.Base(dir) {
case "pixmaps", "contents", "images", "wallpapers", "backgrounds":
dir = filepath.Dir(dir)
default:
return filepath.Base(dir)
}
}
}
// partOfCollection checks if it is likely that a given filename is part of a wallpaper collection
func partOfCollection(filename string) bool {
// filename contains width x height and is preceded by either a "_" or nothing
_, err := FilenameToRes(filename)
return err == nil
}
// pngSize returns the with and height of a PNG file, without reading the entire file
func pngSize(path string) (uint, uint, error) {
pngFile, err := os.Open(path)
if err != nil {
return 0, 0, err
}
ic, err := png.DecodeConfig(pngFile)
if err != nil {
return 0, 0, err
}
return uint(ic.Width), uint(ic.Height), nil
}
// jpegSize returns the with and height of a JPEG file, without reading the entire file
func jpegSize(path string) (uint, uint, error) {
jpegFile, err := os.Open(path)
if err != nil {
return 0, 0, err
}
ic, err := jpeg.DecodeConfig(jpegFile)
if err != nil {
return 0, 0, err
}
return uint(ic.Width), uint(ic.Height), nil
}
// largeEnough checks if the given size is equal to or larger than the global minimum size
func largeEnough(width, height uint) bool {
return (width >= minimumWidth) && (height >= minimumHeight)
}
// visit is called per file that is found, and will be called concurrently by powerwalk.WalkLimit
func (sr *SearchResults) visit(path string, _ os.FileInfo, _ error) error {
switch filepath.Ext(path) {
case ".png":
width, height, err := pngSize(path)
if err != nil {
return err
}
if !largeEnough(width, height) {
return nil
}
wp := &Wallpaper{collectionName(path), path, width, height, partOfCollection(path)}
sr.wallpapers.Store(path, wp)
case ".jpg", ".jpeg":
width, height, err := jpegSize(path)
if err != nil {
return err
}
if !largeEnough(width, height) {
return nil
}
wp := &Wallpaper{collectionName(path), path, width, height, partOfCollection(path)}
sr.wallpapers.Store(path, wp)
case ".svg":
// TODO: Consider supporting SVG wallpapers in the future
return nil
case ".xpm", ".xbm":
// TODO: Consider supporting XPM and/or XBM wallpapers in the future
return nil
case ".stw": // Simple Timed Wallpaper
stw, err := simpletimed.ParseSTW(path)
if err != nil {
return err
}
sr.simpleTimedWallpapers.Store(path, stw)
case ".xml":
gw, err := gnometimed.ParseXML(path)
if err != nil {
return err
}
sr.gnomeWallpapers.Store(path, gw)
}
return nil
}
// sortWallpapers sorts the found wallpapers
func (sr *SearchResults) sortWallpapers() {
var collected []*Wallpaper
sr.wallpapers.Range(func(_, value interface{}) bool {
wp, ok := value.(*Wallpaper)
if !ok {
// internal error
panic("a value in the wallpapers map is not a pointer to a Wallpaper struct")
}
collected = append(collected, wp)
return true
})
// Now sort the collected wallpapers by the collection name, and then by the size
sort.Slice(collected, func(i, j int) bool {
if collected[i].CollectionName == collected[j].CollectionName {
return (collected[i].Width * collected[i].Height) < (collected[j].Width * collected[i].Height)
}
return collected[i].CollectionName < collected[j].CollectionName
})
sr.sortedWallpapers = collected
}
// sortGnomeTimedWallpapers sorts the Found gnome Timed Wallpapers
func (sr *SearchResults) sortGnomeTimedWallpapers() {
var collected []*gnometimed.Wallpaper
sr.gnomeWallpapers.Range(func(_, value interface{}) bool {
gw, ok := value.(*gnometimed.Wallpaper)
if !ok {
// internal error
panic("a value in the gnomeWallpapers map is not a pointer to a GnomeTimedWallpaper struct")
}
collected = append(collected, gw)
return true
})
// Now sort the collected GNOME wallpapers by the collection name
sort.Slice(collected, func(i, j int) bool {
return collected[i].Name < collected[j].Name
})
sr.sortedGnomeTimedWallpapers = collected
}
// sortSimpleTimedWallpapers sorts the found Simple Timed Wallpapers
func (sr *SearchResults) sortSimpleTimedWallpapers() {
var collected []*simpletimed.Wallpaper
sr.simpleTimedWallpapers.Range(func(_, value interface{}) bool {
stw, ok := value.(*simpletimed.Wallpaper)
if !ok {
// internal error
panic("a value in the simpleTimedWallpapers map is not a pointer to a simpletimed.Wallpaper struct")
}
collected = append(collected, stw)
return true
})
// Now sort the collected Simple Timed Wallpapers by the collection name
sort.Slice(collected, func(i, j int) bool {
return collected[i].Name < collected[j].Name
})
sr.sortedSimpleTimedWallpapers = collected
}
// FindWallpapers will search for wallpaper collections, simple timed
// wallpapers and GNOME timed wallpapers in all default wallpaper directories
// on the system.
func FindWallpapers() (*SearchResults, error) {
sr := NewSearchResults()
for _, path := range DefaultWallpaperDirectories {
// Search the given path, using the sr.visit function
if err := powerwalk.WalkLimit(path, sr.visit, numCPU); err != nil {
return nil, err
}
}
sr.sortWallpapers()
sr.sortSimpleTimedWallpapers()
sr.sortGnomeTimedWallpapers()
return sr, nil
}
// FindImagesAt will find images at the given search path.
// Set onlyLarge to true if the images should be large enough for the desktop.
func FindImagesAt(searchPath string, _ []string, onlyLarge bool) ([]string, error) {
found := []string{}
// A visit function that will be called for every file found by the WalkLimit function below
visit := func(path string, _ os.FileInfo, _ error) error {
switch filepath.Ext(path) {
case ".png":
if onlyLarge {
width, height, err := pngSize(path)
if err != nil {
return err
}
if !largeEnough(width, height) {
return nil
}
}
found = append(found, path)
case ".jpg", ".jpeg":
if onlyLarge {
width, height, err := jpegSize(path)
if err != nil {
return err
}
if !largeEnough(width, height) {
return nil
}
}
found = append(found, path)
}
return nil
}
// Search the given path, using the visit function
if err := powerwalk.WalkLimit(searchPath, visit, numCPU); err != nil {
return found, err
}
return found, nil
}
// FindWallpapersAt will search for wallpaper collections, simple timed
// wallpapers and GNOME timed wallpapers in the given path.
func FindWallpapersAt(path string) (*SearchResults, error) {
sr := NewSearchResults()
if err := powerwalk.WalkLimit(path, sr.visit, numCPU); err != nil {
return nil, err
}
sr.sortWallpapers()
sr.sortSimpleTimedWallpapers()
sr.sortGnomeTimedWallpapers()
return sr, nil
}
// CollectionNames gathers all the names of all available wallpaper packs or GNOME timed backgrounds
func (sr *SearchResults) CollectionNames() []string {
var collectionNames []string
for _, wp := range sr.sortedWallpapers {
if wp.PartOfCollection {
collectionNames = append(collectionNames, wp.CollectionName)
}
}
for _, gw := range sr.sortedGnomeTimedWallpapers {
collectionNames = append(collectionNames, gw.Name)
}
for _, stw := range sr.sortedSimpleTimedWallpapers {
collectionNames = append(collectionNames, stw.Name)
}
return unique(collectionNames)
}
// Wallpapers returns a sorted slice of all found wallpapers
func (sr *SearchResults) Wallpapers() []*Wallpaper {
return sr.sortedWallpapers
}
// GnomeTimedWallpapers returns a sorted slice of all found gnome timed wallpapers
func (sr *SearchResults) GnomeTimedWallpapers() []*gnometimed.Wallpaper {
return sr.sortedGnomeTimedWallpapers
}
// SimpleTimedWallpapers returns a sorted slice of all found simple timed wallpapers
func (sr *SearchResults) SimpleTimedWallpapers() []*simpletimed.Wallpaper {
return sr.sortedSimpleTimedWallpapers
}
// WallpapersByName will return simple timed wallpapers that match with the collection name
func (sr *SearchResults) WallpapersByName(name string) []*Wallpaper {
var collection []*Wallpaper
for _, wp := range sr.sortedWallpapers {
if wp.PartOfCollection && wp.CollectionName == name {
collection = append(collection, wp)
}
}
return collection
}
// GnomeTimedWallpapersByName will return gnome timed wallpapers that match with the collection name
func (sr *SearchResults) GnomeTimedWallpapersByName(name string) []*gnometimed.Wallpaper {
var collection []*gnometimed.Wallpaper
for _, gw := range sr.sortedGnomeTimedWallpapers {
if gw.Name == name {
collection = append(collection, gw)
}
}
return collection
}
// SimpleTimedWallpapersByName will return simple timed wallpapers that match with the collection name
func (sr *SearchResults) SimpleTimedWallpapersByName(name string) []*simpletimed.Wallpaper {
var collection []*simpletimed.Wallpaper
for _, stw := range sr.sortedSimpleTimedWallpapers {
if stw.Name == name {
collection = append(collection, stw)
}
}
return collection
}
// Empty checks if these search results are empty
func (sr *SearchResults) Empty() bool {
return len(sr.sortedSimpleTimedWallpapers) == 0 && len(sr.sortedGnomeTimedWallpapers) == 0 && len(sr.sortedWallpapers) == 0
}
// NoTimedWallpapers checks if the current search results contains no timed wallpapers
func (sr *SearchResults) NoTimedWallpapers() bool {
return len(sr.sortedSimpleTimedWallpapers) == 0 && len(sr.sortedGnomeTimedWallpapers) == 0
}