Remove test and frontend packages; app and test context refactoring
This commit is contained in:
parent
6cba9d061f
commit
71adb35cff
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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")
|
||||
})
|
4
internal/context/client_config.go
Normal file
4
internal/context/client_config.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package context
|
||||
|
||||
// HTTP client / Web UI config values
|
||||
type ClientConfig map[string]interface{}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
373
internal/context/context.go
Normal 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
|
||||
}
|
22
internal/context/context_test.go
Normal file
22
internal/context/context_test.go
Normal 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)
|
||||
}
|
129
internal/context/test_context.go
Normal file
129
internal/context/test_context.go
Normal 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)
|
||||
}
|
39
internal/context/test_context_test.go
Normal file
39
internal/context/test_context_test.go
Normal 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)
|
||||
}
|
13
internal/forms/photo_search_test.go
Normal file
13
internal/forms/photo_search_test.go
Normal 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)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package frontend
|
||||
|
||||
// HTTP client / Web UI config values
|
||||
type Config map[string]interface{}
|
|
@ -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
|
20
internal/fsutil/file_test.go
Normal file
20
internal/fsutil/file_test.go
Normal 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)
|
||||
}
|
12
internal/fsutil/hash_test.go
Normal file
12
internal/fsutil/hash_test.go
Normal 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)
|
||||
}
|
|
@ -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,
|
||||
|
|
18
internal/models/camera_test.go
Normal file
18
internal/models/camera_test.go
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
Loading…
Reference in a new issue