mirror of https://github.com/anoshenko/rui.git
472 lines
11 KiB
Go
472 lines
11 KiB
Go
package rui
|
|
|
|
import (
|
|
"embed"
|
|
"io"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
imageDir = "images"
|
|
themeDir = "themes"
|
|
viewDir = "views"
|
|
rawDir = "raw"
|
|
stringsDir = "strings"
|
|
)
|
|
|
|
type scaledImage struct {
|
|
path string
|
|
scale float64
|
|
}
|
|
|
|
type imagePath struct {
|
|
path string
|
|
fs *embed.FS
|
|
}
|
|
|
|
type resourceManager struct {
|
|
embedFS []*embed.FS
|
|
themes map[string]Theme
|
|
images map[string]imagePath
|
|
imageSrcSets map[string][]scaledImage
|
|
path string
|
|
}
|
|
|
|
var resources = resourceManager{
|
|
embedFS: []*embed.FS{},
|
|
themes: map[string]Theme{},
|
|
images: map[string]imagePath{},
|
|
imageSrcSets: map[string][]scaledImage{},
|
|
}
|
|
|
|
func AddEmbedResources(fs *embed.FS) {
|
|
resources.embedFS = append(resources.embedFS, fs)
|
|
rootDirs := resources.embedRootDirs(fs)
|
|
for _, dir := range rootDirs {
|
|
switch dir {
|
|
case imageDir:
|
|
resources.scanEmbedImagesDir(fs, dir, "")
|
|
|
|
case themeDir:
|
|
resources.scanEmbedThemesDir(fs, dir)
|
|
|
|
case stringsDir:
|
|
resources.scanEmbedStringsDir(fs, dir)
|
|
|
|
case viewDir, rawDir:
|
|
// do nothing
|
|
|
|
default:
|
|
if files, err := fs.ReadDir(dir); err == nil {
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
switch file.Name() {
|
|
case imageDir:
|
|
resources.scanEmbedImagesDir(fs, dir+"/"+imageDir, "")
|
|
|
|
case themeDir:
|
|
resources.scanEmbedThemesDir(fs, dir+"/"+themeDir)
|
|
|
|
case stringsDir:
|
|
resources.scanEmbedStringsDir(fs, dir+"/"+stringsDir)
|
|
|
|
case viewDir, rawDir:
|
|
// do nothing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (resources *resourceManager) embedRootDirs(fs *embed.FS) []string {
|
|
result := []string{}
|
|
if files, err := fs.ReadDir("."); err == nil {
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
result = append(result, file.Name())
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (resources *resourceManager) scanEmbedThemesDir(fs *embed.FS, dir string) {
|
|
if files, err := fs.ReadDir(dir); err == nil {
|
|
for _, file := range files {
|
|
name := file.Name()
|
|
path := dir + "/" + name
|
|
if file.IsDir() {
|
|
resources.scanEmbedThemesDir(fs, path)
|
|
} else if strings.ToLower(filepath.Ext(name)) == ".rui" {
|
|
if data, err := fs.ReadFile(path); err == nil {
|
|
resources.registerThemeText(string(data))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (resources *resourceManager) scanEmbedImagesDir(fs *embed.FS, dir, prefix string) {
|
|
if files, err := fs.ReadDir(dir); err == nil {
|
|
for _, file := range files {
|
|
name := file.Name()
|
|
path := dir + "/" + name
|
|
if file.IsDir() {
|
|
resources.scanEmbedImagesDir(fs, path, prefix+name+"/")
|
|
} else {
|
|
ext := strings.ToLower(filepath.Ext(name))
|
|
switch ext {
|
|
case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp", ".webp":
|
|
resources.registerImage(fs, path, prefix+name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func invalidImageFileFormat(filename string) {
|
|
ErrorLog(`Invalid image file name parameters: "` + filename +
|
|
`". Image file name format: name[@x-param].ext (examples: icon.png, icon@1.5x.png)`)
|
|
}
|
|
|
|
func (resources *resourceManager) registerImage(fs *embed.FS, path, filename string) {
|
|
resources.images[filename] = imagePath{fs: fs, path: path}
|
|
|
|
start := strings.LastIndex(filename, "@")
|
|
if start < 0 {
|
|
return
|
|
}
|
|
|
|
ext := strings.LastIndex(filename, ".")
|
|
if start > ext || filename[ext-1] != 'x' {
|
|
invalidImageFileFormat(path)
|
|
return
|
|
}
|
|
|
|
if scale, err := strconv.ParseFloat(filename[start+1:ext-1], 32); err == nil {
|
|
key := filename[:start] + filename[ext:]
|
|
images, ok := resources.imageSrcSets[key]
|
|
if ok {
|
|
for _, image := range images {
|
|
if image.scale == scale {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
images = []scaledImage{}
|
|
}
|
|
resources.imageSrcSets[key] = append(images, scaledImage{path: filename, scale: scale})
|
|
} else {
|
|
invalidImageFileFormat(path)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (resources *resourceManager) scanImagesDirectory(path, filePrefix string) {
|
|
if files, err := os.ReadDir(path); err == nil {
|
|
for _, file := range files {
|
|
filename := file.Name()
|
|
if filename[0] != '.' {
|
|
newPath := path + `/` + filename
|
|
if !file.IsDir() {
|
|
resources.registerImage(nil, newPath, filePrefix+filename)
|
|
} else {
|
|
resources.scanImagesDirectory(newPath, filePrefix+filename+"/")
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ErrorLog(err.Error())
|
|
}
|
|
}
|
|
|
|
func (resources *resourceManager) scanThemesDir(path string) {
|
|
if files, err := os.ReadDir(path); err == nil {
|
|
for _, file := range files {
|
|
filename := file.Name()
|
|
if filename[0] != '.' {
|
|
newPath := path + `/` + filename
|
|
if file.IsDir() {
|
|
resources.scanThemesDir(newPath)
|
|
} else if strings.ToLower(filepath.Ext(newPath)) == ".rui" {
|
|
if data, err := os.ReadFile(newPath); err == nil {
|
|
resources.registerThemeText(string(data))
|
|
} else {
|
|
ErrorLog(err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ErrorLog(err.Error())
|
|
}
|
|
}
|
|
|
|
// SetResourcePath set path of the resource directory
|
|
func SetResourcePath(path string) {
|
|
resources.path = path
|
|
pathLen := len(path)
|
|
if pathLen > 0 && path[pathLen-1] != '/' {
|
|
resources.path += "/"
|
|
}
|
|
|
|
resources.scanImagesDirectory(resources.path+imageDir, "")
|
|
resources.scanThemesDir(resources.path + themeDir)
|
|
resources.scanStringsDir(resources.path + stringsDir)
|
|
}
|
|
|
|
func (resources *resourceManager) scanDefaultResourcePath() {
|
|
if exe, err := os.Executable(); err == nil {
|
|
path := filepath.Dir(exe) + "/resources/"
|
|
resources.scanImagesDirectory(path+imageDir, "")
|
|
resources.scanThemesDir(path + themeDir)
|
|
resources.scanStringsDir(path + stringsDir)
|
|
}
|
|
}
|
|
|
|
func (resources *resourceManager) registerThemeText(text string) bool {
|
|
theme, ok := CreateThemeFromText(text)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
name := theme.Name()
|
|
if name == "" {
|
|
defaultTheme.Append(theme)
|
|
} else if t, ok := resources.themes[name]; ok {
|
|
t.Append(theme)
|
|
} else {
|
|
resources.themes[name] = theme
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func serveResourceFile(filename string, w http.ResponseWriter, r *http.Request) bool {
|
|
serveEmbed := func(fs *embed.FS, path string) bool {
|
|
if file, err := fs.Open(path); err == nil {
|
|
if stat, err := file.Stat(); err == nil {
|
|
http.ServeContent(w, r, filename, stat.ModTime(), file.(io.ReadSeeker))
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if image, ok := resources.images[filename]; ok {
|
|
if image.fs != nil {
|
|
if serveEmbed(image.fs, image.path) {
|
|
return true
|
|
}
|
|
} else {
|
|
if _, err := os.Stat(image.path); err == nil {
|
|
http.ServeFile(w, r, image.path)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, fs := range resources.embedFS {
|
|
if serveEmbed(fs, filename) {
|
|
return true
|
|
}
|
|
for _, dir := range resources.embedRootDirs(fs) {
|
|
if serveEmbed(fs, dir+"/"+filename) {
|
|
return true
|
|
}
|
|
if subDirs, err := fs.ReadDir(dir); err == nil {
|
|
for _, subdir := range subDirs {
|
|
if subdir.IsDir() {
|
|
if serveEmbed(fs, dir+"/"+subdir.Name()+"/"+filename) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
serve := func(path, filename string) bool {
|
|
filepath := path + filename
|
|
if _, err := os.Stat(filepath); err == nil {
|
|
http.ServeFile(w, r, filepath)
|
|
return true
|
|
}
|
|
|
|
filepath = path + imageDir + "/" + filename
|
|
if _, err := os.Stat(filepath); err == nil {
|
|
http.ServeFile(w, r, filepath)
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
if resources.path != "" && serve(resources.path, filename) {
|
|
return true
|
|
}
|
|
|
|
if exe, err := os.Executable(); err == nil {
|
|
path := filepath.Dir(exe) + "/resources/"
|
|
if serve(path, filename) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// ReadRawResource returns the contents of the raw resource with the specified name
|
|
func ReadRawResource(filename string) []byte {
|
|
for _, fs := range resources.embedFS {
|
|
rootDirs := resources.embedRootDirs(fs)
|
|
for _, dir := range rootDirs {
|
|
switch dir {
|
|
case imageDir, themeDir, viewDir, stringsDir:
|
|
// do nothing
|
|
|
|
case rawDir:
|
|
if data, err := fs.ReadFile(dir + "/" + filename); err == nil {
|
|
return data
|
|
}
|
|
|
|
default:
|
|
if data, err := fs.ReadFile(dir + "/" + rawDir + "/" + filename); err == nil {
|
|
return data
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if resources.path != "" {
|
|
if data, err := os.ReadFile(resources.path + rawDir + "/" + filename); err == nil {
|
|
return data
|
|
}
|
|
}
|
|
|
|
if exe, err := os.Executable(); err == nil {
|
|
if data, err := os.ReadFile(filepath.Dir(exe) + "/resources/" + rawDir + "/" + filename); err == nil {
|
|
return data
|
|
}
|
|
}
|
|
|
|
ErrorLogF(`The "%s" raw file don't found`, filename)
|
|
return nil
|
|
}
|
|
|
|
// OpenRawResource returns the contents of the raw resource with the specified name
|
|
func OpenRawResource(filename string) fs.File {
|
|
for _, fs := range resources.embedFS {
|
|
rootDirs := resources.embedRootDirs(fs)
|
|
for _, dir := range rootDirs {
|
|
switch dir {
|
|
case imageDir, themeDir, viewDir, stringsDir:
|
|
// do nothing
|
|
|
|
case rawDir:
|
|
if file, err := fs.Open(dir + "/" + filename); err == nil {
|
|
return file
|
|
}
|
|
|
|
default:
|
|
if file, err := fs.Open(dir + "/" + rawDir + "/" + filename); err == nil {
|
|
return file
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if resources.path != "" {
|
|
if file, err := os.Open(resources.path + rawDir + "/" + filename); err == nil {
|
|
return file
|
|
}
|
|
}
|
|
|
|
if exe, err := os.Executable(); err == nil {
|
|
if file, err := os.Open(filepath.Dir(exe) + "/resources/" + rawDir + "/" + filename); err == nil {
|
|
return file
|
|
}
|
|
}
|
|
|
|
ErrorLogF(`The "%s" raw file don't found`, filename)
|
|
return nil
|
|
}
|
|
|
|
// AllRawResources returns the list of all raw resouces
|
|
func AllRawResources() []string {
|
|
result := []string{}
|
|
|
|
for _, fs := range resources.embedFS {
|
|
rootDirs := resources.embedRootDirs(fs)
|
|
for _, dir := range rootDirs {
|
|
switch dir {
|
|
case imageDir, themeDir, viewDir:
|
|
// do nothing
|
|
|
|
case rawDir:
|
|
if files, err := fs.ReadDir(rawDir); err == nil {
|
|
for _, file := range files {
|
|
result = append(result, file.Name())
|
|
}
|
|
}
|
|
|
|
default:
|
|
if files, err := fs.ReadDir(dir + "/" + rawDir); err == nil {
|
|
for _, file := range files {
|
|
result = append(result, file.Name())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if resources.path != "" {
|
|
if files, err := os.ReadDir(resources.path + rawDir); err == nil {
|
|
for _, file := range files {
|
|
result = append(result, file.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
if exe, err := os.Executable(); err == nil {
|
|
if files, err := os.ReadDir(filepath.Dir(exe) + "/resources/" + rawDir); err == nil {
|
|
for _, file := range files {
|
|
result = append(result, file.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// AllImageResources returns the list of all image resouces
|
|
func AllImageResources() []string {
|
|
result := make([]string, 0, len(resources.images))
|
|
for image := range resources.images {
|
|
result = append(result, image)
|
|
}
|
|
sort.Strings(result)
|
|
return result
|
|
}
|
|
|
|
func AddTheme(theme Theme) {
|
|
if theme != nil {
|
|
name := theme.Name()
|
|
if name == "" {
|
|
defaultTheme.Append(theme)
|
|
} else if t, ok := resources.themes[name]; ok {
|
|
t.Append(theme)
|
|
} else {
|
|
resources.themes[name] = theme
|
|
}
|
|
}
|
|
}
|