Remove test and frontend packages; app and test context refactoring

This commit is contained in:
Michael Mayer 2019-05-03 18:57:28 +02:00
parent 6cba9d061f
commit 71adb35cff
54 changed files with 1027 additions and 1302 deletions

View file

@ -5,21 +5,24 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PhotoPrism</title>
<title>{{ .name }}</title>
<meta property="og:title" content="PhotoPrism: Try our demo! 🌈"/>
<meta property="og:image" content="https://dl.photoprism.org/assets/img/preview.jpg"/>
<meta property="og:url" content="https://demo.photoprism.org/"/>
<meta property="og:description" content="Personal photo management powered by Go and Google TensorFlow. Free and open-source. Made with ❤️ in Berlin." />
<meta property="og:description"
content="Personal photo management powered by Go and Google TensorFlow. Free and open-source. Made with ❤️ in Berlin."/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content="PhotoPrism: Try our demo! 🌈"/>
<meta name="twitter:description" content="Personal photo management powered by Go and Google TensorFlow. Free and open-source. Made with ❤️ in Berlin." />
<meta name="twitter:description"
content="Personal photo management powered by Go and Google TensorFlow. Free and open-source. Made with ❤️ in Berlin."/>
<meta name="twitter:image" content="https://dl.photoprism.org/assets/img/preview.jpg"/>
<meta name="twitter:site" content="@browseyourlife"/>
<meta name="author" content="PhotoPrism.org">
<meta name="description" content="Personal photo management powered by Go and Google TensorFlow. Free and open-source. Made with ❤️ in Berlin." />
<meta name="description"
content="Personal photo management powered by Go and Google TensorFlow. Free and open-source. Made with ❤️ in Berlin."/>
<link rel="shortcut icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/assets/favicons/favicon.png">
@ -29,8 +32,8 @@
<script>
window.appConfig = {
appName: "{{ .appName }}",
appVersion: "{{ .appVersion }}",
name: "{{ .name }}",
version: "{{ .version }}",
debug: {{ .debug }},
cameras: {{ .cameras }},
countries: {{ .countries }}
@ -39,12 +42,20 @@
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade
your browser</a> to improve your experience.</p>
<![endif]-->
<div id="app" class="container">
<div class="loading">
<div class="v-progress-linear" style="height: 7px;"><div class="v-progress-linear__background amber" style="height: 7px; opacity: 0.3; width: 100%;"></div><div class="v-progress-linear__bar"><div class="v-progress-linear__bar__indeterminate v-progress-linear__bar__indeterminate--active"><div class="v-progress-linear__bar__indeterminate long amber"></div><div class="v-progress-linear__bar__indeterminate short amber"></div></div><!----></div></div>
<div class="v-progress-linear" style="height: 7px;">
<div class="v-progress-linear__background amber" style="height: 7px; opacity: 0.3; width: 100%;"></div>
<div class="v-progress-linear__bar">
<div class="v-progress-linear__bar__indeterminate v-progress-linear__bar__indeterminate--active">
<div class="v-progress-linear__bar__indeterminate long amber"></div>
<div class="v-progress-linear__bar__indeterminate short amber"></div>
</div><!----></div>
</div>
</div>
</div>
<footer>

View file

@ -5,19 +5,18 @@ import (
"net/http/httptest"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
)
// API test helper
func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf photoprism.Config) {
conf = test.NewConfig()
func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, ctx *context.Context) {
ctx = context.TestContext()
gin.SetMode(gin.TestMode)
app = gin.New()
router = app.Group("/api/v1")
return app, router, conf
return app, router, ctx
}
// See https://medium.com/@craigchilds94/testing-gin-json-responses-1f258ce3b0b1

View file

@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"github.com/photoprism/photoprism/internal/context"
log "github.com/sirupsen/logrus"
"github.com/gin-gonic/gin"
@ -26,10 +27,10 @@ import (
// before: date Find photos taken before (format: "2006-01-02")
// after: date Find photos taken after (format: "2006-01-02")
// favorites: bool Find favorites only
func GetPhotos(router *gin.RouterGroup, conf photoprism.Config) {
func GetPhotos(router *gin.RouterGroup, ctx *context.Context) {
router.GET("/photos", func(c *gin.Context) {
var form forms.PhotoSearchForm
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
search := photoprism.NewSearch(ctx.OriginalsPath(), ctx.Db())
err := c.MustBindWith(&form, binding.Form)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@ -52,12 +53,12 @@ func GetPhotos(router *gin.RouterGroup, conf photoprism.Config) {
//
// Parameters:
// id: int Photo ID as returned by the API
func LikePhoto(router *gin.RouterGroup, conf photoprism.Config) {
func LikePhoto(router *gin.RouterGroup, ctx *context.Context) {
router.POST("/photos/:id/like", func(c *gin.Context) {
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
search := photoprism.NewSearch(ctx.OriginalsPath(), ctx.Db())
photoID, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
log.Printf("could not find image for id: %s", err.Error())
log.Errorf("could not find image for id: %s", err.Error())
c.Data(http.StatusNotFound, "image", []byte(""))
return
}
@ -70,7 +71,7 @@ func LikePhoto(router *gin.RouterGroup, conf photoprism.Config) {
}
photo.PhotoFavorite = true
conf.Db().Save(&photo)
ctx.Db().Save(&photo)
c.JSON(http.StatusOK, http.Response{})
})
}
@ -79,12 +80,12 @@ func LikePhoto(router *gin.RouterGroup, conf photoprism.Config) {
//
// Parameters:
// id: int Photo ID as returned by the API
func DislikePhoto(router *gin.RouterGroup, conf photoprism.Config) {
func DislikePhoto(router *gin.RouterGroup, ctx *context.Context) {
router.DELETE("/photos/:id/like", func(c *gin.Context) {
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
search := photoprism.NewSearch(ctx.OriginalsPath(), ctx.Db())
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
log.Printf("could not find image for id: %s", err.Error())
log.Errorf("could not find image for id: %s", err.Error())
c.Data(http.StatusNotFound, "image", []byte(""))
return
}
@ -97,7 +98,7 @@ func DislikePhoto(router *gin.RouterGroup, conf photoprism.Config) {
}
photo.PhotoFavorite = false
conf.Db().Save(&photo)
ctx.Db().Save(&photo)
c.JSON(http.StatusOK, http.Response{})
})
}

View file

@ -8,9 +8,9 @@ import (
)
func TestGetPhotos(t *testing.T) {
app, router, conf := NewApiTest()
app, router, ctx := NewApiTest()
GetPhotos(router, conf)
GetPhotos(router, ctx)
result := PerformRequest(app, "GET", "/api/v1/photos?count=10")
@ -18,9 +18,9 @@ func TestGetPhotos(t *testing.T) {
}
func TestLikePhoto(t *testing.T) {
app, router, conf := NewApiTest()
app, router, ctx := NewApiTest()
LikePhoto(router, conf)
LikePhoto(router, ctx)
result := PerformRequest(app, "POST", "/api/v1/photos/1/like")
@ -31,9 +31,9 @@ func TestLikePhoto(t *testing.T) {
}
func TestDislikePhoto(t *testing.T) {
app, router, conf := NewApiTest()
app, router, ctx := NewApiTest()
DislikePhoto(router, conf)
DislikePhoto(router, ctx)
result := PerformRequest(app, "DELETE", "/api/v1/photos/1/like")

View file

@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"github.com/photoprism/photoprism/internal/context"
log "github.com/sirupsen/logrus"
"github.com/gin-gonic/gin"
@ -22,18 +23,18 @@ var photoIconSvg = []byte(`
// type: string Format, either "fit" or "square"
// size: int Size in pixels
// hash: string The file hash as returned by the search API
func GetThumbnail(router *gin.RouterGroup, conf photoprism.Config) {
func GetThumbnail(router *gin.RouterGroup, ctx *context.Context) {
router.GET("/thumbnails/:type/:size/:hash", func(c *gin.Context) {
fileHash := c.Param("hash")
thumbnailType := c.Param("type")
size, err := strconv.Atoi(c.Param("size"))
if err != nil {
log.Printf("invalid size: %s", c.Param("size"))
log.Errorf("invalid size: %s", c.Param("size"))
c.Data(400, "image/svg+xml", photoIconSvg)
return
}
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
search := photoprism.NewSearch(ctx.OriginalsPath(), ctx.Db())
file, err := search.FindFileByHash(fileHash)
if err != nil {
@ -41,36 +42,36 @@ func GetThumbnail(router *gin.RouterGroup, conf photoprism.Config) {
return
}
fileName := fmt.Sprintf("%s/%s", conf.OriginalsPath(), file.FileName)
fileName := fmt.Sprintf("%s/%s", ctx.OriginalsPath(), file.FileName)
mediaFile, err := photoprism.NewMediaFile(fileName)
if err != nil {
log.Printf("could not find image for thumbnail: %s", err.Error())
log.Errorf("could not find image for thumbnail: %s", err.Error())
c.Data(404, "image/svg+xml", photoIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore
file.FileMissing = true
conf.Db().Save(&file)
ctx.Db().Save(&file)
return
}
switch thumbnailType {
case "fit":
if thumbnail, err := mediaFile.Thumbnail(conf.ThumbnailsPath(), size); err == nil {
if thumbnail, err := mediaFile.Thumbnail(ctx.ThumbnailsPath(), size); err == nil {
c.File(thumbnail.Filename())
} else {
log.Printf("could not create thumbnail: %s", err.Error())
log.Errorf("could not create thumbnail: %s", err.Error())
c.Data(400, "image/svg+xml", photoIconSvg)
}
case "square":
if thumbnail, err := mediaFile.SquareThumbnail(conf.ThumbnailsPath(), size); err == nil {
if thumbnail, err := mediaFile.SquareThumbnail(ctx.ThumbnailsPath(), size); err == nil {
c.File(thumbnail.Filename())
} else {
log.Printf("could not create square thumbnail: %s", err.Error())
log.Errorf("could not create square thumbnail: %s", err.Error())
c.Data(400, "image/svg+xml", photoIconSvg)
}
default:
log.Printf("unknown thumbnail type: %s", thumbnailType)
log.Errorf("unknown thumbnail type: %s", thumbnailType)
c.Data(400, "image/svg+xml", photoIconSvg)
}
})

View file

@ -15,41 +15,41 @@ var ConfigCommand = cli.Command{
}
func configAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
fmt.Printf("NAME VALUE\n")
fmt.Printf("debug %t\n", conf.Debug())
fmt.Printf("log-level %s\n", conf.LogLevel())
fmt.Printf("config-file %s\n", conf.ConfigFile())
fmt.Printf("app-name %s\n", conf.AppName())
fmt.Printf("app-version %s\n", conf.AppVersion())
fmt.Printf("app-copyright %s\n", conf.AppCopyright())
fmt.Printf("name %s\n", app.Name())
fmt.Printf("version %s\n", app.Version())
fmt.Printf("copyright %s\n", app.Copyright())
fmt.Printf("debug %t\n", app.Debug())
fmt.Printf("log-level %s\n", app.LogLevel())
fmt.Printf("config-file %s\n", app.ConfigFile())
fmt.Printf("database-driver %s\n", conf.DatabaseDriver())
fmt.Printf("database-dsn %s\n", conf.DatabaseDsn())
fmt.Printf("database-driver %s\n", app.DatabaseDriver())
fmt.Printf("database-dsn %s\n", app.DatabaseDsn())
fmt.Printf("http-host %s\n", conf.HttpServerHost())
fmt.Printf("http-port %d\n", conf.HttpServerPort())
fmt.Printf("http-mode %s\n", conf.HttpServerMode())
fmt.Printf("http-host %s\n", app.HttpServerHost())
fmt.Printf("http-port %d\n", app.HttpServerPort())
fmt.Printf("http-mode %s\n", app.HttpServerMode())
fmt.Printf("sql-host %s\n", conf.SqlServerHost())
fmt.Printf("sql-port %d\n", conf.SqlServerPort())
fmt.Printf("sql-password %s\n", conf.SqlServerPassword())
fmt.Printf("sql-path %s\n", conf.SqlServerPath())
fmt.Printf("sql-host %s\n", app.SqlServerHost())
fmt.Printf("sql-port %d\n", app.SqlServerPort())
fmt.Printf("sql-password %s\n", app.SqlServerPassword())
fmt.Printf("sql-path %s\n", app.SqlServerPath())
fmt.Printf("assets-path %s\n", conf.AssetsPath())
fmt.Printf("originals-path %s\n", conf.OriginalsPath())
fmt.Printf("import-path %s\n", conf.ImportPath())
fmt.Printf("export-path %s\n", conf.ExportPath())
fmt.Printf("cache-path %s\n", conf.CachePath())
fmt.Printf("thumbnails-path %s\n", conf.ThumbnailsPath())
fmt.Printf("tf-model-path %s\n", conf.TensorFlowModelPath())
fmt.Printf("templates-path %s\n", conf.HttpTemplatesPath())
fmt.Printf("favicons-path %s\n", conf.HttpFaviconsPath())
fmt.Printf("public-path %s\n", conf.HttpPublicPath())
fmt.Printf("public-build-path %s\n", conf.HttpPublicBuildPath())
fmt.Printf("assets-path %s\n", app.AssetsPath())
fmt.Printf("originals-path %s\n", app.OriginalsPath())
fmt.Printf("import-path %s\n", app.ImportPath())
fmt.Printf("export-path %s\n", app.ExportPath())
fmt.Printf("cache-path %s\n", app.CachePath())
fmt.Printf("thumbnails-path %s\n", app.ThumbnailsPath())
fmt.Printf("tf-model-path %s\n", app.TensorFlowModelPath())
fmt.Printf("templates-path %s\n", app.HttpTemplatesPath())
fmt.Printf("favicons-path %s\n", app.HttpFaviconsPath())
fmt.Printf("public-path %s\n", app.HttpPublicPath())
fmt.Printf("public-build-path %s\n", app.HttpPublicBuildPath())
fmt.Printf("darktable-cli %s\n", conf.DarktableCli())
fmt.Printf("darktable-cli %s\n", app.DarktableCli())
return nil
}

View file

@ -3,16 +3,16 @@ package commands
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestConfigCommand(t *testing.T) {
var err error
ctx := test.CliContext()
ctx := context.CliTestContext()
output := test.Capture(func() {
output := context.CaptureOutput(func() {
err = ConfigCommand.Run(ctx)
})

View file

@ -15,17 +15,17 @@ var ConvertCommand = cli.Command{
}
func convertAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
if err := conf.CreateDirectories(); err != nil {
if err := app.CreateDirectories(); err != nil {
return err
}
log.Infof("converting RAW images in %s to JPEG", conf.OriginalsPath())
log.Infof("converting RAW images in %s to JPEG", app.OriginalsPath())
converter := photoprism.NewConverter(conf.DarktableCli())
converter := photoprism.NewConverter(app.DarktableCli())
converter.ConvertAll(conf.OriginalsPath())
converter.ConvertAll(app.OriginalsPath())
log.Infof("image conversion complete")

View file

@ -40,9 +40,9 @@ var exportFlags = []cli.Flag{
}
func exportAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
if err := conf.CreateDirectories(); err != nil {
if err := app.CreateDirectories(); err != nil {
return err
}
@ -70,13 +70,13 @@ func exportAction(ctx *cli.Context) error {
}
}
exportPath := fmt.Sprintf("%s/%s", conf.ExportPath(), name)
exportPath := fmt.Sprintf("%s/%s", app.ExportPath(), name)
size := ctx.Int("size")
originals := photoprism.FindOriginalsByDate(conf.OriginalsPath(), afterDate, beforeDate)
originals := photoprism.FindOriginalsByDate(app.OriginalsPath(), afterDate, beforeDate)
log.Infof("exporting photos to %s", exportPath)
photoprism.ExportPhotosFromOriginals(originals, conf.ThumbnailsPath(), exportPath, size)
photoprism.ExportPhotosFromOriginals(originals, app.ThumbnailsPath(), exportPath, size)
log.Infof("photo export complete")

View file

@ -15,25 +15,25 @@ var ImportCommand = cli.Command{
}
func importAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
if err := conf.CreateDirectories(); err != nil {
if err := app.CreateDirectories(); err != nil {
return err
}
conf.MigrateDb()
app.MigrateDb()
log.Infof("importing photos from %s", conf.ImportPath())
log.Infof("importing photos from %s", app.ImportPath())
tensorFlow := photoprism.NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := photoprism.NewTensorFlow(app.TensorFlowModelPath())
indexer := photoprism.NewIndexer(conf.OriginalsPath(), tensorFlow, conf.Db())
indexer := photoprism.NewIndexer(app.OriginalsPath(), tensorFlow, app.Db())
converter := photoprism.NewConverter(conf.DarktableCli())
converter := photoprism.NewConverter(app.DarktableCli())
importer := photoprism.NewImporter(conf.OriginalsPath(), indexer, converter)
importer := photoprism.NewImporter(app.OriginalsPath(), indexer, converter)
importer.ImportPhotosFromDirectory(conf.ImportPath())
importer.ImportPhotosFromDirectory(app.ImportPath())
log.Info("photo import complete")

View file

@ -15,19 +15,19 @@ var IndexCommand = cli.Command{
}
func indexAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
if err := conf.CreateDirectories(); err != nil {
if err := app.CreateDirectories(); err != nil {
return err
}
conf.MigrateDb()
app.MigrateDb()
log.Infof("indexing photos in %s", conf.OriginalsPath())
log.Infof("indexing photos in %s", app.OriginalsPath())
tensorFlow := photoprism.NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := photoprism.NewTensorFlow(app.TensorFlowModelPath())
indexer := photoprism.NewIndexer(conf.OriginalsPath(), tensorFlow, conf.Db())
indexer := photoprism.NewIndexer(app.OriginalsPath(), tensorFlow, app.Db())
files := indexer.IndexAll()

View file

@ -14,11 +14,11 @@ var MigrateCommand = cli.Command{
}
func migrateAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext( ctx)
log.Infoln("migrating database")
conf.MigrateDb()
app.MigrateDb()
log.Infoln("database migration complete")

View file

@ -38,21 +38,21 @@ var startFlags = []cli.Flag{
}
func startAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
if conf.HttpServerPort() < 1 {
if app.HttpServerPort() < 1 {
log.Fatal("server port must be a positive integer")
}
if err := conf.CreateDirectories(); err != nil {
if err := app.CreateDirectories(); err != nil {
log.Fatal(err)
}
conf.MigrateDb()
app.MigrateDb()
log.Infof("starting web server at %s:%d", conf.HttpServerHost(), conf.HttpServerPort())
log.Infof("starting web server at %s:%d", app.HttpServerHost(), app.HttpServerPort())
server.Start(conf)
server.Start(app)
return nil
}

View file

@ -29,13 +29,13 @@ var ThumbnailsCommand = cli.Command{
}
func thumbnailsAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
if err := conf.CreateDirectories(); err != nil {
if err := app.CreateDirectories(); err != nil {
return err
}
log.Infof("creating thumbnails in %s", conf.ThumbnailsPath())
log.Infof("creating thumbnails in \"%s\"", app.ThumbnailsPath())
sizes := ctx.IntSlice("size")
@ -49,7 +49,7 @@ func thumbnailsAction(ctx *cli.Context) error {
}
for _, size := range sizes {
photoprism.CreateThumbnailsFromOriginals(conf.OriginalsPath(), conf.ThumbnailsPath(), size, ctx.Bool("square"))
photoprism.CreateThumbnailsFromOriginals(app.OriginalsPath(), app.ThumbnailsPath(), size, ctx.Bool("square"))
}
log.Info("thumbnails created")

View file

@ -15,9 +15,9 @@ var VersionCommand = cli.Command{
}
func versionAction(ctx *cli.Context) error {
conf := context.NewConfig(ctx)
app := context.NewContext(ctx)
fmt.Println(conf.AppVersion())
fmt.Println(app.Version())
return nil
}

View file

@ -1,4 +1,4 @@
package test
package context
import (
"bytes"
@ -7,7 +7,7 @@ import (
)
// Returns output to stdout and stderr for testing
func Capture(f func()) string {
func CaptureOutput(f func()) string {
r, w, err := os.Pipe()
if err != nil {
panic(err)

View file

@ -1,4 +1,4 @@
package test
package context
import (
"fmt"
@ -8,8 +8,8 @@ import (
"github.com/stretchr/testify/assert"
)
func TestCapture(t *testing.T) {
result := Capture(func() {
func TestCaptureOutput(t *testing.T) {
result := CaptureOutput(func() {
fmt.Fprint(os.Stdout, "foo")
fmt.Fprint(os.Stderr, "bar")
})

View file

@ -0,0 +1,4 @@
package context
// HTTP client / Web UI config values
type ClientConfig map[string]interface{}

View file

@ -1,18 +1,10 @@
package context
import (
"os"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/kylelemons/go-gypsy/yaml"
"github.com/photoprism/photoprism/internal/frontend"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/models"
"github.com/photoprism/photoprism/internal/tidb"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@ -30,66 +22,28 @@ const (
//
// See https://github.com/photoprism/photoprism/issues/50#issuecomment-433856358
type Config struct {
appName string
appVersion string
appCopyright string
debug bool
logLevel string
configFile string
assetsPath string
cachePath string
originalsPath string
importPath string
exportPath string
sqlServerHost string
sqlServerPort uint
sqlServerPath string
sqlServerPassword string
httpServerHost string
httpServerPort int
httpServerMode string
httpServerPassword string
darktableCli string
databaseDriver string
databaseDsn string
db *gorm.DB
}
// NewConfig() creates a new configuration entity by using two methods:
//
// 1. SetValuesFromFile: This will initialize values from a yaml config file.
//
// 2. SetValuesFromCliContext: Which comes after SetValuesFromFile and overrides
// any previous values giving an option two override file configs through the CLI.
func NewConfig(ctx *cli.Context) *Config {
c := &Config{}
if ctx.GlobalBool("debug") {
c.debug = ctx.GlobalBool("debug")
}
c.appName = ctx.App.Name
c.appCopyright = ctx.App.Copyright
c.appVersion = ctx.App.Version
log.SetLevel(c.LogLevel())
log.SetFormatter(&log.TextFormatter{
DisableColors: false,
FullTimestamp: true,
})
if err := c.SetValuesFromFile(fsutil.ExpandedFilename(ctx.GlobalString("config-file"))); err != nil {
log.Info(err)
}
if err := c.SetValuesFromCliContext(ctx); err != nil {
log.Error(err)
}
log.SetLevel(c.LogLevel())
return c
Name string
Version string
Copyright string
Debug bool
LogLevel string
ConfigFile string
AssetsPath string
CachePath string
OriginalsPath string
ImportPath string
ExportPath string
SqlServerHost string
SqlServerPort uint
SqlServerPath string
SqlServerPassword string
HttpServerHost string
HttpServerPort int
HttpServerMode string
HttpServerPassword string
DarktableCli string
DatabaseDriver string
DatabaseDsn string
}
// SetValuesFromFile uses a yaml config file to initiate the configuration entity.
@ -100,77 +54,77 @@ func (c *Config) SetValuesFromFile(fileName string) error {
return err
}
c.configFile = fileName
c.ConfigFile = fileName
if debug, err := yamlConfig.GetBool("debug"); err == nil {
c.debug = debug
c.Debug = debug
}
if logLevel, err := yamlConfig.Get("log-level"); err == nil {
c.logLevel = logLevel
c.LogLevel = logLevel
}
if sqlServerHost, err := yamlConfig.Get("sql-host"); err == nil {
c.sqlServerHost = sqlServerHost
c.SqlServerHost = sqlServerHost
}
if sqlServerPort, err := yamlConfig.GetInt("sql-port"); err == nil {
c.sqlServerPort = uint(sqlServerPort)
c.SqlServerPort = uint(sqlServerPort)
}
if sqlServerPassword, err := yamlConfig.Get("sql-password"); err == nil {
c.sqlServerPassword = sqlServerPassword
c.SqlServerPassword = sqlServerPassword
}
if sqlServerPath, err := yamlConfig.Get("sql-path"); err == nil {
c.sqlServerPath = sqlServerPath
c.SqlServerPath = sqlServerPath
}
if httpServerHost, err := yamlConfig.Get("http-host"); err == nil {
c.httpServerHost = httpServerHost
c.HttpServerHost = httpServerHost
}
if httpServerPort, err := yamlConfig.GetInt("http-port"); err == nil {
c.httpServerPort = int(httpServerPort)
c.HttpServerPort = int(httpServerPort)
}
if httpServerMode, err := yamlConfig.Get("http-mode"); err == nil {
c.httpServerMode = httpServerMode
c.HttpServerMode = httpServerMode
}
if httpServerPassword, err := yamlConfig.Get("http-password"); err == nil {
c.httpServerPassword = httpServerPassword
c.HttpServerPassword = httpServerPassword
}
if assetsPath, err := yamlConfig.Get("assets-path"); err == nil {
c.assetsPath = fsutil.ExpandedFilename(assetsPath)
c.AssetsPath = fsutil.ExpandedFilename(assetsPath)
}
if cachePath, err := yamlConfig.Get("cache-path"); err == nil {
c.cachePath = fsutil.ExpandedFilename(cachePath)
c.CachePath = fsutil.ExpandedFilename(cachePath)
}
if originalsPath, err := yamlConfig.Get("originals-path"); err == nil {
c.originalsPath = fsutil.ExpandedFilename(originalsPath)
c.OriginalsPath = fsutil.ExpandedFilename(originalsPath)
}
if importPath, err := yamlConfig.Get("import-path"); err == nil {
c.importPath = fsutil.ExpandedFilename(importPath)
c.ImportPath = fsutil.ExpandedFilename(importPath)
}
if exportPath, err := yamlConfig.Get("export-path"); err == nil {
c.exportPath = fsutil.ExpandedFilename(exportPath)
c.ExportPath = fsutil.ExpandedFilename(exportPath)
}
if darktableCli, err := yamlConfig.Get("darktable-cli"); err == nil {
c.darktableCli = fsutil.ExpandedFilename(darktableCli)
c.DarktableCli = fsutil.ExpandedFilename(darktableCli)
}
if databaseDriver, err := yamlConfig.Get("database-driver"); err == nil {
c.databaseDriver = databaseDriver
c.DatabaseDriver = databaseDriver
}
if databaseDsn, err := yamlConfig.Get("database-dsn"); err == nil {
c.databaseDsn = databaseDsn
c.DatabaseDsn = databaseDsn
}
return nil
@ -180,376 +134,72 @@ func (c *Config) SetValuesFromFile(fileName string) error {
// for the entity.
func (c *Config) SetValuesFromCliContext(ctx *cli.Context) error {
if ctx.GlobalBool("debug") {
c.debug = ctx.GlobalBool("debug")
c.Debug = ctx.GlobalBool("debug")
}
if ctx.GlobalIsSet("log-level") || c.logLevel == "" {
c.logLevel = ctx.GlobalString("log-level")
if ctx.GlobalIsSet("log-level") || c.LogLevel == "" {
c.LogLevel = ctx.GlobalString("log-level")
}
if ctx.GlobalIsSet("assets-path") || c.assetsPath == "" {
c.assetsPath = fsutil.ExpandedFilename(ctx.GlobalString("assets-path"))
if ctx.GlobalIsSet("assets-path") || c.AssetsPath == "" {
c.AssetsPath = fsutil.ExpandedFilename(ctx.GlobalString("assets-path"))
}
if ctx.GlobalIsSet("cache-path") || c.cachePath == "" {
c.cachePath = fsutil.ExpandedFilename(ctx.GlobalString("cache-path"))
if ctx.GlobalIsSet("cache-path") || c.CachePath == "" {
c.CachePath = fsutil.ExpandedFilename(ctx.GlobalString("cache-path"))
}
if ctx.GlobalIsSet("originals-path") || c.originalsPath == "" {
c.originalsPath = fsutil.ExpandedFilename(ctx.GlobalString("originals-path"))
if ctx.GlobalIsSet("originals-path") || c.OriginalsPath == "" {
c.OriginalsPath = fsutil.ExpandedFilename(ctx.GlobalString("originals-path"))
}
if ctx.GlobalIsSet("import-path") || c.importPath == "" {
c.importPath = fsutil.ExpandedFilename(ctx.GlobalString("import-path"))
if ctx.GlobalIsSet("import-path") || c.ImportPath == "" {
c.ImportPath = fsutil.ExpandedFilename(ctx.GlobalString("import-path"))
}
if ctx.GlobalIsSet("export-path") || c.exportPath == "" {
c.exportPath = fsutil.ExpandedFilename(ctx.GlobalString("export-path"))
if ctx.GlobalIsSet("export-path") || c.ExportPath == "" {
c.ExportPath = fsutil.ExpandedFilename(ctx.GlobalString("export-path"))
}
if ctx.GlobalIsSet("darktable-cli") || c.darktableCli == "" {
c.darktableCli = fsutil.ExpandedFilename(ctx.GlobalString("darktable-cli"))
if ctx.GlobalIsSet("darktable-cli") || c.DarktableCli == "" {
c.DarktableCli = fsutil.ExpandedFilename(ctx.GlobalString("darktable-cli"))
}
if ctx.GlobalIsSet("database-driver") || c.databaseDriver == "" {
c.databaseDriver = ctx.GlobalString("database-driver")
if ctx.GlobalIsSet("database-driver") || c.DatabaseDriver == "" {
c.DatabaseDriver = ctx.GlobalString("database-driver")
}
if ctx.GlobalIsSet("database-dsn") || c.databaseDsn == "" {
c.databaseDsn = ctx.GlobalString("database-dsn")
if ctx.GlobalIsSet("database-dsn") || c.DatabaseDsn == "" {
c.DatabaseDsn = ctx.GlobalString("database-dsn")
}
if ctx.GlobalIsSet("sql-host") || c.sqlServerHost == "" {
c.sqlServerHost = ctx.GlobalString("sql-host")
if ctx.GlobalIsSet("sql-host") || c.SqlServerHost == "" {
c.SqlServerHost = ctx.GlobalString("sql-host")
}
if ctx.GlobalIsSet("sql-port") || c.sqlServerPort == 0 {
c.sqlServerPort = ctx.GlobalUint("sql-port")
if ctx.GlobalIsSet("sql-port") || c.SqlServerPort == 0 {
c.SqlServerPort = ctx.GlobalUint("sql-port")
}
if ctx.GlobalIsSet("sql-path") || c.sqlServerPath == "" {
c.sqlServerPath = ctx.GlobalString("sql-path")
if ctx.GlobalIsSet("sql-password") || c.SqlServerPassword == "" {
c.SqlServerPassword = ctx.GlobalString("sql-password")
}
if ctx.GlobalIsSet("http-host") || c.httpServerHost == "" {
c.httpServerHost = ctx.GlobalString("http-host")
if ctx.GlobalIsSet("sql-path") || c.SqlServerPath == "" {
c.SqlServerPath = ctx.GlobalString("sql-path")
}
if ctx.GlobalIsSet("http-port") || c.httpServerPort == 0 {
c.httpServerPort = ctx.GlobalInt("http-port")
if ctx.GlobalIsSet("http-host") || c.HttpServerHost == "" {
c.HttpServerHost = ctx.GlobalString("http-host")
}
if ctx.GlobalIsSet("http-mode") || c.httpServerMode == "" {
c.httpServerMode = ctx.GlobalString("http-mode")
if ctx.GlobalIsSet("http-port") || c.HttpServerPort == 0 {
c.HttpServerPort = ctx.GlobalInt("http-port")
}
if ctx.GlobalIsSet("http-mode") || c.HttpServerMode == "" {
c.HttpServerMode = ctx.GlobalString("http-mode")
}
return nil
}
// CreateDirectories creates all the folders that photoprism needs. These are:
// originalsPath
// ThumbnailsPath
// importPath
// exportPath
func (c *Config) CreateDirectories() error {
if err := os.MkdirAll(c.OriginalsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ImportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ExportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ThumbnailsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.SqlServerPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.TensorFlowModelPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.HttpPublicBuildPath(), os.ModePerm); err != nil {
return err
}
return nil
}
// connectToDatabase establishes a database connection.
// When used with the internal driver, it may create a new database server instance.
// It tries to do this 12 times with a 5 second sleep interval in between.
func (c *Config) connectToDatabase() error {
dbDriver := c.DatabaseDriver()
dbDsn := c.DatabaseDsn()
isTiDB := false
initSuccess := false
if dbDriver == DbTiDB {
isTiDB = true
dbDriver = DbMySQL
}
db, err := gorm.Open(dbDriver, dbDsn)
if err != nil || db == nil {
if isTiDB {
log.Infof("starting database server at %s:%d\n", c.SqlServerHost(), c.SqlServerPort())
go tidb.Start(c.SqlServerPath(), c.SqlServerPort(), c.SqlServerHost(), c.Debug())
}
for i := 1; i <= 12; i++ {
time.Sleep(5 * time.Second)
db, err = gorm.Open(dbDriver, dbDsn)
if db != nil && err == nil {
break
}
if isTiDB && !initSuccess {
err = tidb.InitDatabase(c.SqlServerPort(), c.SqlServerPassword())
if err != nil {
log.Debug(err)
} else {
initSuccess = true
}
}
}
if err != nil || db == nil {
log.Fatal(err)
}
}
c.db = db
return err
}
// AppName returns the application name.
func (c *Config) AppName() string {
return c.appName
}
// AppVersion returns the application version.
func (c *Config) AppVersion() string {
return c.appVersion
}
// AppCopyright returns the application copyright.
func (c *Config) AppCopyright() string {
return c.appCopyright
}
// Debug returns true if debug mode is on.
func (c *Config) Debug() bool {
return c.debug
}
// LogLevel returns the logrus log level.
func (c *Config) LogLevel() log.Level {
if c.Debug() {
c.logLevel = "debug"
}
if logLevel, err := log.ParseLevel(c.logLevel); err == nil {
return logLevel
} else {
return log.ErrorLevel
}
}
// ConfigFile returns the config file name.
func (c *Config) ConfigFile() string {
return c.configFile
}
// SqlServerHost returns the built-in SQL server host name or IP address (empty for all interfaces).
func (c *Config) SqlServerHost() string {
return c.sqlServerHost
}
// SqlServerPort returns the built-in SQL server port.
func (c *Config) SqlServerPort() uint {
return c.sqlServerPort
}
// SqlServerPath returns the database storage path for TiDB.
func (c *Config) SqlServerPath() string {
if c.sqlServerPath != "" {
return c.sqlServerPath
}
return c.ServerPath() + "/database"
}
// SqlServerPassword returns the password for the built-in database server.
func (c *Config) SqlServerPassword() string {
return c.sqlServerPassword
}
// HttpServerHost returns the built-in HTTP server host name or IP address (empty for all interfaces).
func (c *Config) HttpServerHost() string {
if c.httpServerHost == "" {
return "0.0.0.0"
}
return c.httpServerHost
}
// HttpServerPort returns the built-in HTTP server port.
func (c *Config) HttpServerPort() int {
return c.httpServerPort
}
// HttpServerMode returns the server mode.
func (c *Config) HttpServerMode() string {
return c.httpServerMode
}
// HttpServerPassword returns the password for the user interface (optional).
func (c *Config) HttpServerPassword() string {
return c.httpServerPassword
}
// OriginalsPath returns the originals.
func (c *Config) OriginalsPath() string {
return c.originalsPath
}
// ImportPath returns the import directory.
func (c *Config) ImportPath() string {
return c.importPath
}
// ExportPath returns the export directory.
func (c *Config) ExportPath() string {
return c.exportPath
}
// DarktableCli returns the darktable-cli binary file name.
func (c *Config) DarktableCli() string {
return c.darktableCli
}
// DatabaseDriver returns the database driver name.
func (c *Config) DatabaseDriver() string {
return c.databaseDriver
}
// DatabaseDsn returns the database data source name (DSN).
func (c *Config) DatabaseDsn() string {
return c.databaseDsn
}
// CachePath returns the path to the cache.
func (c *Config) CachePath() string {
return c.cachePath
}
// ThumbnailsPath returns the path to the cached thumbnails.
func (c *Config) ThumbnailsPath() string {
return c.CachePath() + "/thumbnails"
}
// AssetsPath returns the path to the assets.
func (c *Config) AssetsPath() string {
return c.assetsPath
}
// TensorFlowModelPath returns the tensorflow model path.
func (c *Config) TensorFlowModelPath() string {
return c.AssetsPath() + "/tensorflow"
}
// ServerPath returns the server assets path (public files, favicons, templates,...).
func (c *Config) ServerPath() string {
return c.AssetsPath() + "/server"
}
// HttpTemplatesPath returns the server templates path.
func (c *Config) HttpTemplatesPath() string {
return c.ServerPath() + "/templates"
}
// HttpFaviconsPath returns the favicons path.
func (c *Config) HttpFaviconsPath() string {
return c.HttpPublicPath() + "/favicons"
}
// HttpPublicPath returns the public server path (//server/assets/*).
func (c *Config) HttpPublicPath() string {
return c.ServerPath() + "/public"
}
// HttpPublicBuildPath returns the public build path (//server/assets/build/*).
func (c *Config) HttpPublicBuildPath() string {
return c.HttpPublicPath() + "/build"
}
// Db returns the db connection.
func (c *Config) Db() *gorm.DB {
if c.db == nil {
c.connectToDatabase()
}
return c.db
}
// MigrateDb will start a migration process.
func (c *Config) MigrateDb() {
db := c.Db()
db.AutoMigrate(&models.File{},
&models.Photo{},
&models.Tag{},
&models.Album{},
&models.Location{},
&models.Camera{},
&models.Lens{},
&models.Country{})
}
// ClientConfig returns a loaded and set configuration entity.
func (c *Config) ClientConfig() frontend.Config {
db := c.Db()
var cameras []*models.Camera
type country struct {
LocCountry string
LocCountryCode string
}
var countries []country
db.Model(&models.Location{}).Select("DISTINCT loc_country_code, loc_country").Scan(&countries)
db.Where("deleted_at IS NULL").Limit(1000).Order("camera_model").Find(&cameras)
jsHash := fsutil.Hash(c.HttpPublicBuildPath() + "/app.js")
cssHash := fsutil.Hash(c.HttpPublicBuildPath() + "/app.css")
result := frontend.Config{
"appName": c.AppName(),
"appVersion": c.AppVersion(),
"debug": c.Debug(),
"cameras": cameras,
"countries": countries,
"jsHash": jsHash,
"cssHash": cssHash,
}
return result
}

View file

@ -4,38 +4,21 @@ import (
"testing"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/test"
"github.com/stretchr/testify/assert"
)
func TestNewConfig(t *testing.T) {
ctx := test.CliContext()
assert.True(t, ctx.IsSet("assets-path"))
assert.False(t, ctx.Bool("debug"))
c := NewConfig(ctx)
assert.IsType(t, new(Config), c)
assert.Equal(t, fsutil.ExpandedFilename("../../assets"), c.AssetsPath())
assert.False(t, c.Debug())
}
func TestConfig_SetValuesFromFile(t *testing.T) {
c := NewConfig(test.CliContext())
c := NewConfig(CliTestContext())
err := c.SetValuesFromFile(fsutil.ExpandedFilename("../../configs/photoprism.yml"))
assert.Nil(t, err)
assert.Equal(t, "/srv/photoprism", c.AssetsPath())
assert.Equal(t, "/srv/photoprism/cache", c.CachePath())
assert.Equal(t, "/srv/photoprism/cache/thumbnails", c.ThumbnailsPath())
assert.Equal(t, "/srv/photoprism/photos/originals", c.OriginalsPath())
assert.Equal(t, "/srv/photoprism/photos/import", c.ImportPath())
assert.Equal(t, "/srv/photoprism/photos/export", c.ExportPath())
assert.Equal(t, "internal", c.DatabaseDriver())
assert.Equal(t, "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true", c.DatabaseDsn())
assert.Equal(t, "/srv/photoprism", c.AssetsPath)
assert.Equal(t, "/srv/photoprism/cache", c.CachePath)
assert.Equal(t, "/srv/photoprism/photos/originals", c.OriginalsPath)
assert.Equal(t, "/srv/photoprism/photos/import", c.ImportPath)
assert.Equal(t, "/srv/photoprism/photos/export", c.ExportPath)
assert.Equal(t, "internal", c.DatabaseDriver)
assert.Equal(t, "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true", c.DatabaseDsn)
}

373
internal/context/context.go Normal file
View file

@ -0,0 +1,373 @@
package context
import (
"os"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/models"
"github.com/photoprism/photoprism/internal/tidb"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
type Context struct {
db *gorm.DB
config *Config
}
// NewConfig() creates a new configuration entity by using two methods:
//
// 1. SetValuesFromFile: This will initialize values from a yaml config file.
//
// 2. SetValuesFromCliContext: Which comes after SetValuesFromFile and overrides
// any previous values giving an option two override file configs through the CLI.
func NewConfig(ctx *cli.Context) *Config {
c := &Config{}
c.Name = ctx.App.Name
c.Copyright = ctx.App.Copyright
c.Version = ctx.App.Version
log.SetFormatter(&log.TextFormatter{
DisableColors: false,
FullTimestamp: true,
})
if err := c.SetValuesFromFile(fsutil.ExpandedFilename(ctx.GlobalString("config-file"))); err != nil {
log.Debug(err)
}
if err := c.SetValuesFromCliContext(ctx); err != nil {
log.Error(err)
}
return c
}
func NewContext(ctx *cli.Context) *Context {
if ctx.GlobalBool("debug") {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.ErrorLevel)
}
c := &Context{config: NewConfig(ctx)}
log.SetLevel(c.LogLevel())
return c
}
// CreateDirectories creates all the folders that photoprism needs. These are:
// OriginalsPath
// ThumbnailsPath
// ImportPath
// ExportPath
func (c *Context) CreateDirectories() error {
if err := os.MkdirAll(c.OriginalsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ImportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ExportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ThumbnailsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.SqlServerPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.TensorFlowModelPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.HttpPublicBuildPath(), os.ModePerm); err != nil {
return err
}
return nil
}
// connectToDatabase establishes a database connection.
// When used with the internal driver, it may create a new database server instance.
// It tries to do this 12 times with a 5 second sleep interval in between.
func (c *Context) connectToDatabase() error {
dbDriver := c.DatabaseDriver()
dbDsn := c.DatabaseDsn()
isTiDB := false
initSuccess := false
if dbDriver == DbTiDB {
isTiDB = true
dbDriver = DbMySQL
}
db, err := gorm.Open(dbDriver, dbDsn)
if err != nil || db == nil {
if isTiDB {
log.Infof("starting database server at %s:%d\n", c.SqlServerHost(), c.SqlServerPort())
go tidb.Start(c.SqlServerPath(), c.SqlServerPort(), c.SqlServerHost(), c.Debug())
}
for i := 1; i <= 12; i++ {
time.Sleep(5 * time.Second)
db, err = gorm.Open(dbDriver, dbDsn)
if db != nil && err == nil {
break
}
if isTiDB && !initSuccess {
err = tidb.InitDatabase(c.SqlServerPort(), c.SqlServerPassword())
if err != nil {
log.Debug(err)
} else {
initSuccess = true
}
}
}
if err != nil || db == nil {
log.Fatal(err)
}
}
c.db = db
return err
}
// Name returns the application name.
func (c *Context) Name() string {
return c.config.Name
}
// Version returns the application version.
func (c *Context) Version() string {
return c.config.Version
}
// Copyright returns the application copyright.
func (c *Context) Copyright() string {
return c.config.Copyright
}
// Debug returns true if Debug mode is on.
func (c *Context) Debug() bool {
return c.config.Debug
}
// LogLevel returns the logrus log level.
func (c *Context) LogLevel() log.Level {
if c.Debug() {
c.config.LogLevel = "debug"
}
if logLevel, err := log.ParseLevel(c.config.LogLevel); err == nil {
return logLevel
} else {
return log.ErrorLevel
}
}
// TestConfigFile returns the config file name.
func (c *Context) ConfigFile() string {
return c.config.ConfigFile
}
// SqlServerHost returns the built-in SQL server host name or IP address (empty for all interfaces).
func (c *Context) SqlServerHost() string {
return c.config.SqlServerHost
}
// SqlServerPort returns the built-in SQL server port.
func (c *Context) SqlServerPort() uint {
return c.config.SqlServerPort
}
// SqlServerPath returns the database storage path for TiDB.
func (c *Context) SqlServerPath() string {
if c.config.SqlServerPath != "" {
return c.config.SqlServerPath
}
return c.ServerPath() + "/database"
}
// SqlServerPassword returns the password for the built-in database server.
func (c *Context) SqlServerPassword() string {
return c.config.SqlServerPassword
}
// HttpServerHost returns the built-in HTTP server host name or IP address (empty for all interfaces).
func (c *Context) HttpServerHost() string {
if c.config.HttpServerHost == "" {
return "0.0.0.0"
}
return c.config.HttpServerHost
}
// HttpServerPort returns the built-in HTTP server port.
func (c *Context) HttpServerPort() int {
return c.config.HttpServerPort
}
// HttpServerMode returns the server mode.
func (c *Context) HttpServerMode() string {
return c.config.HttpServerMode
}
// HttpServerPassword returns the password for the user interface (optional).
func (c *Context) HttpServerPassword() string {
return c.config.HttpServerPassword
}
// OriginalsPath returns the originals.
func (c *Context) OriginalsPath() string {
return c.config.OriginalsPath
}
// ImportPath returns the import directory.
func (c *Context) ImportPath() string {
return c.config.ImportPath
}
// ExportPath returns the export directory.
func (c *Context) ExportPath() string {
return c.config.ExportPath
}
// DarktableCli returns the darktable-cli binary file name.
func (c *Context) DarktableCli() string {
return c.config.DarktableCli
}
// DatabaseDriver returns the database driver name.
func (c *Context) DatabaseDriver() string {
return c.config.DatabaseDriver
}
// DatabaseDsn returns the database data source name (DSN).
func (c *Context) DatabaseDsn() string {
return c.config.DatabaseDsn
}
// CachePath returns the path to the cache.
func (c *Context) CachePath() string {
return c.config.CachePath
}
// ThumbnailsPath returns the path to the cached thumbnails.
func (c *Context) ThumbnailsPath() string {
return c.CachePath() + "/thumbnails"
}
// AssetsPath returns the path to the assets.
func (c *Context) AssetsPath() string {
return c.config.AssetsPath
}
// TensorFlowModelPath returns the tensorflow model path.
func (c *Context) TensorFlowModelPath() string {
return c.AssetsPath() + "/tensorflow"
}
// ServerPath returns the server assets path (public files, favicons, templates,...).
func (c *Context) ServerPath() string {
return c.AssetsPath() + "/server"
}
// HttpTemplatesPath returns the server templates path.
func (c *Context) HttpTemplatesPath() string {
return c.ServerPath() + "/templates"
}
// HttpFaviconsPath returns the favicons path.
func (c *Context) HttpFaviconsPath() string {
return c.HttpPublicPath() + "/favicons"
}
// HttpPublicPath returns the public server path (//server/assets/*).
func (c *Context) HttpPublicPath() string {
return c.ServerPath() + "/public"
}
// HttpPublicBuildPath returns the public build path (//server/assets/build/*).
func (c *Context) HttpPublicBuildPath() string {
return c.HttpPublicPath() + "/build"
}
// Db returns the db connection.
func (c *Context) Db() *gorm.DB {
if c.db == nil {
c.connectToDatabase()
}
return c.db
}
// MigrateDb will start a migration process.
func (c *Context) MigrateDb() {
db := c.Db()
db.AutoMigrate(&models.File{},
&models.Photo{},
&models.Tag{},
&models.Album{},
&models.Location{},
&models.Camera{},
&models.Lens{},
&models.Country{})
}
// ClientConfig returns a loaded and set configuration entity.
func (c *Context) ClientConfig() ClientConfig {
db := c.Db()
var cameras []*models.Camera
type country struct {
LocCountry string
LocCountryCode string
}
var countries []country
db.Model(&models.Location{}).Select("DISTINCT loc_country_code, loc_country").Scan(&countries)
db.Where("deleted_at IS NULL").Limit(1000).Order("camera_model").Find(&cameras)
jsHash := fsutil.Hash(c.HttpPublicBuildPath() + "/app.js")
cssHash := fsutil.Hash(c.HttpPublicBuildPath() + "/app.css")
result := ClientConfig{
"name": c.Name(),
"version": c.Version(),
"copyright": c.Copyright(),
"debug": c.Debug(),
"cameras": cameras,
"countries": countries,
"jsHash": jsHash,
"cssHash": cssHash,
}
return result
}

View file

@ -0,0 +1,22 @@
package context
import (
"testing"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/stretchr/testify/assert"
)
func TestNewAppConfig(t *testing.T) {
ctx := CliTestContext()
assert.True(t, ctx.IsSet("assets-path"))
assert.False(t, ctx.Bool("debug"))
c := NewConfig(ctx)
assert.IsType(t, new(Config), c)
assert.Equal(t, fsutil.ExpandedFilename("../../assets"), c.AssetsPath)
assert.False(t, c.Debug)
}

View file

@ -0,0 +1,129 @@
package context
import (
"flag"
"fmt"
"os"
"testing"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/urfave/cli"
log "github.com/sirupsen/logrus"
)
const (
TestDataZip = "/tmp/photoprism/testdata.zip"
TestDataURL = "https://dl.photoprism.org/fixtures/testdata-20190501.zip"
TestDataHash = "1a59b358b80221ab3e76efb683ad72402f0b0844"
)
var testContext *Context
func testDataPath(assetsPath string) string {
return assetsPath + "/testdata"
}
func NewTestConfig() *Config {
assetsPath := fsutil.ExpandedFilename("../../assets")
testDataPath := testDataPath(assetsPath)
c := &Config{
ConfigFile: "../../configs/photoprism.yml",
DarktableCli: "/usr/bin/darktable-cli",
AssetsPath: assetsPath,
CachePath: testDataPath + "/cache",
OriginalsPath: testDataPath + "/originals",
ImportPath: testDataPath + "/import",
ExportPath: testDataPath + "/export",
DatabaseDriver: "mysql",
DatabaseDsn: "photoprism:photoprism@tcp(database:3306)/photoprism?parseTime=true",
}
return c
}
func TestContext() *Context {
if testContext == nil {
testContext = NewTestContext()
}
return testContext
}
func NewTestContext() *Context {
log.SetLevel(log.FatalLevel)
c := &Context{config: NewTestConfig()}
c.MigrateDb()
return c
}
// Returns example cli context for testing
func CliTestContext() *cli.Context {
config := NewTestConfig()
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("debug", false, "doc")
globalSet.String("config-file", config.ConfigFile, "doc")
globalSet.String("assets-path", config.AssetsPath, "doc")
globalSet.String("originals-path", config.OriginalsPath, "doc")
globalSet.String("darktable-cli", config.DarktableCli, "doc")
app := cli.NewApp()
c := cli.NewContext(app, globalSet, nil)
c.Set("config-file", config.ConfigFile)
c.Set("assets-path", config.AssetsPath)
c.Set("originals-path", config.OriginalsPath)
c.Set("darktable-cli", config.DarktableCli)
return c
}
func (c *Context) RemoveTestData(t *testing.T) {
os.RemoveAll(c.ImportPath())
os.RemoveAll(c.ExportPath())
os.RemoveAll(c.OriginalsPath())
os.RemoveAll(c.CachePath())
}
func (c *Context) DownloadTestData(t *testing.T) {
if fsutil.Exists(TestDataZip) {
hash := fsutil.Hash(TestDataZip)
if hash != TestDataHash {
os.Remove(TestDataZip)
t.Logf("Removed outdated test data zip file (fingerprint %s)\n", hash)
}
}
if !fsutil.Exists(TestDataZip) {
fmt.Printf("Downloading latest test data zip file from %s\n", TestDataURL)
if err := fsutil.Download(TestDataZip, TestDataURL); err != nil {
fmt.Printf("Download failed: %s\n", err.Error())
}
}
}
func (c *Context) UnzipTestData(t *testing.T) {
if _, err := fsutil.Unzip(TestDataZip, testDataPath(c.AssetsPath())); err != nil {
t.Logf("Could not unzip test data: %s\n", err.Error())
}
}
func (c *Context) InitializeTestData(t *testing.T) {
t.Log("Initializing test data")
c.RemoveTestData(t)
c.DownloadTestData(t)
c.UnzipTestData(t)
}

View file

@ -0,0 +1,39 @@
package context
import (
"testing"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
func TestTestCliContext(t *testing.T) {
result := CliTestContext()
assert.IsType(t, new(cli.Context), result)
}
func TestTestContext(t *testing.T) {
result := TestContext()
assert.IsType(t, new(Context), result)
}
func TestNewTestConfig(t *testing.T) {
c := NewTestConfig()
assert.IsType(t, new(Config), c)
assert.Equal(t, fsutil.ExpandedFilename("../../assets"), c.AssetsPath)
assert.False(t, c.Debug)
}
func TestNewTestContext_Db(t *testing.T) {
c := NewTestContext()
db := c.Db()
assert.IsType(t, &gorm.DB{}, db)
}

View file

@ -0,0 +1,13 @@
package forms
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPhotoSearchForm(t *testing.T) {
form := &PhotoSearchForm{}
assert.IsType(t, new(PhotoSearchForm), form)
}

View file

@ -1,4 +0,0 @@
package frontend
// HTTP client / Web UI config values
type Config map[string]interface{}

View file

@ -1,8 +0,0 @@
/*
Package frontend contains user interface related code.
Additional information can be found in our Developer Guide:
https://github.com/photoprism/photoprism/wiki
*/
package frontend

View file

@ -0,0 +1,20 @@
package fsutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExists(t *testing.T) {
assert.True(t, Exists("./_fixtures/test.jpg"))
assert.False(t, Exists("./foo.jpg"))
}
func TestExpandedFilename(t *testing.T) {
filename := ExpandedFilename("./_fixtures/test.jpg")
assert.IsType(t, "", filename)
t.Logf("ExpandedFilename: %s", filename)
}

View file

@ -0,0 +1,12 @@
package fsutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHash(t *testing.T) {
hash := Hash("_fixtures/test.jpg")
assert.Equal(t, "516cb1fefbfd9fa66f1db50b94503a480cee30db", hash)
}

View file

@ -22,7 +22,13 @@ func NewCamera(modelName string, makeName string) *Camera {
modelName = "Unknown"
}
cameraSlug := slug.MakeLang(modelName, "en")
var cameraSlug string
if makeName != "" {
cameraSlug = slug.MakeLang(makeName+" "+modelName, "en")
} else {
cameraSlug = slug.MakeLang(modelName, "en")
}
result := &Camera{
CameraModel: modelName,

View file

@ -0,0 +1,18 @@
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewCamera(t *testing.T) {
camera := NewCamera("EOS 6D", "Canon")
expected := &Camera{
CameraModel: "EOS 6D",
CameraMake: "Canon",
CameraSlug: "canon-eos-6d",
}
assert.Equal(t, expected, camera)
}

View file

@ -3,17 +3,17 @@ package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestMediaFile_GetColors(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
t.Run("dog.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ImportPath() + "/dog.jpg"); err == nil {
if mediaFile, err := NewMediaFile(ctx.ImportPath() + "/dog.jpg"); err == nil {
colors, main, l, s, err := mediaFile.Colors()
t.Log(colors, main, l, s, err)
@ -30,7 +30,7 @@ func TestMediaFile_GetColors(t *testing.T) {
})
t.Run("ape.jpeg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ImportPath() + "/ape.jpeg"); err == nil {
if mediaFile, err := NewMediaFile(ctx.ImportPath() + "/ape.jpeg"); err == nil {
colors, main, l, s, err := mediaFile.Colors()
t.Log(colors, main, l, s, err)
@ -47,7 +47,7 @@ func TestMediaFile_GetColors(t *testing.T) {
})
t.Run("iphone/IMG_6788.JPG", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG"); err == nil {
if mediaFile, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG"); err == nil {
colors, main, l, s, err := mediaFile.Colors()
t.Log(colors, main, l, s, err)
@ -63,7 +63,7 @@ func TestMediaFile_GetColors(t *testing.T) {
})
t.Run("raw/20140717_154212_1EC48F8489.jpg", func(t *testing.T) {
if mediaFile, err := NewMediaFile(conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg"); err == nil {
if mediaFile, err := NewMediaFile(ctx.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg"); err == nil {
colors, main, l, s, err := mediaFile.Colors()
t.Log(colors, main, l, s, err)

View file

@ -1,54 +0,0 @@
package photoprism
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/internal/frontend"
"github.com/sirupsen/logrus"
)
// Config interface implemented in context (cli) and test packages
type Config interface {
Debug() bool
LogLevel() logrus.Level
Db() *gorm.DB
CreateDirectories() error
MigrateDb()
ClientConfig() frontend.Config
ConfigFile() string
AppName() string
AppVersion() string
AppCopyright() string
SqlServerHost() string
SqlServerPort() uint
SqlServerPath() string
SqlServerPassword() string
HttpServerHost() string
HttpServerPort() int
HttpServerMode() string
HttpServerPassword() string
HttpTemplatesPath() string
HttpFaviconsPath() string
HttpPublicPath() string
HttpPublicBuildPath() string
DatabaseDriver() string
DatabaseDsn() string
AssetsPath() string
ServerPath() string
OriginalsPath() string
ImportPath() string
ExportPath() string
CachePath() string
ThumbnailsPath() string
TensorFlowModelPath() string
DarktableCli() string
}

View file

@ -1,71 +0,0 @@
package photoprism
import (
"flag"
"testing"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/context"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/test"
"github.com/urfave/cli"
"github.com/stretchr/testify/assert"
)
func getTestCliContext() *cli.Context {
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("debug", false, "doc")
globalSet.String("config-file", test.ConfigFile, "doc")
globalSet.String("assets-path", test.AssetsPath, "doc")
globalSet.String("originals-path", test.OriginalsPath, "doc")
globalSet.String("darktable-cli", test.DarktableCli, "doc")
app := cli.NewApp()
c := cli.NewContext(app, globalSet, nil)
c.Set("config-file", test.ConfigFile)
c.Set("assets-path", test.AssetsPath)
c.Set("originals-path", test.OriginalsPath)
c.Set("darktable-cli", test.DarktableCli)
return c
}
func TestNewConfig(t *testing.T) {
ctx := getTestCliContext()
assert.True(t, ctx.IsSet("assets-path"))
assert.False(t, ctx.Bool("debug"))
c := context.NewConfig(ctx)
assert.IsType(t, new(context.Config), c)
assert.Equal(t, test.AssetsPath, c.AssetsPath())
assert.False(t, c.Debug())
}
func TestContextConfig_SetValuesFromFile(t *testing.T) {
c := context.NewConfig(getTestCliContext())
c.SetValuesFromFile(fsutil.ExpandedFilename(test.ConfigFile))
assert.Equal(t, "/srv/photoprism", c.AssetsPath())
assert.Equal(t, "/srv/photoprism/cache", c.CachePath())
assert.Equal(t, "/srv/photoprism/cache/thumbnails", c.ThumbnailsPath())
assert.Equal(t, "/srv/photoprism/photos/originals", c.OriginalsPath())
assert.Equal(t, "/srv/photoprism/photos/import", c.ImportPath())
assert.Equal(t, "/srv/photoprism/photos/export", c.ExportPath())
assert.Equal(t, "internal", c.DatabaseDriver())
assert.Equal(t, "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true", c.DatabaseDsn())
}
func TestTestConfig_ConnectToDatabase(t *testing.T) {
c := test.NewConfig()
db := c.Db()
assert.IsType(t, &gorm.DB{}, db)
}

View file

@ -32,7 +32,7 @@ func (c *Converter) ConvertAll(path string) {
err := filepath.Walk(path, func(filename string, fileInfo os.FileInfo, err error) error {
if err != nil {
log.Error(err.Error())
log.Error("Walk", err.Error())
return nil
}
@ -47,7 +47,7 @@ func (c *Converter) ConvertAll(path string) {
}
if _, err := c.ConvertToJpeg(mediaFile); err != nil {
log.Error(err.Error())
log.Warnf("file could not be converted to JPEG: \"%s\"", filename)
}
return nil
@ -78,7 +78,7 @@ func (c *Converter) ConvertToJpeg(image *MediaFile) (*MediaFile, error) {
return mediaFile, nil
}
log.Errorf("converting \"%s\" to \"%s\"", image.filename, jpegFilename)
log.Infof("converting \"%s\" to \"%s\"", image.filename, jpegFilename)
xmpFilename := baseFilename + ".xmp"

View file

@ -4,15 +4,15 @@ import (
"os"
"testing"
"github.com/photoprism/photoprism/internal/context"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/test"
"github.com/stretchr/testify/assert"
)
func TestNewConverter(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
converter := NewConverter(conf.DarktableCli())
converter := NewConverter(ctx.DarktableCli())
assert.IsType(t, &Converter{}, converter)
}
@ -22,13 +22,13 @@ func TestConverter_ConvertToJpeg(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
converter := NewConverter(conf.DarktableCli())
converter := NewConverter(ctx.DarktableCli())
jpegFilename := conf.ImportPath() + "/iphone/IMG_6788.JPG"
jpegFilename := ctx.ImportPath() + "/iphone/IMG_6788.JPG"
assert.Truef(t, fsutil.Exists(jpegFilename), "file does not exist: %s", jpegFilename)
@ -52,7 +52,7 @@ func TestConverter_ConvertToJpeg(t *testing.T) {
assert.Equal(t, "iPhone SE", infoJpeg.CameraModel)
rawFilemame := conf.ImportPath() + "/raw/IMG_1435.CR2"
rawFilemame := ctx.ImportPath() + "/raw/IMG_1435.CR2"
t.Logf("Testing RAW to JPEG converter with %s", rawFilemame)
@ -62,7 +62,7 @@ func TestConverter_ConvertToJpeg(t *testing.T) {
imageRaw, _ := converter.ConvertToJpeg(rawMediaFile)
assert.True(t, fsutil.Exists(conf.ImportPath()+"/raw/IMG_1435.jpg"), "Jpeg file was not found - is Darktable installed?")
assert.True(t, fsutil.Exists(ctx.ImportPath()+"/raw/IMG_1435.jpg"), "Jpeg file was not found - is Darktable installed?")
assert.NotEqual(t, rawFilemame, imageRaw.filename)
@ -78,15 +78,15 @@ func TestConverter_ConvertAll(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
converter := NewConverter(conf.DarktableCli())
converter := NewConverter(ctx.DarktableCli())
converter.ConvertAll(conf.ImportPath())
converter.ConvertAll(ctx.ImportPath())
jpegFilename := conf.ImportPath() + "/raw/IMG_1435.jpg"
jpegFilename := ctx.ImportPath() + "/raw/IMG_1435.jpg"
assert.True(t, fsutil.Exists(jpegFilename), "Jpeg file was not found - is Darktable installed?")
@ -102,13 +102,13 @@ func TestConverter_ConvertAll(t *testing.T) {
assert.Equal(t, "Canon EOS M10", infoRaw.CameraModel, "Camera model should be Canon EOS M10")
existingJpegFilename := conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg"
existingJpegFilename := ctx.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg"
oldHash := fsutil.Hash(existingJpegFilename)
os.Remove(existingJpegFilename)
converter.ConvertAll(conf.ImportPath())
converter.ConvertAll(ctx.ImportPath())
newHash := fsutil.Hash(existingJpegFilename)

View file

@ -2,6 +2,7 @@ package photoprism
import (
"errors"
"fmt"
"math"
"strings"
"time"
@ -32,7 +33,7 @@ type ExifData struct {
// ExifData return ExifData given a single mediaFile.
func (m *MediaFile) ExifData() (*ExifData, error) {
if m == nil {
return nil, errors.New("media file is null")
return nil, errors.New("can't parse Exif data: file instance is null")
}
if m.exifData != nil {
@ -40,7 +41,7 @@ func (m *MediaFile) ExifData() (*ExifData, error) {
}
if !m.IsPhoto() {
return nil, errors.New("not a JPEG or Raw file")
return nil, errors.New(fmt.Sprintf("file not compatible with Exif: \"%s\"", m.Filename()))
}
m.exifData = &ExifData{}

View file

@ -3,16 +3,16 @@ package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestMediaFile_GetExifData(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
image1, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
image1, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
@ -30,11 +30,11 @@ func TestMediaFile_GetExifData_Slow(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
image2, err := NewMediaFile(conf.ImportPath() + "/raw/IMG_1435.CR2")
image2, err := NewMediaFile(ctx.ImportPath() + "/raw/IMG_1435.CR2")
assert.Nil(t, err)

View file

@ -10,7 +10,7 @@ import (
log "github.com/sirupsen/logrus"
)
// FindOriginalsByDate searches the originalsPath given a time frame in the format of
// FindOriginalsByDate searches the OriginalsPath given a time frame in the format of
// after <=> before and returns a list of results.
func FindOriginalsByDate(originalsPath string, after time.Time, before time.Time) (result []*MediaFile) {
filepath.Walk(originalsPath, func(filename string, fileInfo os.FileInfo, err error) error {

View file

@ -3,37 +3,38 @@ package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestNewImporter(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
indexer := NewIndexer(conf.OriginalsPath(), tensorFlow, conf.Db())
indexer := NewIndexer(ctx.OriginalsPath(), tensorFlow, ctx.Db())
converter := NewConverter(conf.DarktableCli())
converter := NewConverter(ctx.DarktableCli())
importer := NewImporter(conf.OriginalsPath(), indexer, converter)
importer := NewImporter(ctx.OriginalsPath(), indexer, converter)
assert.IsType(t, &Importer{}, importer)
}
func TestImporter_GetDestinationFilename(t *testing.T) {
conf := test.NewConfig()
conf.InitializeTestData(t)
ctx := context.TestContext()
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
ctx.InitializeTestData(t)
indexer := NewIndexer(conf.OriginalsPath(), tensorFlow, conf.Db())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
converter := NewConverter(conf.DarktableCli())
indexer := NewIndexer(ctx.OriginalsPath(), tensorFlow, ctx.Db())
importer := NewImporter(conf.OriginalsPath(), indexer, converter)
converter := NewConverter(ctx.DarktableCli())
rawFile, err := NewMediaFile(conf.ImportPath() + "/raw/IMG_1435.CR2")
importer := NewImporter(ctx.OriginalsPath(), indexer, converter)
rawFile, err := NewMediaFile(ctx.ImportPath() + "/raw/IMG_1435.CR2")
assert.Nil(t, err)
@ -41,7 +42,7 @@ func TestImporter_GetDestinationFilename(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, conf.OriginalsPath()+"/2018/02/20180204_170813_863A6248DCCA.cr2", filename)
assert.Equal(t, ctx.OriginalsPath()+"/2018/02/20180204_170813_863A6248DCCA.cr2", filename)
}
func TestImporter_ImportPhotosFromDirectory(t *testing.T) {
@ -49,17 +50,17 @@ func TestImporter_ImportPhotosFromDirectory(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
indexer := NewIndexer(conf.OriginalsPath(), tensorFlow, conf.Db())
indexer := NewIndexer(ctx.OriginalsPath(), tensorFlow, ctx.Db())
converter := NewConverter(conf.DarktableCli())
converter := NewConverter(ctx.DarktableCli())
importer := NewImporter(conf.OriginalsPath(), indexer, converter)
importer := NewImporter(ctx.OriginalsPath(), indexer, converter)
importer.ImportPhotosFromDirectory(conf.ImportPath())
importer.ImportPhotosFromDirectory(ctx.ImportPath())
}

View file

@ -542,12 +542,14 @@ func (m *MediaFile) decodeDimensions() error {
m.width = size.Width
m.height = size.Height
} else {
if exif, err := m.ExifData(); err == nil {
m.width = exif.Width
m.height = exif.Height
} else {
exif, err := m.ExifData()
if err != nil {
return err
}
m.width = exif.Width
m.height = exif.Height
}
return nil
@ -555,6 +557,10 @@ func (m *MediaFile) decodeDimensions() error {
// Width return the width dimension of a mediafile.
func (m *MediaFile) Width() int {
if !m.IsPhoto() {
return 0
}
if m.width <= 0 {
if err := m.decodeDimensions(); err != nil {
log.Error(err)
@ -566,6 +572,10 @@ func (m *MediaFile) Width() int {
// Height returns the height dimension of a mediafile.
func (m *MediaFile) Height() int {
if !m.IsPhoto() {
return 0
}
if m.height <= 0 {
if err := m.decodeDimensions(); err != nil {
log.Error(err)

View file

@ -3,20 +3,20 @@ package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestMediaFile_GetRelatedFiles(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
mediaFile, err := NewMediaFile(conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.cr2")
mediaFile, err := NewMediaFile(ctx.ImportPath() + "/raw/20140717_154212_1EC48F8489.cr2")
assert.Nil(t, err)
expectedBaseFilename := conf.ImportPath() + "/raw/20140717_154212_1EC48F8489"
expectedBaseFilename := ctx.ImportPath() + "/raw/20140717_154212_1EC48F8489"
related, _, err := mediaFile.RelatedFiles()
@ -38,11 +38,11 @@ func TestMediaFile_GetRelatedFiles(t *testing.T) {
}
func TestMediaFile_GetRelatedFiles_Ordering(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
mediaFile, err := NewMediaFile(conf.ImportPath() + "/20130203_193332_0AE340D280.jpg")
mediaFile, err := NewMediaFile(ctx.ImportPath() + "/20130203_193332_0AE340D280.jpg")
assert.Nil(t, err)
@ -59,13 +59,13 @@ func TestMediaFile_GetRelatedFiles_Ordering(t *testing.T) {
}
func TestMediaFile_GetEditedFilename(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
mediaFile1, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
mediaFile1, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
assert.Equal(t, conf.ImportPath()+"/iphone/IMG_E6788.JPG", mediaFile1.EditedFilename())
assert.Equal(t, ctx.ImportPath()+"/iphone/IMG_E6788.JPG", mediaFile1.EditedFilename())
/* TODO: Add example files to import.zip
mediaFile2, err := NewMediaFile("/foo/bar/IMG_E1234.jpg")
@ -73,34 +73,34 @@ func TestMediaFile_GetEditedFilename(t *testing.T) {
assert.Equal(t, "", mediaFile2.EditedFilename())
*/
mediaFile3, err := NewMediaFile(conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg")
mediaFile3, err := NewMediaFile(ctx.ImportPath() + "/raw/20140717_154212_1EC48F8489.jpg")
assert.Nil(t, err)
assert.Equal(t, "", mediaFile3.EditedFilename())
}
func TestMediaFile_GetMimeType(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
image1, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
image1, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
assert.Equal(t, "image/jpeg", image1.MimeType())
image2, err := NewMediaFile(conf.ImportPath() + "/raw/20140717_154212_1EC48F8489.cr2")
image2, err := NewMediaFile(ctx.ImportPath() + "/raw/20140717_154212_1EC48F8489.cr2")
assert.Nil(t, err)
assert.Equal(t, "application/octet-stream", image2.MimeType())
}
func TestMediaFile_Exists(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
mediaFile, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
mediaFile, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
assert.NotNil(t, mediaFile)
assert.True(t, mediaFile.Exists())
mediaFile, err = NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788_XYZ.JPG")
mediaFile, err = NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788_XYZ.JPG")
assert.NotNil(t, err)
assert.Nil(t, mediaFile)
}

View file

@ -3,18 +3,18 @@ package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/context"
"github.com/photoprism/photoprism/internal/forms"
"github.com/photoprism/photoprism/internal/test"
)
func TestSearch_Photos_Query(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.CreateDirectories()
ctx.CreateDirectories()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
search := NewSearch(conf.OriginalsPath(), conf.Db())
search := NewSearch(ctx.OriginalsPath(), ctx.Db())
var form forms.PhotoSearchForm
@ -41,13 +41,13 @@ func TestSearch_Photos_Query(t *testing.T) {
}
func TestSearch_Photos_Camera(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.CreateDirectories()
ctx.CreateDirectories()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
search := NewSearch(conf.OriginalsPath(), conf.Db())
search := NewSearch(ctx.OriginalsPath(), ctx.Db())
var form forms.PhotoSearchForm

View file

@ -4,18 +4,18 @@ import (
"io/ioutil"
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestTensorFlow_GetImageTagsFromFile(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
result, err := tensorFlow.GetImageTagsFromFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
result, err := tensorFlow.GetImageTagsFromFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
@ -42,13 +42,13 @@ func TestTensorFlow_GetImageTags(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
if imageBuffer, err := ioutil.ReadFile(conf.ImportPath() + "/iphone/IMG_6788.JPG"); err != nil {
if imageBuffer, err := ioutil.ReadFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG"); err != nil {
t.Error(err)
} else {
result, err := tensorFlow.GetImageTags(imageBuffer)
@ -74,13 +74,13 @@ func TestTensorFlow_GetImageTags_Dog(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
if imageBuffer, err := ioutil.ReadFile(conf.ImportPath() + "/dog.jpg"); err != nil {
if imageBuffer, err := ioutil.ReadFile(ctx.ImportPath() + "/dog.jpg"); err != nil {
t.Error(err)
} else {
result, err := tensorFlow.GetImageTags(imageBuffer)

View file

@ -29,13 +29,13 @@ func CreateThumbnailsFromOriginals(originalsPath string, thumbnailsPath string,
if thumbnail, err := mediaFile.SquareThumbnail(thumbnailsPath, size); err != nil {
log.Errorf("could not create thumbnail: %s", err.Error())
} else {
log.Infof("created %dx%d px thumbnail for \"%s\"", thumbnail.Width(), thumbnail.Height(), mediaFile.RelativeFilename(originalsPath))
log.Infof("created %dx%d thumbnail for \"%s\"", thumbnail.Width(), thumbnail.Height(), mediaFile.RelativeFilename(originalsPath))
}
} else {
if thumbnail, err := mediaFile.Thumbnail(thumbnailsPath, size); err != nil {
log.Errorf("could not create thumbnail: %s", err.Error())
} else {
log.Infof("created %dx%d px thumbnail for \"%s\"", thumbnail.Width(), thumbnail.Height(), mediaFile.RelativeFilename(originalsPath))
log.Infof("created %dx%d thumbnail for \"%s\"", thumbnail.Width(), thumbnail.Height(), mediaFile.RelativeFilename(originalsPath))
}
}

View file

@ -3,21 +3,21 @@ package photoprism
import (
"testing"
"github.com/photoprism/photoprism/internal/test"
"github.com/photoprism/photoprism/internal/context"
"github.com/stretchr/testify/assert"
)
func TestMediaFile_GetThumbnail(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.CreateDirectories()
ctx.CreateDirectories()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
image1, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
image1, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
thumbnail1, err := image1.Thumbnail(conf.ThumbnailsPath(), 350)
thumbnail1, err := image1.Thumbnail(ctx.ThumbnailsPath(), 350)
assert.Empty(t, err)
@ -25,16 +25,16 @@ func TestMediaFile_GetThumbnail(t *testing.T) {
}
func TestMediaFile_GetSquareThumbnail(t *testing.T) {
conf := test.NewConfig()
ctx := context.TestContext()
conf.CreateDirectories()
ctx.CreateDirectories()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
image1, err := NewMediaFile(conf.ImportPath() + "/iphone/IMG_6788.JPG")
image1, err := NewMediaFile(ctx.ImportPath() + "/iphone/IMG_6788.JPG")
assert.Nil(t, err)
thumbnail1, err := image1.SquareThumbnail(conf.ThumbnailsPath(), 350)
thumbnail1, err := image1.SquareThumbnail(ctx.ThumbnailsPath(), 350)
assert.Empty(t, err)
@ -46,23 +46,23 @@ func TestCreateThumbnailsFromOriginals(t *testing.T) {
t.Skip("skipping test in short mode.")
}
conf := test.NewConfig()
ctx := context.TestContext()
conf.CreateDirectories()
ctx.CreateDirectories()
conf.InitializeTestData(t)
ctx.InitializeTestData(t)
tensorFlow := NewTensorFlow(conf.TensorFlowModelPath())
tensorFlow := NewTensorFlow(ctx.TensorFlowModelPath())
indexer := NewIndexer(conf.OriginalsPath(), tensorFlow, conf.Db())
indexer := NewIndexer(ctx.OriginalsPath(), tensorFlow, ctx.Db())
converter := NewConverter(conf.DarktableCli())
converter := NewConverter(ctx.DarktableCli())
importer := NewImporter(conf.OriginalsPath(), indexer, converter)
importer := NewImporter(ctx.OriginalsPath(), indexer, converter)
importer.ImportPhotosFromDirectory(conf.ImportPath())
importer.ImportPhotosFromDirectory(ctx.ImportPath())
CreateThumbnailsFromOriginals(conf.OriginalsPath(), conf.ThumbnailsPath(), 600, false)
CreateThumbnailsFromOriginals(ctx.OriginalsPath(), ctx.ThumbnailsPath(), 600, false)
CreateThumbnailsFromOriginals(conf.OriginalsPath(), conf.ThumbnailsPath(), 300, true)
CreateThumbnailsFromOriginals(ctx.OriginalsPath(), ctx.ThumbnailsPath(), 300, true)
}

View file

@ -5,27 +5,27 @@ import (
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/api"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/context"
)
func registerRoutes(app *gin.Engine, conf photoprism.Config) {
func registerRoutes(app *gin.Engine, ctx *context.Context) {
// Favicon
app.StaticFile("/favicon.ico", conf.HttpFaviconsPath()+"/favicon.ico")
app.StaticFile("/favicon.ico", ctx.HttpFaviconsPath()+"/favicon.ico")
// Static assets like js and css files
app.Static("/assets", conf.HttpPublicPath())
app.Static("/assets", ctx.HttpPublicPath())
// JSON-REST API Version 1
v1 := app.Group("/api/v1")
{
api.GetPhotos(v1, conf)
api.GetThumbnail(v1, conf)
api.LikePhoto(v1, conf)
api.DislikePhoto(v1, conf)
api.GetPhotos(v1, ctx)
api.GetThumbnail(v1, ctx)
api.LikePhoto(v1, ctx)
api.DislikePhoto(v1, ctx)
}
// Default HTML page (client-side routing implemented via Vue.js)
app.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", conf.ClientConfig())
c.HTML(http.StatusOK, "index.tmpl", ctx.ClientConfig())
})
}

View file

@ -4,15 +4,15 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/context"
log "github.com/sirupsen/logrus"
)
// Start the REST API server using the configuration provided
func Start(conf photoprism.Config) {
if conf.HttpServerMode() != "" {
gin.SetMode(conf.HttpServerMode())
} else if conf.Debug() == false {
func Start(ctx *context.Context) {
if ctx.HttpServerMode() != "" {
gin.SetMode(ctx.HttpServerMode())
} else if ctx.Debug() == false {
gin.SetMode(gin.ReleaseMode)
}
@ -20,11 +20,11 @@ func Start(conf photoprism.Config) {
app.Use(gin.Logger(), gin.Recovery())
// Set template directory
app.LoadHTMLGlob(conf.HttpTemplatesPath() + "/*")
app.LoadHTMLGlob(ctx.HttpTemplatesPath() + "/*")
registerRoutes(app, conf)
registerRoutes(app, ctx)
if err := app.Run(fmt.Sprintf("%s:%d", conf.HttpServerHost(), conf.HttpServerPort())); err != nil {
if err := app.Run(fmt.Sprintf("%s:%d", ctx.HttpServerHost(), ctx.HttpServerPort())); err != nil {
log.Error(err)
}
}

View file

@ -1,356 +0,0 @@
package test
import (
"fmt"
"os"
"testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/internal/frontend"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/models"
)
const (
DataURL = "https://dl.photoprism.org/fixtures/test.zip"
DataHash = "1a59b358b80221ab3e76efb683ad72402f0b0844"
ConfigFile = "../../configs/photoprism.yml"
)
var DarktableCli = "/usr/bin/darktable-cli"
var DataZip = "/tmp/photoprism/testdata.zip"
var AssetsPath = fsutil.ExpandedFilename("../../assets")
var DataPath = AssetsPath + "/testdata"
var CachePath = fsutil.ExpandedFilename(DataPath + "/cache")
var OriginalsPath = fsutil.ExpandedFilename(DataPath + "/originals")
var ImportPath = fsutil.ExpandedFilename(DataPath + "/import")
var ExportPath = fsutil.ExpandedFilename(DataPath + "/export")
var DatabaseDriver = "mysql"
var DatabaseDsn = "photoprism:photoprism@tcp(database:3306)/photoprism?parseTime=true"
func init() {
conf := NewConfig()
conf.MigrateDb()
}
type Config struct {
db *gorm.DB
}
func (c *Config) RemoveTestData(t *testing.T) {
os.RemoveAll(c.ImportPath())
os.RemoveAll(c.ExportPath())
os.RemoveAll(c.OriginalsPath())
os.RemoveAll(c.CachePath())
}
func (c *Config) DownloadTestData(t *testing.T) {
if fsutil.Exists(DataZip) {
hash := fsutil.Hash(DataZip)
if hash != DataHash {
os.Remove(DataZip)
t.Logf("Removed outdated test data zip file (fingerprint %s)\n", hash)
}
}
if !fsutil.Exists(DataZip) {
fmt.Printf("Downloading latest test data zip file from %s\n", DataURL)
if err := fsutil.Download(DataZip, DataURL); err != nil {
fmt.Printf("Download failed: %s\n", err.Error())
}
}
}
func (c *Config) UnzipTestData(t *testing.T) {
if _, err := fsutil.Unzip(DataZip, DataPath); err != nil {
t.Logf("Could not unzip test data: %s\n", err.Error())
}
}
func (c *Config) InitializeTestData(t *testing.T) {
t.Log("Initializing test data")
c.RemoveTestData(t)
c.DownloadTestData(t)
c.UnzipTestData(t)
}
func NewConfig() *Config {
return &Config{}
}
// CreateDirectories creates all the folders that photoprism needs. These are:
// OriginalsPath
// ThumbnailsPath
// ImportPath
// ExportPath
func (c *Config) CreateDirectories() error {
if err := os.MkdirAll(c.OriginalsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ImportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ExportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.ThumbnailsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.SqlServerPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.TensorFlowModelPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.HttpPublicBuildPath(), os.ModePerm); err != nil {
return err
}
return nil
}
// connectToDatabase estabilishes a connection to a database given a driver.
// It tries to do this 12 times with a 5 second sleep intervall in between.
func (c *Config) connectToDatabase() error {
dbDriver := c.DatabaseDriver()
dbDsn := c.DatabaseDsn()
db, err := gorm.Open(dbDriver, dbDsn)
if err != nil || db == nil {
for i := 1; i <= 12; i++ {
time.Sleep(5 * time.Second)
db, err = gorm.Open(dbDriver, dbDsn)
if db != nil && err == nil {
break
}
}
if err != nil || db == nil {
log.Fatal(err)
}
}
c.db = db
return err
}
// AppName returns the application name.
func (c *Config) AppName() string {
return "PhotoPrism"
}
// AppVersion returns the application version.
func (c *Config) AppVersion() string {
return "DEVELOPMENT"
}
// AppCopyright returns the application copyright.
func (c *Config) AppCopyright() string {
return "The PhotoPrism contributors <hello@photoprism.org>"
}
// Debug returns true if debug mode is on.
func (c *Config) Debug() bool {
return false
}
// LogLevel returns the logrus log level.
func (c *Config) LogLevel() log.Level {
return log.DebugLevel
}
// ConfigFile returns the config file name.
func (c *Config) ConfigFile() string {
return ConfigFile
}
// HttpServerHost returns the server IP address (empty for all).
func (c *Config) HttpServerHost() string {
return "127.0.0.1"
}
// HttpServerPort returns the server port.
func (c *Config) HttpServerPort() int {
return 80
}
// SqlServerHost returns the database server IP address (empty for all).
func (c *Config) SqlServerHost() string {
return "127.0.0.1"
}
// SqlServerPort returns the database server port.
func (c *Config) SqlServerPort() uint {
return 4001
}
// SqlServerPassword returns the password for the built-in database server.
func (c *Config) SqlServerPassword() string {
return "photoprism"
}
// HttpServerMode returns the server mode.
func (c *Config) HttpServerMode() string {
return "test"
}
// HttpServerPassword returns the password for the Web UI.
func (c *Config) HttpServerPassword() string {
return ""
}
// OriginalsPath returns the originals.
func (c *Config) OriginalsPath() string {
return OriginalsPath
}
// ImportPath returns the import directory.
func (c *Config) ImportPath() string {
return ImportPath
}
// ExportPath returns the export directory.
func (c *Config) ExportPath() string {
return ExportPath
}
// DarktableCli returns the darktable-cli binary file name.
func (c *Config) DarktableCli() string {
return DarktableCli
}
// DatabaseDriver returns the database driver name.
func (c *Config) DatabaseDriver() string {
return DatabaseDriver
}
// DatabaseDsn returns the database data source name (DSN).
func (c *Config) DatabaseDsn() string {
return DatabaseDsn
}
// CachePath returns the path to the cache.
func (c *Config) CachePath() string {
return CachePath
}
// ThumbnailsPath returns the path to the cached thumbnails.
func (c *Config) ThumbnailsPath() string {
return c.CachePath() + "/thumbnails"
}
// AssetsPath returns the path to the assets.
func (c *Config) AssetsPath() string {
return AssetsPath
}
// TensorFlowModelPath returns the tensorflow model path.
func (c *Config) TensorFlowModelPath() string {
return c.AssetsPath() + "/tensorflow"
}
// SqlServerPath returns the database storage path (e.g. for SQLite or Bleve).
func (c *Config) SqlServerPath() string {
return c.ServerPath() + "/database"
}
// ServerPath returns the server assets path (public files, favicons, templates,...).
func (c *Config) ServerPath() string {
return c.AssetsPath() + "/server"
}
// HttpTemplatesPath returns the server templates path.
func (c *Config) HttpTemplatesPath() string {
return c.ServerPath() + "/templates"
}
// HttpFaviconsPath returns the favicons path.
func (c *Config) HttpFaviconsPath() string {
return c.HttpPublicPath() + "/favicons"
}
// HttpPublicPath returns the public server path (//server/assets/*).
func (c *Config) HttpPublicPath() string {
return c.ServerPath() + "/public"
}
// HttpPublicBuildPath returns the public build path (//server/assets/build/*).
func (c *Config) HttpPublicBuildPath() string {
return c.HttpPublicPath() + "/build"
}
// Db gets a db connection. If it already is estabilished it will return that.
func (c *Config) Db() *gorm.DB {
if c.db == nil {
c.connectToDatabase()
}
return c.db
}
// MigrateDb will start a migration process.
func (c *Config) MigrateDb() {
db := c.Db()
db.AutoMigrate(&models.File{},
&models.Photo{},
&models.Tag{},
&models.Album{},
&models.Location{},
&models.Camera{},
&models.Lens{},
&models.Country{})
}
// ClientConfig returns a loaded and set configuration entity.
func (c *Config) ClientConfig() frontend.Config {
db := c.Db()
var cameras []*models.Camera
type country struct {
LocCountry string
LocCountryCode string
}
var countries []country
db.Model(&models.Location{}).Select("DISTINCT loc_country_code, loc_country").Scan(&countries)
db.Where("deleted_at IS NULL").Limit(1000).Order("camera_model").Find(&cameras)
jsHash := fsutil.Hash(c.HttpPublicBuildPath() + "/app.js")
cssHash := fsutil.Hash(c.HttpPublicBuildPath() + "/app.css")
result := frontend.Config{
"appName": c.AppName(),
"appVersion": c.AppVersion(),
"debug": c.Debug(),
"cameras": cameras,
"countries": countries,
"jsHash": jsHash,
"cssHash": cssHash,
}
return result
}

View file

@ -1,25 +0,0 @@
package test
import (
"testing"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
)
func TestNewConfig(t *testing.T) {
c := NewConfig()
assert.IsType(t, new(Config), c)
assert.Equal(t, AssetsPath, c.AssetsPath())
assert.False(t, c.Debug())
}
func TestConfig_ConnectToDatabase(t *testing.T) {
c := NewConfig()
db := c.Db()
assert.IsType(t, &gorm.DB{}, db)
}

View file

@ -1,28 +0,0 @@
package test
import (
"flag"
"github.com/urfave/cli"
)
// Returns example cli context for testing
func CliContext() *cli.Context {
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("debug", false, "doc")
globalSet.String("config-file", ConfigFile, "doc")
globalSet.String("assets-path", AssetsPath, "doc")
globalSet.String("originals-path", OriginalsPath, "doc")
globalSet.String("darktable-cli", DarktableCli, "doc")
app := cli.NewApp()
c := cli.NewContext(app, globalSet, nil)
c.Set("config-file", ConfigFile)
c.Set("assets-path", AssetsPath)
c.Set("originals-path", OriginalsPath)
c.Set("darktable-cli", DarktableCli)
return c
}

View file

@ -1,14 +0,0 @@
package test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
func TestCliContext(t *testing.T) {
result := CliContext()
assert.IsType(t, new(cli.Context), result)
}

View file

@ -1,8 +0,0 @@
/*
Package context contains test configuration.
Additional information can be found in our Developer Guide:
https://github.com/photoprism/photoprism/wiki
*/
package test