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
|
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package api
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/syncthing/syncthing/lib/api/auto"
"github.com/syncthing/syncthing/lib/assets"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/sync"
)
const themePrefix = "theme-assets/"
type staticsServer struct {
assetDir string
assets map[string]assets.Asset
availableThemes []string
mut sync.RWMutex
theme string
lastThemeChange time.Time
}
func newStaticsServer(theme, assetDir string) *staticsServer {
s := &staticsServer{
assetDir: assetDir,
assets: auto.Assets(),
mut: sync.NewRWMutex(),
theme: theme,
lastThemeChange: time.Now().UTC(),
}
seen := make(map[string]struct{})
// Load themes from compiled in assets.
for file := range auto.Assets() {
theme := strings.Split(file, "/")[0]
if _, ok := seen[theme]; !ok {
seen[theme] = struct{}{}
s.availableThemes = append(s.availableThemes, theme)
}
}
if assetDir != "" {
// Load any extra themes from the asset override dir.
for _, dir := range dirNames(assetDir) {
if _, ok := seen[dir]; !ok {
seen[dir] = struct{}{}
s.availableThemes = append(s.availableThemes, dir)
}
}
}
return s
}
func (s *staticsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/themes.json":
s.serveThemes(w)
default:
s.serveAsset(w, r)
}
}
func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, must-revalidate")
file := r.URL.Path
if file[0] == '/' {
file = file[1:]
}
if file == "" {
file = "index.html"
}
s.mut.RLock()
theme := s.theme
modificationTime := s.lastThemeChange
s.mut.RUnlock()
// If path starts with special prefix, get theme and file from path
if strings.HasPrefix(file, themePrefix) {
path := file[len(themePrefix):]
i := strings.IndexRune(path, '/')
if i == -1 {
http.NotFound(w, r)
return
}
theme = path[:i]
file = path[i+1:]
}
// Check for an override for the current theme.
if s.serveFromAssetDir(file, theme, w, r) {
return
}
// Check for a compiled in asset for the current theme.
if s.serveFromAssets(file, theme, modificationTime, w, r) {
return
}
// Check for an overridden default asset.
if s.serveFromAssetDir(file, config.DefaultTheme, w, r) {
return
}
// Check for a compiled in default asset.
if s.serveFromAssets(file, config.DefaultTheme, modificationTime, w, r) {
return
}
http.NotFound(w, r)
}
func (s *staticsServer) serveFromAssetDir(file, theme string, w http.ResponseWriter, r *http.Request) bool {
if s.assetDir == "" {
return false
}
p := filepath.Join(s.assetDir, theme, filepath.FromSlash(file))
if _, err := os.Stat(p); err != nil {
return false
}
mtype := assets.MimeTypeForFile(file)
if mtype != "" {
w.Header().Set("Content-Type", mtype)
}
http.ServeFile(w, r, p)
return true
}
func (s *staticsServer) serveFromAssets(file, theme string, modificationTime time.Time, w http.ResponseWriter, r *http.Request) bool {
as, ok := s.assets[theme+"/"+file]
if !ok {
return false
}
as.Modified = modificationTime
assets.Serve(w, r, as)
return true
}
func (s *staticsServer) serveThemes(w http.ResponseWriter) {
sendJSON(w, map[string][]string{
"themes": s.availableThemes,
})
}
func (s *staticsServer) setTheme(theme string) {
s.mut.Lock()
s.theme = theme
s.lastThemeChange = time.Now().UTC()
s.mut.Unlock()
}
func (s *staticsServer) String() string {
return fmt.Sprintf("staticsServer@%p", s)
}
|