2017-11-10 03:33:11 +00:00
package common
2017-01-01 15:45:43 +00:00
2017-05-29 14:52:37 +00:00
import (
2022-02-21 03:53:13 +00:00
"database/sql"
"encoding/json"
"errors"
"html/template"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
qgen "git.tuxpa.in/a/gosora/query_gen"
2017-05-29 14:52:37 +00:00
)
2017-01-01 15:45:43 +00:00
2018-10-02 04:09:17 +00:00
// TODO: Something more thread-safe
2017-12-01 02:04:29 +00:00
type ThemeList map [ string ] * Theme
2017-11-06 04:02:35 +00:00
2018-05-27 09:36:35 +00:00
var Themes ThemeList = make ( map [ string ] * Theme ) // ? Refactor this into a store?
2017-11-10 03:33:11 +00:00
var DefaultThemeBox atomic . Value
2017-11-11 04:06:16 +00:00
var ChangeDefaultThemeMutex sync . Mutex
2021-05-11 09:25:07 +00:00
var ThemesSlice [ ] * Theme
2017-01-01 15:45:43 +00:00
2018-12-08 00:45:27 +00:00
// TODO: Fallback to a random theme if this doesn't exist, so admins can remove themes they don't use
2017-09-22 02:21:17 +00:00
// TODO: Use this when the default theme doesn't exist
2018-03-17 08:16:43 +00:00
var fallbackTheme = "cosora"
2018-05-27 09:36:35 +00:00
var overridenTemplates = make ( map [ string ] bool ) // ? What is this used for?
2017-06-19 08:06:54 +00:00
2017-11-11 23:34:27 +00:00
type ThemeStmts struct {
2022-02-21 03:32:53 +00:00
getAll * sql . Stmt
isDefault * sql . Stmt
update * sql . Stmt
add * sql . Stmt
2017-11-11 23:34:27 +00:00
}
var themeStmts ThemeStmts
2017-09-23 19:57:13 +00:00
func init ( ) {
2022-02-21 03:32:53 +00:00
DbInits . Add ( func ( acc * qgen . Accumulator ) error {
t , cols := "themes" , "uname,default"
themeStmts = ThemeStmts {
getAll : acc . Select ( t ) . Columns ( cols ) . Prepare ( ) ,
isDefault : acc . Select ( t ) . Columns ( "default" ) . Where ( "uname=?" ) . Prepare ( ) ,
update : acc . Update ( t ) . Set ( "default=?" ) . Where ( "uname=?" ) . Prepare ( ) ,
add : acc . Insert ( t ) . Columns ( cols ) . Fields ( "?,?" ) . Prepare ( ) ,
}
return acc . FirstError ( )
} )
2017-09-23 19:57:13 +00:00
}
2018-05-27 09:36:35 +00:00
func NewThemeList ( ) ( themes ThemeList , err error ) {
2022-02-21 03:32:53 +00:00
themes = make ( map [ string ] * Theme )
themeFiles , err := ioutil . ReadDir ( "./themes" )
if err != nil {
return themes , err
}
if len ( themeFiles ) == 0 {
return themes , errors . New ( "You don't have any themes" )
}
var lastTheme , defaultTheme string
for _ , themeFile := range themeFiles {
if ! themeFile . IsDir ( ) {
continue
}
themeName := themeFile . Name ( )
log . Printf ( "Adding theme '%s'" , themeName )
themePath := "./themes/" + themeName
themeFile , err := ioutil . ReadFile ( themePath + "/theme.json" )
if err != nil {
return themes , err
}
th := & Theme { }
err = json . Unmarshal ( themeFile , th )
if err != nil {
return themes , err
}
if th . Name == "" {
return themes , errors . New ( "Theme " + themePath + " doesn't have a name set in theme.json" )
}
if th . Name == fallbackTheme {
defaultTheme = fallbackTheme
}
lastTheme = th . Name
// TODO: Implement the static file part of this and fsnotify
if th . Path != "" {
log . Print ( "Resolving redirect to " + th . Path )
themeFile , err := ioutil . ReadFile ( th . Path + "/theme.json" )
if err != nil {
return themes , err
}
th = & Theme { Path : th . Path }
err = json . Unmarshal ( themeFile , th )
if err != nil {
return themes , err
}
} else {
th . Path = themePath
}
th . Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
// TODO: Let the theme specify where it's resources are via the JSON file?
// TODO: Let the theme inherit CSS from another theme?
// ? - This might not be too helpful, as it only searches for /public/ and not if /public/ is empty. Still, it might help some people with a slightly less cryptic error
log . Print ( th . Path + "/public/" )
_ , err = os . Stat ( th . Path + "/public/" )
if err != nil {
if os . IsNotExist ( err ) {
return themes , errors . New ( "We couldn't find this theme's resources. E.g. the /public/ folder." )
} else {
log . Print ( "We weren't able to access this theme's resources due to a permissions issue or some other problem" )
return themes , err
}
}
if th . FullImage != "" {
DebugLog ( "Adding theme image" )
err = StaticFiles . Add ( th . Path + "/" + th . FullImage , themePath )
if err != nil {
return themes , err
}
}
th . TemplatesMap = make ( map [ string ] string )
th . TmplPtr = make ( map [ string ] interface { } )
if th . Templates != nil {
for _ , themeTmpl := range th . Templates {
th . TemplatesMap [ themeTmpl . Name ] = themeTmpl . Source
th . TmplPtr [ themeTmpl . Name ] = TmplPtrMap [ "o_" + themeTmpl . Source ]
}
}
th . IntTmplHandle = DefaultTemplates
overrides , err := ioutil . ReadDir ( th . Path + "/overrides/" )
if err != nil && ! os . IsNotExist ( err ) {
return themes , err
}
if len ( overrides ) > 0 {
overCount := 0
th . OverridenMap = make ( map [ string ] bool )
for _ , override := range overrides {
if override . IsDir ( ) {
continue
}
ext := filepath . Ext ( themePath + "/overrides/" + override . Name ( ) )
log . Print ( "attempting to add " + themePath + "/overrides/" + override . Name ( ) )
if ext != ".html" {
log . Print ( "not a html file" )
continue
}
overCount ++
nosuf := strings . TrimSuffix ( override . Name ( ) , ext )
th . OverridenTemplates = append ( th . OverridenTemplates , nosuf )
th . OverridenMap [ nosuf ] = true
//th.TmplPtr[nosuf] = TmplPtrMap["o_"+nosuf]
log . Print ( "succeeded" )
}
localTmpls := template . New ( "" )
err = loadTemplates ( localTmpls , th . Name )
if err != nil {
return themes , err
}
th . IntTmplHandle = localTmpls
log . Printf ( "theme.OverridenTemplates: %+v\n" , th . OverridenTemplates )
log . Printf ( "theme.IntTmplHandle: %+v\n" , th . IntTmplHandle )
} else {
log . Print ( "no overrides for " + th . Name )
}
for i , res := range th . Resources {
ext := filepath . Ext ( res . Name )
switch ext {
case ".css" :
res . Type = ResTypeSheet
case ".js" :
res . Type = ResTypeScript
}
switch res . Location {
case "global" :
res . LocID = LocGlobal
case "frontend" :
res . LocID = LocFront
case "panel" :
res . LocID = LocPanel
}
th . Resources [ i ] = res
}
for _ , dock := range th . Docks {
if id , ok := DockToID [ dock ] ; ok {
th . DocksID = append ( th . DocksID , id )
}
}
// TODO: Bind the built template, or an interpreted one for any dock overrides this theme has
themes [ th . Name ] = th
ThemesSlice = append ( ThemesSlice , th )
}
if defaultTheme == "" {
defaultTheme = lastTheme
}
DefaultThemeBox . Store ( defaultTheme )
return themes , nil
2017-01-01 15:45:43 +00:00
}
2018-05-27 09:36:35 +00:00
// TODO: Make the initThemes and LoadThemes functions less confusing
// ? - Delete themes which no longer exist in the themes folder from the database?
2019-10-01 21:06:22 +00:00
func ( t ThemeList ) LoadActiveStatus ( ) error {
2022-02-21 03:32:53 +00:00
ChangeDefaultThemeMutex . Lock ( )
defer ChangeDefaultThemeMutex . Unlock ( )
rows , e := themeStmts . getAll . Query ( )
if e != nil {
return e
}
defer rows . Close ( )
var uname string
var defaultThemeSwitch bool
for rows . Next ( ) {
e = rows . Scan ( & uname , & defaultThemeSwitch )
if e != nil {
return e
}
// Was the theme deleted at some point?
theme , ok := t [ uname ]
if ! ok {
continue
}
if defaultThemeSwitch {
DebugLogf ( "Loading the default theme '%s'" , theme . Name )
theme . Active = true
DefaultThemeBox . Store ( theme . Name )
theme . MapTemplates ( )
} else {
DebugLogf ( "Loading the theme '%s'" , theme . Name )
theme . Active = false
}
t [ uname ] = theme
}
return rows . Err ( )
2017-01-01 15:45:43 +00:00
}
2019-10-01 21:06:22 +00:00
func ( t ThemeList ) LoadStaticFiles ( ) error {
2022-02-21 03:32:53 +00:00
for _ , theme := range t {
if e := theme . LoadStaticFiles ( ) ; e != nil {
return e
}
}
return nil
2017-01-01 15:45:43 +00:00
}
2017-11-11 04:06:16 +00:00
func ResetTemplateOverrides ( ) {
2022-02-21 03:32:53 +00:00
log . Print ( "Resetting the template overrides" )
for name := range overridenTemplates {
log . Print ( "Resetting '" + name + "' template override" )
originPointer , ok := TmplPtrMap [ "o_" + name ]
if ! ok {
log . Print ( "The origin template doesn't exist!" )
return
}
destTmplPtr , ok := TmplPtrMap [ name ]
if ! ok {
log . Print ( "The destination template doesn't exist!" )
return
}
// Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go
oPtr , ok := originPointer . ( func ( interface { } , io . Writer ) error )
if ! ok {
log . Print ( "name: " , name )
LogError ( errors . New ( "Unknown destination template type!" ) )
return
}
dPtr , ok := destTmplPtr . ( * func ( interface { } , io . Writer ) error )
if ! ok {
LogError ( errors . New ( "The source and destination templates are incompatible" ) )
return
}
* dPtr = oPtr
log . Print ( "The template override was reset" )
}
overridenTemplates = make ( map [ string ] bool )
log . Print ( "All of the template overrides have been reset" )
2017-01-01 15:45:43 +00:00
}
2017-08-13 11:22:34 +00:00
2017-09-03 04:50:31 +00:00
// CreateThemeTemplate creates a theme template on the current default theme
2020-01-14 05:07:00 +00:00
func CreateThemeTemplate ( theme , name string ) {
2022-02-21 03:32:53 +00:00
Themes [ theme ] . TmplPtr [ name ] = func ( pi Page , w http . ResponseWriter ) error {
mapping , ok := Themes [ DefaultThemeBox . Load ( ) . ( string ) ] . TemplatesMap [ name ]
if ! ok {
mapping = name
}
return DefaultTemplates . ExecuteTemplate ( w , mapping + ".html" , pi )
}
2017-08-13 11:22:34 +00:00
}
2017-09-10 16:57:22 +00:00
func GetDefaultThemeName ( ) string {
2022-02-21 03:32:53 +00:00
return DefaultThemeBox . Load ( ) . ( string )
2017-09-10 16:57:22 +00:00
}
func SetDefaultThemeName ( name string ) {
2022-02-21 03:32:53 +00:00
DefaultThemeBox . Store ( name )
2017-09-10 16:57:22 +00:00
}