From 107da91700f5d1d81304140874087bbc95b99f4c Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sun, 17 Jan 2021 16:50:48 +0100 Subject: [PATCH] Refactor handling of environment variables --- .gitignore | 2 +- api/database/database.go | 24 +++++++----- api/database/drivers/database_drivers.go | 6 ++- api/example.env | 3 ++ api/graphql/resolvers/album.go | 4 ++ api/graphql/resolvers/user.go | 2 +- api/routes/photos.go | 2 +- api/routes/videos.go | 2 +- api/scanner/cleanup_media.go | 4 +- api/scanner/process_photo.go | 6 +-- api/scanner/scanner_user.go | 7 ++-- api/server.go | 7 ++-- api/utils/Endpoints.go | 25 ++++++------ api/utils/environment_variables.go | 50 ++++++++++++++++++++++++ docker-compose.example.yml | 2 +- 15 files changed, 105 insertions(+), 41 deletions(-) create mode 100644 api/utils/environment_variables.go diff --git a/.gitignore b/.gitignore index 20f06d09..3418feca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # See https://help.github.com/ignore-files/ for more about ignoring files. cache/ -photo_cache/ +media_cache/ /photos_path photoview.db diff --git a/api/database/database.go b/api/database/database.go index 78d29d45..1a11c509 100644 --- a/api/database/database.go +++ b/api/database/database.go @@ -2,13 +2,14 @@ package database import ( "context" + "fmt" "log" "net/url" - "os" "time" "github.com/photoview/photoview/api/database/drivers" "github.com/photoview/photoview/api/graphql/models" + "github.com/photoview/photoview/api/utils" "github.com/pkg/errors" "gorm.io/driver/mysql" @@ -18,13 +19,14 @@ import ( ) func getMysqlAddress() (*url.URL, error) { - address, err := url.Parse(os.Getenv("PHOTOVIEW_MYSQL_URL")) - if err != nil { - return nil, errors.Wrap(err, "Could not parse mysql url") + addressString := utils.EnvMysqlURL.GetValue() + if addressString == "" { + return nil, errors.New(fmt.Sprintf("Environment variable %s missing, exiting", utils.EnvMysqlURL.GetName())) } - if address.String() == "" { - return nil, errors.New("Environment variable PHOTOVIEW_MYSQL_URL missing, exiting") + address, err := url.Parse(addressString) + if err != nil { + return nil, errors.Wrap(err, "Could not parse mysql url") } queryValues := address.Query() @@ -36,7 +38,7 @@ func getMysqlAddress() (*url.URL, error) { } func getSqliteAddress() (*url.URL, error) { - path := os.Getenv("PHOTOVIEW_SQLITE_PATH") + path := utils.EnvSqlitePath.GetValue() if path == "" { path = "photoview.db" } @@ -62,8 +64,12 @@ func SetupDatabase() (*gorm.DB, error) { config := gorm.Config{} - // Enable database debug logging - config.Logger = logger.Default.LogMode(logger.Info) + // Configure database logging + if utils.DevelopmentMode() { + config.Logger = logger.Default.LogMode(logger.Info) + } else { + config.Logger = logger.Default.LogMode(logger.Warn) + } var databaseDialect gorm.Dialector switch drivers.DatabaseDriver() { diff --git a/api/database/drivers/database_drivers.go b/api/database/drivers/database_drivers.go index a4e1211a..eab45ef2 100644 --- a/api/database/drivers/database_drivers.go +++ b/api/database/drivers/database_drivers.go @@ -1,10 +1,12 @@ package drivers import ( - "os" "strings" + + "github.com/photoview/photoview/api/utils" ) +// DatabaseDriverType represents the name of a database driver type DatabaseDriverType string const ( @@ -15,7 +17,7 @@ const ( func DatabaseDriver() DatabaseDriverType { var driver DatabaseDriverType - driverString := strings.ToLower(os.Getenv("PHOTOVIEW_DATABASE_DRIVER")) + driverString := strings.ToLower(utils.EnvDatabaseDriver.GetValue()) switch driverString { case "mysql": diff --git a/api/example.env b/api/example.env index 7e6a25af..27227f74 100644 --- a/api/example.env +++ b/api/example.env @@ -13,6 +13,9 @@ PHOTOVIEW_LISTEN_PORT=4001 PHOTOVIEW_API_ENDPOINT=http://localhost:4001/ PHOTOVIEW_UI_ENDPOINT=http://localhost:1234/ +# Path where media should be cached, defaults to ./media_cache +# PHOTOVIEW_MEDIA_CACHE=./media_cache + # Set to 1 for the server to also serve the built static ui files PHOTOVIEW_SERVE_UI=0 # When PHOTOVIEW_SERVE_UI is 1, PHOTOVIEW_PUBLIC_ENDPOINT is used instead of PHOTOVIEW_API_ENDPOINT and PHOTOVIEW_UI_ENDPOINT diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go index c19998e9..b6a6a82b 100644 --- a/api/graphql/resolvers/album.go +++ b/api/graphql/resolvers/album.go @@ -20,6 +20,10 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onl return nil, err } + if len(user.Albums) == 0 { + return nil, nil + } + userAlbumIDs := make([]int, len(user.Albums)) for i, album := range user.Albums { userAlbumIDs[i] = album.ID diff --git a/api/graphql/resolvers/user.go b/api/graphql/resolvers/user.go index 67ae2008..d7ed5914 100644 --- a/api/graphql/resolvers/user.go +++ b/api/graphql/resolvers/user.go @@ -335,7 +335,7 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, if deletedAlbumIDs != nil { // Delete albums from cache for _, id := range deletedAlbumIDs { - cacheAlbumPath := path.Join(scanner.PhotoCache(), strconv.Itoa(id)) + cacheAlbumPath := path.Join(scanner.MediaCachePath(), strconv.Itoa(id)) if err := os.RemoveAll(cacheAlbumPath); err != nil { return nil, err diff --git a/api/routes/photos.go b/api/routes/photos.go index 06b00728..9da3826d 100644 --- a/api/routes/photos.go +++ b/api/routes/photos.go @@ -41,7 +41,7 @@ func RegisterPhotoRoutes(db *gorm.DB, router *mux.Router) { var cachedPath string if mediaURL.Purpose == models.PhotoThumbnail || mediaURL.Purpose == models.PhotoHighRes || mediaURL.Purpose == models.VideoThumbnail { - cachedPath = path.Join(scanner.PhotoCache(), strconv.Itoa(int(media.AlbumID)), strconv.Itoa(int(mediaURL.MediaID)), mediaURL.MediaName) + cachedPath = path.Join(scanner.MediaCachePath(), strconv.Itoa(int(media.AlbumID)), strconv.Itoa(int(mediaURL.MediaID)), mediaURL.MediaName) } else if mediaURL.Purpose == models.MediaOriginal { cachedPath = media.Path } else { diff --git a/api/routes/videos.go b/api/routes/videos.go index b193c433..c774537a 100644 --- a/api/routes/videos.go +++ b/api/routes/videos.go @@ -40,7 +40,7 @@ func RegisterVideoRoutes(db *gorm.DB, router *mux.Router) { var cachedPath string if mediaURL.Purpose == models.VideoWeb { - cachedPath = path.Join(scanner.PhotoCache(), strconv.Itoa(int(media.AlbumID)), strconv.Itoa(int(mediaURL.MediaID)), mediaURL.MediaName) + cachedPath = path.Join(scanner.MediaCachePath(), strconv.Itoa(int(media.AlbumID)), strconv.Itoa(int(mediaURL.MediaID)), mediaURL.MediaName) } else { log.Printf("ERROR: Can not handle media_purpose for video: %s\n", mediaURL.Purpose) w.WriteHeader(http.StatusInternalServerError) diff --git a/api/scanner/cleanup_media.go b/api/scanner/cleanup_media.go index e89b2d13..f5693a04 100644 --- a/api/scanner/cleanup_media.go +++ b/api/scanner/cleanup_media.go @@ -36,7 +36,7 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error for _, media := range mediaList { mediaIDs = append(mediaIDs, media.ID) - cachePath := path.Join(PhotoCache(), strconv.Itoa(int(albumId)), strconv.Itoa(int(media.ID))) + cachePath := path.Join(MediaCachePath(), strconv.Itoa(int(albumId)), strconv.Itoa(int(media.ID))) err := os.RemoveAll(cachePath) if err != nil { deleteErrors = append(deleteErrors, errors.Wrapf(err, "delete unused cache folder (%s)", cachePath)) @@ -84,7 +84,7 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model deleteAlbumIDs := make([]int, len(albums)) for i, album := range albums { deleteAlbumIDs[i] = album.ID - cachePath := path.Join(PhotoCache(), strconv.Itoa(int(album.ID))) + cachePath := path.Join(MediaCachePath(), strconv.Itoa(int(album.ID))) err := os.RemoveAll(cachePath) if err != nil { deleteErrors = append(deleteErrors, errors.Wrapf(err, "delete unused cache folder (%s)", cachePath)) diff --git a/api/scanner/process_photo.go b/api/scanner/process_photo.go index 6f6f7d6f..9c4b4db0 100644 --- a/api/scanner/process_photo.go +++ b/api/scanner/process_photo.go @@ -202,14 +202,14 @@ func processPhoto(tx *gorm.DB, imageData *EncodeMediaData, photoCachePath *strin func makeMediaCacheDir(media *models.Media) (*string, error) { // Make root cache dir if not exists - if _, err := os.Stat(PhotoCache()); os.IsNotExist(err) { - if err := os.Mkdir(PhotoCache(), os.ModePerm); err != nil { + if _, err := os.Stat(MediaCachePath()); os.IsNotExist(err) { + if err := os.Mkdir(MediaCachePath(), os.ModePerm); err != nil { return nil, errors.Wrap(err, "could not make root image cache directory") } } // Make album cache dir if not exists - albumCachePath := path.Join(PhotoCache(), strconv.Itoa(int(media.AlbumID))) + albumCachePath := path.Join(MediaCachePath(), strconv.Itoa(int(media.AlbumID))) if _, err := os.Stat(albumCachePath); os.IsNotExist(err) { if err := os.Mkdir(albumCachePath, os.ModePerm); err != nil { return nil, errors.Wrap(err, "could not make album image cache directory") diff --git a/api/scanner/scanner_user.go b/api/scanner/scanner_user.go index bee7c3d0..0b3f081e 100644 --- a/api/scanner/scanner_user.go +++ b/api/scanner/scanner_user.go @@ -207,10 +207,11 @@ func ScannerError(format string, args ...interface{}) { }) } -func PhotoCache() string { - photoCache := os.Getenv("PHOTO_CACHE") +// MediaCachePath returns the path for where the media cache is located on the file system +func MediaCachePath() string { + photoCache := utils.EnvMediaCachePath.GetValue() if photoCache == "" { - photoCache = "./photo_cache" + photoCache = "./media_cache" } return photoCache diff --git a/api/server.go b/api/server.go index f2223b4f..506d2b86 100644 --- a/api/server.go +++ b/api/server.go @@ -3,7 +3,6 @@ package main import ( "log" "net/http" - "os" "path" "github.com/gorilla/handlers" @@ -29,7 +28,7 @@ func main() { log.Println("No .env file found") } - devMode := os.Getenv("PHOTOVIEW_DEVELOPMENT_MODE") == "1" + devMode := utils.DevelopmentMode() db, err := database.SetupDatabase() if err != nil { @@ -92,7 +91,7 @@ func main() { videoRouter := endpointRouter.PathPrefix("/video").Subrouter() routes.RegisterVideoRoutes(db, videoRouter) - shouldServeUI := os.Getenv("PHOTOVIEW_SERVE_UI") == "1" + shouldServeUI := utils.ShouldServeUI() if shouldServeUI { spa := routes.NewSpaHandler("/ui", "index.html") @@ -111,7 +110,7 @@ func main() { log.Printf("Photoview UI public endpoint ready at %s\n", uiEndpoint.String()) if !shouldServeUI { - log.Printf("Notice: UI is not served by the the api (PHOTOVIEW_SERVE_UI=0)") + log.Printf("Notice: UI is not served by the the api (%s=0)", utils.EnvServeUI.GetName()) } } diff --git a/api/utils/Endpoints.go b/api/utils/Endpoints.go index af5500b4..30ec5f7f 100644 --- a/api/utils/Endpoints.go +++ b/api/utils/Endpoints.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "net/url" - "os" "path" "strconv" ) @@ -12,7 +11,7 @@ import ( func ApiListenUrl() *url.URL { const defaultPort = "4001" - shouldServeUI := os.Getenv("PHOTOVIEW_SERVE_UI") == "1" + shouldServeUI := ShouldServeUI() apiPrefix := "/" if shouldServeUI { @@ -21,19 +20,19 @@ func ApiListenUrl() *url.URL { var listenAddr string - listenAddr = os.Getenv("PHOTOVIEW_LISTEN_IP") + listenAddr = EnvListenIP.GetValue() if listenAddr == "" { listenAddr = "127.0.0.1" } - listenPortStr := os.Getenv("PHOTOVIEW_LISTEN_PORT") + listenPortStr := EnvListenPort.GetValue() if listenPortStr == "" { listenPortStr = defaultPort } listenPort, err := strconv.Atoi(listenPortStr) if err != nil { - log.Fatalf("PHOTOVIEW_LISTEN_PORT must be a number: '%s'\n%s", listenPortStr, err) + log.Fatalf("%s must be a number: '%s'\n%s", EnvListenPort.GetName(), listenPortStr, err) } apiUrl, err := url.Parse(fmt.Sprintf("http://%s:%d", listenAddr, listenPort)) @@ -46,16 +45,16 @@ func ApiListenUrl() *url.URL { } func ApiEndpointUrl() *url.URL { - apiEndpointStr := os.Getenv("PHOTOVIEW_API_ENDPOINT") + apiEndpointStr := EnvAPIEndpoint.GetValue() - shouldServeUI := os.Getenv("PHOTOVIEW_SERVE_UI") == "1" + shouldServeUI := ShouldServeUI() if shouldServeUI { - apiEndpointStr = os.Getenv("PHOTOVIEW_PUBLIC_ENDPOINT") + apiEndpointStr = EnvPublicEndpoint.GetValue() } apiEndpointUrl, err := url.Parse(apiEndpointStr) if err != nil { - log.Fatalf("ERROR: Environment variable PHOTOVIEW_API_ENDPOINT is not a proper url") + log.Fatalf("ERROR: Environment variable %s is not a proper url (%s)", EnvAPIEndpoint.GetName(), EnvAPIEndpoint.GetValue()) } if shouldServeUI { @@ -66,16 +65,16 @@ func ApiEndpointUrl() *url.URL { } func UiEndpointUrl() *url.URL { - uiEndpointStr := os.Getenv("PHOTOVIEW_UI_ENDPOINT") + uiEndpointStr := EnvUIEndpoint.GetValue() - shouldServeUI := os.Getenv("PHOTOVIEW_SERVE_UI") == "1" + shouldServeUI := ShouldServeUI() if shouldServeUI { - uiEndpointStr = os.Getenv("PHOTOVIEW_PUBLIC_ENDPOINT") + uiEndpointStr = EnvPublicEndpoint.GetValue() } uiEndpointUrl, err := url.Parse(uiEndpointStr) if err != nil { - log.Fatalf("ERROR: Environment variable PHOTOVIEW_UI_ENDPOINT is not a proper url") + log.Fatalf("ERROR: Environment variable %s is not a proper url (%s)", EnvUIEndpoint.GetName(), EnvUIEndpoint.GetValue()) } return uiEndpointUrl diff --git a/api/utils/environment_variables.go b/api/utils/environment_variables.go new file mode 100644 index 00000000..15f5a2e6 --- /dev/null +++ b/api/utils/environment_variables.go @@ -0,0 +1,50 @@ +package utils + +import "os" + +// EnvironmentVariable represents the name of an environment variable used to configure Photoview +type EnvironmentVariable string + +// General options +const ( + EnvDevelopmentMode EnvironmentVariable = "PHOTOVIEW_DEVELOPMENT_MODE" + EnvServeUI EnvironmentVariable = "PHOTOVIEW_SERVE_UI" + EnvMediaCachePath EnvironmentVariable = "PHOTOVIEW_MEDIA_CACHE" +) + +// Network related +const ( + EnvListenIP EnvironmentVariable = "PHOTOVIEW_LISTEN_IP" + EnvListenPort EnvironmentVariable = "PHOTOVIEW_LISTEN_PORT" + EnvAPIEndpoint EnvironmentVariable = "PHOTOVIEW_API_ENDPOINT" + EnvUIEndpoint EnvironmentVariable = "PHOTOVIEW_UI_ENDPOINT" + EnvPublicEndpoint EnvironmentVariable = "PHOTOVIEW_PUBLIC_ENDPOINT" +) + +// Database related +const ( + EnvDatabaseDriver EnvironmentVariable = "PHOTOVIEW_DATABASE_DRIVER" + EnvMysqlURL EnvironmentVariable = "PHOTOVIEW_MYSQL_URL" + EnvSqlitePath EnvironmentVariable = "PHOTOVIEW_SQLITE_PATH" +) + +// GetName returns the name of the environment variable itself +func (v EnvironmentVariable) GetName() string { + return string(v) +} + +// GetValue returns the value of the environment +func (v EnvironmentVariable) GetValue() string { + return os.Getenv(string(v)) +} + +// ShouldServeUI whether or not the "serve ui" option is enabled +func ShouldServeUI() bool { + return EnvServeUI.GetValue() == "1" +} + +// DevelopmentMode describes whether or not the server is running in development mode, +// and should thus print debug informations and enable other features related to developing. +func DevelopmentMode() bool { + return EnvDevelopmentMode.GetValue() == "1" +} diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 481fb60e..a3cb8b6b 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -24,7 +24,7 @@ services: - PHOTOVIEW_MYSQL_URL=photoview:photosecret@tcp(db)/photoview - PHOTOVIEW_LISTEN_IP=photoview - PHOTOVIEW_LISTEN_PORT=80 - - PHOTO_CACHE=/app/cache + - PHOTOVIEW_MEDIA_CACHE=/app/cache # Change This: The publicly exposed url # For example if the server is available from the domain example.com,