Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Theresa Gresch 2020-04-29 10:30:57 +02:00
commit b8416f3d04
37 changed files with 305 additions and 266 deletions

View file

@ -15,14 +15,14 @@ assets-path: ~/.local/share/photoprism
resources-path: ~/.local/share/photoprism/resources
originals-path: ~/Pictures/Originals
import-path: ~/Pictures/Import
sql-host: localhost
sql-port: 4000
sql-password: photoprism
http-host:
http-mode: release
http-port: 2342
database-driver: internal
database-dsn: root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true
tidb-host: localhost
tidb-port: 2343
tidb-password: photoprism
database-driver: tidb
database-dsn: root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true
pid-filename: ~/.local/share/photoprism/photoprism.pid
log-filename: ~/.local/share/photoprism/photoprism.log
detach-server: false

View file

@ -12,23 +12,23 @@ services:
- "~/.cache/go-mod:/go/pkg/mod"
environment:
PHOTOPRISM_URL: "http://localhost:2342/"
PHOTOPRISM_UPLOAD_NSFW: "false"
PHOTOPRISM_DETECT_NSFW: "true"
PHOTOPRISM_PID_FILENAME: "photoprism.pid"
PHOTOPRISM_LOG_FILENAME: "photoprism.log"
PHOTOPRISM_DETACH_SERVER: "true"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_TIDB_HOST: "0.0.0.0"
PHOTOPRISM_TIDB_PORT: 2343
PHOTOPRISM_TIDB_PASSWORD: "photoprism"
PHOTOPRISM_DATABASE_DRIVER: "tidb"
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
PHOTOPRISM_TITLE: "PhotoPrism"
PHOTOPRISM_SUBTITLE: "Browse your life"
PHOTOPRISM_DESCRIPTION: "Personal Photo Management tested by Travis CI."
PHOTOPRISM_AUTHOR: "PhotoPrism.org"
PHOTOPRISM_TWITTER: "@browseyourlife"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_SQL_HOST: "0.0.0.0"
PHOTOPRISM_SQL_PORT: 4000
PHOTOPRISM_SQL_PASSWORD: "photoprism"
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true"
PHOTOPRISM_DATABASE_DRIVER: "internal"
PHOTOPRISM_PID_FILENAME: "photoprism.pid"
PHOTOPRISM_LOG_FILENAME: "photoprism.log"
PHOTOPRISM_DETACH_SERVER: "true"
PHOTOPRISM_UPLOAD_NSFW: "false"
PHOTOPRISM_DETECT_NSFW: "true"
CODECOV_TOKEN:
CODECOV_ENV:
CODECOV_URL:

View file

@ -8,16 +8,12 @@ services:
- photoprism-db
ports:
- "2342:2342" # Web Server (PhotoPrism)
- "4000:4000" # Database (MySQL compatible)
- "2343:2343" # Database (built-in TiDB)
volumes:
- ".:/go/src/github.com/photoprism/photoprism"
shm_size: "2gb"
environment:
PHOTOPRISM_URL: "http://localhost:2342/"
PHOTOPRISM_TITLE: "PhotoPrism"
PHOTOPRISM_SUBTITLE: "Browse your life"
PHOTOPRISM_AUTHOR: "PhotoPrism.org"
PHOTOPRISM_TWITTER: "@browseyourlife"
PHOTOPRISM_DEBUG: "true"
PHOTOPRISM_READONLY: "false"
PHOTOPRISM_PUBLIC: "false"
@ -25,6 +21,13 @@ services:
PHOTOPRISM_UPLOAD_NSFW: "false"
PHOTOPRISM_DETECT_NSFW: "true"
PHOTOPRISM_SERVER_MODE: "debug"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_TIDB_HOST: "0.0.0.0"
PHOTOPRISM_TIDB_PORT: 2343
PHOTOPRISM_TIDB_PASSWORD: "photoprism"
PHOTOPRISM_DATABASE_DRIVER: "tidb"
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_CACHE_PATH: "/go/src/github.com/photoprism/photoprism/assets/cache"
PHOTOPRISM_RESOURCES_PATH: "/go/src/github.com/photoprism/photoprism/assets/resources"
@ -32,13 +35,10 @@ services:
PHOTOPRISM_IMPORT_PATH: "/go/src/github.com/photoprism/photoprism/assets/photos/import"
PHOTOPRISM_TEMP_PATH: "/go/src/github.com/photoprism/photoprism/assets/photos/temp"
PHOTOPRISM_ORIGINALS_PATH: "/go/src/github.com/photoprism/photoprism/assets/photos/originals"
PHOTOPRISM_DATABASE_DRIVER: "internal"
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_SQL_HOST: "0.0.0.0"
PHOTOPRISM_SQL_PORT: 4000
PHOTOPRISM_SQL_PASSWORD: "photoprism"
PHOTOPRISM_TITLE: "PhotoPrism"
PHOTOPRISM_SUBTITLE: "Browse your life"
PHOTOPRISM_AUTHOR: "PhotoPrism.org"
PHOTOPRISM_TWITTER: "@browseyourlife"
TF_CPP_MIN_LOG_LEVEL: 0
photoprism-db:

View file

@ -119,8 +119,8 @@ RUN echo "alias go=richgo" > /root/.bash_aliases
# Set up project directory
WORKDIR "/go/src/github.com/photoprism/photoprism"
# Expose HTTP port 2342 plus 4000 for TiDB and 9515 for chromedriver
EXPOSE 2342 4000 9515
# Expose HTTP port 2342 plus 2343 for TiDB and 9515 for chromedriver
EXPOSE 2342 2343 9515
# Keep container running (services can be started manually using a terminal)
CMD tail -f /dev/null

View file

@ -56,7 +56,7 @@ ENV PATH /photoprism/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin
ENV PHOTOPRISM_ORIGINALS_PATH /photoprism/originals
ENV PHOTOPRISM_IMPORT_PATH /photoprism/import
ENV PHOTOPRISM_DATABASE_PATH /photoprism/database
ENV PHOTOPRISM_TIDB_PATH /photoprism/database
ENV PHOTOPRISM_TEMP_PATH /photoprism/temp
ENV PHOTOPRISM_CACHE_PATH /photoprism/cache
ENV PHOTOPRISM_CONFIG_PATH /photoprism/config
@ -85,7 +85,7 @@ RUN chmod -R 777 /photoprism
RUN photoprism -v
# Expose http and database ports
EXPOSE 2342 4000
EXPOSE 2342 2343
# Run server
CMD photoprism start

View file

@ -52,7 +52,7 @@ RUN apt-get update && apt-get upgrade && \
ENV LD_LIBRARY_PATH /root/.local/lib:/usr/local/lib:/usr/lib:/lib
ENV TF_CPP_MIN_LOG_LEVEL 0
RUN curl -L \
"https://dl.photoprism.org/tensorflow/arm64/libtensorflow-arm64-1.14.0.tar.gz" | \
"https://dl.photoprism.org/tensorflow/arm64/libtensorflow-arm64-1.15.2.tar.gz" | \
tar -C "/usr" -xz
RUN ldconfig
@ -94,7 +94,7 @@ RUN wget "https://dl.photoprism.org/tensorflow/nasnet.zip?${BUILD_TAG}" -O /tmp/
# Set up project directory
WORKDIR "/go/src/github.com/photoprism/photoprism"
COPY ../../photoprism-arm64 .
COPY . .
# Build PhotoPrism
RUN make dep build-js install
@ -140,7 +140,7 @@ ENV PATH /photoprism/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin
ENV PHOTOPRISM_ORIGINALS_PATH /photoprism/originals
ENV PHOTOPRISM_IMPORT_PATH /photoprism/import
ENV PHOTOPRISM_DATABASE_PATH /photoprism/database
ENV PHOTOPRISM_TIDB_PATH /photoprism/database
ENV PHOTOPRISM_TEMP_PATH /photoprism/temp
ENV PHOTOPRISM_CACHE_PATH /photoprism/cache
ENV PHOTOPRISM_CONFIG_PATH /photoprism/config
@ -169,7 +169,7 @@ RUN chmod -R 777 /photoprism
RUN photoprism -v
# Expose http and database ports
EXPOSE 2342 4000
EXPOSE 2342 2343
# Run server
CMD photoprism start

View file

@ -13,7 +13,7 @@ services:
- seccomp:unconfined
ports:
- 2342:2342 # [local port]:[container port]
# - 4000:4000 # Internal database (MySQL compatible)
# - 2343:2343 # Database (built-in TiDB)
healthcheck: # Optional
test: "photoprism status"
interval: 60s
@ -35,13 +35,13 @@ services:
PHOTOPRISM_DISABLE_SETTINGS: "false"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_SQL_HOST: "0.0.0.0"
PHOTOPRISM_SQL_PORT: 4000 # Port for internal TiDB SQL server (driver "internal")
PHOTOPRISM_SQL_PASSWORD: "photoprism" # Plain text only (username "root")
PHOTOPRISM_TIDB_HOST: "0.0.0.0"
PHOTOPRISM_TIDB_PORT: 2343 # Port for built-in TiDB SQL server (driver "tidb")
PHOTOPRISM_TIDB_PASSWORD: "photoprism" # Plain text only (username "root")
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # Plain text or bcrypt hash (escape "$" with "$$")
PHOTOPRISM_WEBDAV_PASSWORD: "photoprism" # Plain text only (username "photoprism")
PHOTOPRISM_DATABASE_DRIVER: "internal" # Change to "mysql" for external MySQL or MariaDB
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true"
PHOTOPRISM_DATABASE_DRIVER: "tidb" # Change to "mysql" for external MySQL or MariaDB
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
# PHOTOPRISM_THUMB_QUALITY: 95 # High-quality thumbnails (optional)
# PHOTOPRISM_THUMB_SIZE: 3840
# PHOTOPRISM_THUMB_LIMIT: 3840

View file

@ -12,7 +12,7 @@ services:
restart: unless-stopped
ports:
- 2342:2342 # [local port]:[container port]
# - 4000:4000 # Internal database (MySQL compatible)
# - 2343:2343 # Database (built-in TiDB)
healthcheck: # Optional
test: "photoprism status"
interval: 60s
@ -34,13 +34,13 @@ services:
PHOTOPRISM_DISABLE_SETTINGS: "false"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_SQL_HOST: "0.0.0.0"
PHOTOPRISM_SQL_PORT: 4000 # Port for internal TiDB SQL server (driver "internal")
PHOTOPRISM_SQL_PASSWORD: "photoprism" # Plain text only (username "root")
PHOTOPRISM_TIDB_HOST: "0.0.0.0"
PHOTOPRISM_TIDB_PORT: 2343 # Port for built-in TiDB SQL server (driver "tidb")
PHOTOPRISM_TIDB_PASSWORD: "photoprism" # Plain text only (username "root")
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # Plain text or bcrypt hash (escape "$" with "$$")
PHOTOPRISM_WEBDAV_PASSWORD: "photoprism" # Plain text only (username "photoprism")
PHOTOPRISM_DATABASE_DRIVER: "internal" # Change to "mysql" for external MySQL or MariaDB
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true"
PHOTOPRISM_DATABASE_DRIVER: "tidb" # Change to "mysql" for external MySQL or MariaDB
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
# PHOTOPRISM_DATABASE_DRIVER: "mysql" # Using MariaDB or MySQL instead of the internal TiDB is optional
# PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?parseTime=true"
# PHOTOPRISM_THUMB_QUALITY: 95 # High-quality thumbnails (optional, default JPEG quality is 90)

View file

@ -85,7 +85,7 @@
<div class="caption">
<button @click.exact="editPhoto(index)">
<v-icon size="14" title="Taken" v-if="photo.TakenSrc">date_range</v-icon>
<v-icon size="14" title="Imported" v-else>save</v-icon>
<v-icon size="14" title="Modified" v-else>save</v-icon>
{{ photo.getDateString() }}
</button>
<br/>

View file

@ -102,7 +102,7 @@ class Photo extends RestModel {
refreshFileAttr() {
const file = this.mainFile();
if (!file) {
if (!file || !file.FileHash) {
return;
}

View file

@ -29,7 +29,11 @@ class Thumb extends Model {
let result = [];
photos.forEach((p) => {
result.push(this.fromPhoto(p));
let thumb = this.fromPhoto(p);
if(thumb) {
result.push(thumb);
}
});
return result;
@ -40,11 +44,15 @@ class Thumb extends Model {
return this.fromFile(photo, photo.Files.find(f => !!f.FilePrimary));
}
if(!photo || !photo.FileHash) {
return false;
}
const result = {
uuid: photo.PhotoUUID,
title: photo.PhotoTitle,
favorite: photo.PhotoFavorite,
download_url: "/api/v1/download/" + photo.FileHash,
download_url: this.downloadUrl(photo),
original_w: photo.FileWidth,
original_h: photo.FileHeight,
};
@ -63,11 +71,15 @@ class Thumb extends Model {
}
static fromFile(photo, file) {
if(!photo || !file || !file.FileHash) {
return false;
}
const result = {
uuid: photo.PhotoUUID,
title: photo.PhotoTitle,
favorite: photo.PhotoFavorite,
download_url: "/api/v1/download/" + file.FileHash,
download_url: this.downloadUrl(file),
original_w: file.FileWidth,
original_h: file.FileHeight,
};
@ -92,8 +104,12 @@ class Thumb extends Model {
if (!p.Files) return;
p.Files.forEach((f) => {
if (f.FileType === "jpg") {
result.push(this.fromFile(p, f));
if (f && f.FileType === "jpg") {
let thumb = this.fromFile(p, f);
if(thumb) {
result.push(thumb);
}
}
}
);
@ -132,6 +148,15 @@ class Thumb extends Model {
return "/api/v1/thumbnails/" + file.FileHash + "/" + type;
}
static downloadUrl(file) {
if (!file || !file.FileHash) {
return "";
}
return "/api/v1/download/" + file.FileHash;
}
}
export default Thumb;

View file

@ -57,18 +57,18 @@ func configAction(ctx *cli.Context) error {
fmt.Printf("static-path %s\n", conf.HttpStaticPath())
fmt.Printf("static-build-path %s\n", conf.HttpStaticBuildPath())
fmt.Printf("database-path %s\n", conf.DatabasePath())
fmt.Printf("database-driver %s\n", conf.DatabaseDriver())
fmt.Printf("database-dsn %s\n", conf.DatabaseDsn())
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("http-host %s\n", conf.HttpServerHost())
fmt.Printf("http-port %d\n", conf.HttpServerPort())
fmt.Printf("http-mode %s\n", conf.HttpServerMode())
fmt.Printf("tidb-host %s\n", conf.TidbServerHost())
fmt.Printf("tidb-port %d\n", conf.TidbServerPort())
fmt.Printf("tidb-password %s\n", conf.TidbServerPassword())
fmt.Printf("tidb-path %s\n", conf.TidbServerPath())
fmt.Printf("database-driver %s\n", conf.DatabaseDriver())
fmt.Printf("database-dsn %s\n", conf.DatabaseDsn())
fmt.Printf("sips-bin %s\n", conf.SipsBin())
fmt.Printf("darktable-bin %s\n", conf.DarktableBin())
fmt.Printf("exiftool-bin %s\n", conf.ExifToolBin())

View file

@ -51,11 +51,11 @@ func startAction(ctx *cli.Context) error {
if ctx.IsSet("config") {
fmt.Printf("NAME VALUE\n")
fmt.Printf("detach-server %t\n", conf.DetachServer())
fmt.Printf("database-path %s\n", conf.DatabasePath())
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("tidb-host %s\n", conf.TidbServerHost())
fmt.Printf("tidb-port %d\n", conf.TidbServerPort())
fmt.Printf("tidb-password %s\n", conf.TidbServerPassword())
fmt.Printf("tidb-path %s\n", conf.TidbServerPath())
fmt.Printf("http-host %s\n", conf.HttpServerHost())
fmt.Printf("http-port %d\n", conf.HttpServerPort())

View file

@ -36,7 +36,7 @@ func TestConfig_Version(t *testing.T) {
c := NewConfig(ctx)
version := c.Version()
assert.Equal(t, "1.0.0", version)
assert.Equal(t, "test", version)
}
func TestConfig_TensorFlowVersion(t *testing.T) {
@ -103,35 +103,35 @@ func TestConfig_DetachServer(t *testing.T) {
assert.Equal(t, false, detachServer)
}
func TestConfig_SqlServerHost(t *testing.T) {
func TestConfig_TidbServerHost(t *testing.T) {
ctx := CliTestContext()
c := NewConfig(ctx)
host := c.SqlServerHost()
host := c.TidbServerHost()
assert.Equal(t, "127.0.0.1", host)
}
func TestConfig_SqlServerPort(t *testing.T) {
func TestConfig_TidbServerPort(t *testing.T) {
ctx := CliTestContext()
c := NewConfig(ctx)
port := c.SqlServerPort()
assert.Equal(t, uint(4000), port)
port := c.TidbServerPort()
assert.Equal(t, uint(2343), port)
}
func TestConfig_SqlServerPath(t *testing.T) {
func TestConfig_TidbServerPath(t *testing.T) {
ctx := CliTestContext()
c := NewConfig(ctx)
path := c.DatabasePath()
path := c.TidbServerPath()
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/assets/resources/database", path)
}
func TestConfig_SqlServerPassword(t *testing.T) {
func TestConfig_TidbServerPassword(t *testing.T) {
ctx := CliTestContext()
c := NewConfig(ctx)
password := c.SqlServerPassword()
password := c.TidbServerPassword()
assert.Equal(t, "", password)
}
@ -222,7 +222,7 @@ func TestConfig_DatabaseDriver(t *testing.T) {
c := NewConfig(ctx)
driver := c.DatabaseDriver()
assert.Equal(t, "internal", driver)
assert.Equal(t, DriverTidb, driver)
}
func TestConfig_DatabaseDsn(t *testing.T) {
@ -230,7 +230,7 @@ func TestConfig_DatabaseDsn(t *testing.T) {
c := NewConfig(ctx)
dsn := c.DatabaseDriver()
assert.Equal(t, "internal", dsn)
assert.Equal(t, DriverTidb, dsn)
}
func TestConfig_CachePath(t *testing.T) {

View file

@ -18,17 +18,17 @@ import (
// DatabaseDriver returns the database driver name.
func (c *Config) DatabaseDriver() string {
if c.params.DatabaseDriver == "" {
return DbTiDB
if strings.ToLower(c.params.DatabaseDriver) == "mysql" {
return DriverMysql
}
return c.params.DatabaseDriver
return DriverTidb
}
// DatabaseDsn returns the database data source name (DSN).
func (c *Config) DatabaseDsn() string {
if c.params.DatabaseDsn == "" {
return "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true"
return "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
}
return c.params.DatabaseDsn
@ -67,7 +67,6 @@ func (c *Config) MigrateDb() {
&entity.FileSync{},
&entity.Photo{},
&entity.Description{},
&entity.Event{},
&entity.Place{},
&entity.Location{},
&entity.Camera{},
@ -106,7 +105,6 @@ func (c *Config) DropTables() {
&entity.FileSync{},
&entity.Photo{},
&entity.Description{},
&entity.Event{},
&entity.Place{},
&entity.Location{},
&entity.Camera{},
@ -146,17 +144,17 @@ func (c *Config) connectToDatabase(ctx context.Context) error {
isTiDB := false
initSuccess := false
if dbDriver == DbTiDB {
if dbDriver == DriverTidb {
isTiDB = true
dbDriver = DbMySQL
dbDriver = DriverMysql
}
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())
log.Infof("starting database server at %s:%d\n", c.TidbServerHost(), c.TidbServerPort())
go tidb.Start(ctx, c.DatabasePath(), c.SqlServerPort(), c.SqlServerHost(), c.Debug())
go tidb.Start(ctx, c.TidbServerPath(), c.TidbServerPort(), c.TidbServerHost(), c.Debug())
}
for i := 1; i <= 12; i++ {
@ -169,7 +167,7 @@ func (c *Config) connectToDatabase(ctx context.Context) error {
}
if isTiDB && !initSuccess {
err = tidb.InitDatabase(c.SqlServerPort(), c.SqlServerPassword())
err = tidb.InitDatabase(c.TidbServerPort(), c.TidbServerPassword())
if err != nil {
log.Debug(err)

View file

@ -61,8 +61,8 @@ func (c *Config) CreateDirectories() error {
return createError(c.ResourcesPath(), err)
}
if err := os.MkdirAll(c.DatabasePath(), os.ModePerm); err != nil {
return createError(c.DatabasePath(), err)
if err := os.MkdirAll(c.TidbServerPath(), os.ModePerm); err != nil {
return createError(c.TidbServerPath(), err)
}
if err := os.MkdirAll(c.TensorFlowModelPath(), os.ModePerm); err != nil {

View file

@ -149,23 +149,6 @@ var GlobalFlags = []cli.Flag{
Value: "~/.local/share/photoprism",
EnvVar: "PHOTOPRISM_ASSETS_PATH",
},
cli.StringFlag{
Name: "database-path",
Usage: "built-in database server storage path",
EnvVar: "PHOTOPRISM_DATABASE_PATH",
},
cli.StringFlag{
Name: "database-driver",
Usage: "database `DRIVER` (internal or mysql)",
Value: "internal",
EnvVar: "PHOTOPRISM_DATABASE_DRIVER",
},
cli.StringFlag{
Name: "database-dsn",
Usage: "database data source name (`DSN`)",
Value: "root:@tcp(localhost:4000)/photoprism?parseTime=true",
EnvVar: "PHOTOPRISM_DATABASE_DSN",
},
cli.StringFlag{
Name: "sips-bin",
Usage: "sips cli binary `FILENAME`",
@ -192,6 +175,7 @@ var GlobalFlags = []cli.Flag{
},
cli.IntFlag{
Name: "http-port",
Value: 2342,
Usage: "HTTP server port",
EnvVar: "PHOTOPRISM_HTTP_PORT",
},
@ -206,19 +190,37 @@ var GlobalFlags = []cli.Flag{
EnvVar: "PHOTOPRISM_HTTP_MODE",
},
cli.IntFlag{
Name: "sql-port",
Usage: "built-in SQL server port",
EnvVar: "PHOTOPRISM_SQL_PORT",
Name: "tidb-port",
Value: 2343,
Usage: "built-in TiDB server port",
EnvVar: "PHOTOPRISM_TIDB_PORT",
},
cli.StringFlag{
Name: "sql-host",
Usage: "built-in SQL server host",
EnvVar: "PHOTOPRISM_SQL_HOST",
Name: "tidb-host",
Usage: "built-in TiDB server host",
EnvVar: "PHOTOPRISM_TIDB_HOST",
},
cli.StringFlag{
Name: "sql-password",
Usage: "built-in SQL server password",
EnvVar: "PHOTOPRISM_SQL_PASSWORD",
Name: "tidb-password",
Usage: "built-in TiDB server password",
EnvVar: "PHOTOPRISM_TIDB_PASSWORD",
},
cli.StringFlag{
Name: "tidb-path",
Usage: "built-in TiDB server storage path",
EnvVar: "PHOTOPRISM_TIDB_PATH",
},
cli.StringFlag{
Name: "database-driver",
Usage: "database `DRIVER` (tidb or mysql)",
Value: "tidb",
EnvVar: "PHOTOPRISM_DATABASE_DRIVER",
},
cli.StringFlag{
Name: "database-dsn",
Usage: "database data source name (`DSN`)",
Value: "root:@tcp(localhost:2343)/photoprism?parseTime=true",
EnvVar: "PHOTOPRISM_DATABASE_DSN",
},
cli.BoolFlag{
Name: "detect-nsfw",

View file

@ -15,8 +15,8 @@ import (
// define database drivers const
const (
DbTiDB = "internal"
DbMySQL = "mysql"
DriverTidb = "tidb"
DriverMysql = "mysql"
)
// Params provides a struct in which application configuration is stored.
@ -54,12 +54,12 @@ type Params struct {
ImportPath string `yaml:"import-path" flag:"import-path"`
AssetsPath string `yaml:"assets-path" flag:"assets-path"`
ResourcesPath string `yaml:"resources-path" flag:"resources-path"`
DatabasePath string `yaml:"database-path" flag:"database-path"`
DatabaseDriver string `yaml:"database-driver" flag:"database-driver"`
DatabaseDsn string `yaml:"database-dsn" flag:"database-dsn"`
SqlServerHost string `yaml:"sql-host" flag:"sql-host"`
SqlServerPort uint `yaml:"sql-port" flag:"sql-port"`
SqlServerPassword string `yaml:"sql-password" flag:"sql-password"`
TidbServerHost string `yaml:"tidb-host" flag:"tidb-host"`
TidbServerPort uint `yaml:"tidb-port" flag:"tidb-port"`
TidbServerPassword string `yaml:"tidb-password" flag:"tidb-password"`
TidbServerPath string `yaml:"tidb-path" flag:"tidb-path"`
HttpServerHost string `yaml:"http-host" flag:"http-host"`
HttpServerPort int `yaml:"http-port" flag:"http-port"`
HttpServerMode string `yaml:"http-mode" flag:"http-mode"`
@ -116,7 +116,7 @@ func (c *Params) expandFilenames() {
c.OriginalsPath = fs.Abs(c.OriginalsPath)
c.ImportPath = fs.Abs(c.ImportPath)
c.TempPath = fs.Abs(c.TempPath)
c.DatabasePath = fs.Abs(c.DatabasePath)
c.TidbServerPath = fs.Abs(c.TidbServerPath)
c.PIDFilename = fs.Abs(c.PIDFilename)
c.LogFilename = fs.Abs(c.LogFilename)
}

View file

@ -36,7 +36,7 @@ func TestParams_SetValuesFromFile(t *testing.T) {
assert.Equal(t, "/srv/photoprism/photos/originals", c.OriginalsPath)
assert.Equal(t, "/srv/photoprism/photos/import", c.ImportPath)
assert.Equal(t, "/srv/photoprism/temp", c.TempPath)
assert.Equal(t, "internal", c.DatabaseDriver)
assert.Equal(t, "root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true", c.DatabaseDsn)
assert.Equal(t, DriverTidb, c.DatabaseDriver)
assert.Equal(t, "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true", c.DatabaseDsn)
assert.Equal(t, 81, c.HttpServerPort)
}

View file

@ -2,15 +2,6 @@ package config
import "github.com/photoprism/photoprism/pkg/fs"
// DatabasePath returns the database storage path for TiDB.
func (c *Config) DatabasePath() string {
if c.params.DatabasePath == "" {
return c.ResourcesPath() + "/database"
}
return fs.Abs(c.params.DatabasePath)
}
// DetachServer returns true if server should detach from console (daemon mode).
func (c *Config) DetachServer() bool {
return c.params.DetachServer
@ -72,25 +63,34 @@ func (c *Config) HttpStaticBuildPath() string {
return c.HttpStaticPath() + "/build"
}
// SqlServerHost returns the built-in SQL server host name or IP address (empty for all interfaces).
func (c *Config) SqlServerHost() string {
if c.params.SqlServerHost == "" {
// TidbServerHost returns the host for the built-in TiDB server. (empty for all interfaces).
func (c *Config) TidbServerHost() string {
if c.params.TidbServerHost == "" {
return "127.0.0.1"
}
return c.params.SqlServerHost
return c.params.TidbServerHost
}
// SqlServerPort returns the built-in SQL server port.
func (c *Config) SqlServerPort() uint {
if c.params.SqlServerPort == 0 {
return 4000
// TidbServerPort returns the port for the built-in TiDB server.
func (c *Config) TidbServerPort() uint {
if c.params.TidbServerPort == 0 {
return 2343
}
return c.params.SqlServerPort
return c.params.TidbServerPort
}
// SqlServerPassword returns the password for the built-in database server.
func (c *Config) SqlServerPassword() string {
return c.params.SqlServerPassword
// TidbServerPassword returns the password for the built-in TiDB server.
func (c *Config) TidbServerPassword() string {
return c.params.TidbServerPassword
}
// TidbServerPath returns the database storage path for the built-in TiDB server.
func (c *Config) TidbServerPath() string {
if c.params.TidbServerPath == "" {
return c.ResourcesPath() + "/database"
}
return fs.Abs(c.params.TidbServerPath)
}

View file

@ -144,7 +144,7 @@ func CliTestContext() *cli.Context {
globalSet.Bool("detect-nsfw", config.DetectNSFW, "doc")
app := cli.NewApp()
app.Version = "1.0.0"
app.Version = "test"
c := cli.NewContext(app, globalSet, nil)

View file

@ -5,14 +5,14 @@ cache-path: /srv/photoprism/cache
originals-path: /srv/photoprism/photos/originals
import-path: /srv/photoprism/photos/import
temp-path: /srv/photoprism/temp
sql-host: localhost
sql-port: 4000
sql-password: photoprism
http-host:
http-mode: release
http-port: 81
http-password:
database-driver: internal
database-dsn: root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true
tidb-host: localhost
tidb-port: 2343
tidb-password: photoprism
database-driver: tidb
database-dsn: root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true
theme: lavendel
language: english

View file

@ -1,36 +0,0 @@
package entity
import (
"time"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/pkg/rnd"
)
// Event defines temporal event that can be used to link photos together
type Event struct {
EventUUID string `gorm:"type:varbinary(36);unique_index;"`
EventSlug string `gorm:"type:varbinary(255);unique_index;"`
EventName string
EventType string
EventDescription string `gorm:"type:text;"`
EventNotes string `gorm:"type:text;"`
EventBegin time.Time `gorm:"type:datetime;"`
EventEnd time.Time `gorm:"type:datetime;"`
EventLat float32 `gorm:"type:FLOAT;"`
EventLng float32 `gorm:"type:FLOAT;"`
EventDist float32 `gorm:"type:FLOAT;"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
// TableName returns Event table identifier "events"
func (Event) TableName() string {
return "events"
}
// BeforeCreate computes a random UUID when a new event is created in database
func (e *Event) BeforeCreate(scope *gorm.Scope) error {
return scope.SetColumn("EventUUID", rnd.PPID('e'))
}

View file

@ -1,14 +0,0 @@
package entity
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEvent_TableName(t *testing.T) {
event := &Event{EventSlug: "christmas-2000"}
tableName := event.TableName()
assert.Equal(t, "events", tableName)
}

View file

@ -45,21 +45,22 @@ func (m *Location) Find(db *gorm.DB, api string) error {
return err
}
if place := FindPlaceByLabel(l.ID, l.LocLabel, db); place != nil {
if place := FindPlaceByLabel(l.S2Token(), l.Label(), db); place != nil {
m.Place = place
} else {
m.Place = &Place{
ID: l.ID,
LocLabel: l.LocLabel,
LocCity: l.LocCity,
LocState: l.LocState,
LocCountry: l.LocCountry,
ID: l.S2Token(),
LocLabel: l.Label(),
LocCity: l.City(),
LocState: l.State(),
LocCountry: l.CountryCode(),
LocKeywords: l.KeywordString(),
}
}
m.LocName = l.LocName
m.LocCategory = l.LocCategory
m.LocSource = l.LocSource
m.LocName = l.Name()
m.LocCategory = l.Category()
m.LocSource = l.Source()
if err := db.Create(m).Error; err == nil {
return nil
@ -72,11 +73,17 @@ func (m *Location) Find(db *gorm.DB, api string) error {
// Keywords computes keyword based on a Location
func (m *Location) Keywords() (result []string) {
if m.Place == nil {
log.Errorf("location: place for %s is nil - you might have found a bug", m.ID)
return result
}
result = append(result, txt.Keywords(txt.ReplaceSpaces(m.City(), "-"))...)
result = append(result, txt.Keywords(txt.ReplaceSpaces(m.State(), "-"))...)
result = append(result, txt.Keywords(txt.ReplaceSpaces(m.CountryName(), "-"))...)
result = append(result, txt.Keywords(m.Category())...)
result = append(result, txt.Keywords(m.Name())...)
result = append(result, txt.Keywords(m.Place.LocKeywords)...)
result = txt.UniqueWords(result)

View file

@ -455,8 +455,6 @@ func (m *Photo) SetTakenAt(taken, local time.Time, zone, source string) {
if zone != "" {
m.TimeZone = zone
} else {
m.TimeZone = time.UTC.String()
}
}

View file

@ -11,10 +11,11 @@ import (
// Place used to associate photos to places
type Place struct {
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"`
LocLabel string `gorm:"type:varbinary(512);unique_index;"`
LocCity string `gorm:"type:varchar(128);"`
LocState string `gorm:"type:varchar(128);"`
LocLabel string `gorm:"type:varbinary(768);unique_index;"`
LocCity string `gorm:"type:varchar(255);"`
LocState string `gorm:"type:varchar(255);"`
LocCountry string `gorm:"type:varbinary(2);"`
LocKeywords string `gorm:"type:varchar(255);"`
LocNotes string `gorm:"type:text;"`
LocFavorite bool
CreatedAt time.Time
@ -24,11 +25,14 @@ type Place struct {
// UnknownPlace is defined here to use it as a default
var UnknownPlace = Place{
ID: "zz",
LocLabel: "Unknown",
LocCity: "Unknown",
LocState: "Unknown",
LocCountry: "zz",
ID: "zz",
LocLabel: "Unknown",
LocCity: "Unknown",
LocState: "Unknown",
LocCountry: "zz",
LocKeywords: "",
LocNotes: "",
LocFavorite: false,
}
// CreateUnknownPlace initializes default place in the database

View file

@ -32,6 +32,7 @@ type Location struct {
LocState string
LocCountry string
LocSource string
LocKeywords []string
}
type LocationSource interface {
@ -42,9 +43,10 @@ type LocationSource interface {
City() string
State() string
Source() string
Keywords() []string
}
func NewLocation(id string, name string, category string, label string, city string, state string, country string, source string) *Location {
func NewLocation(id, name, category, label, city, state, country, source string, keywords []string) *Location {
result := &Location{
ID: id,
LocName: name,
@ -54,6 +56,7 @@ func NewLocation(id string, name string, category string, label string, city str
LocState: state,
LocCountry: country,
LocSource: source,
LocKeywords: keywords,
}
return result
@ -84,6 +87,7 @@ func (l *Location) QueryPlaces() error {
l.LocCountry = s.CountryCode()
l.LocCategory = s.Category()
l.LocLabel = s.Label()
l.LocKeywords = s.Keywords()
return nil
}
@ -114,6 +118,7 @@ func (l *Location) Assign(s LocationSource) error {
l.LocCountry = s.CountryCode()
l.LocCategory = s.Category()
l.LocLabel = l.label()
l.LocKeywords = s.Keywords()
return nil
}
@ -147,6 +152,10 @@ func (l *Location) label() string {
return strings.Join(loc[:], ", ")
}
func (l Location) S2Token() string {
return l.ID
}
func (l Location) Name() string {
return l.LocName
}
@ -178,3 +187,11 @@ func (l Location) CountryName() string {
func (l Location) Source() string {
return l.LocSource
}
func (l Location) Keywords() []string {
return l.LocKeywords
}
func (l Location) KeywordString() string {
return strings.Join(l.LocKeywords, ", ")
}

View file

@ -15,18 +15,42 @@ func TestLocation_QueryPlaces(t *testing.T) {
lng := 13.40806264572578
id := s2.Token(lat, lng)
l := NewLocation(id, "", "", "", "", "", "", "")
l := NewLocation(id, "", "", "", "", "", "", "", []string{})
if err := l.QueryPlaces(); err != nil {
t.Fatal(err)
}
assert.Equal(t, "Alt-Berlin", l.LocName)
assert.Equal(t, "Berlin, Germany", l.LocLabel)
})
}
func TestLocation_Assign(t *testing.T) {
t.Run("Italy", func(t *testing.T) {
id := "47786b2bed37"
o, err := places.FindLocation(id)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "Comici I", o.Name())
assert.Equal(t, "Trentino-Alto Adige", o.State())
assert.Equal(t, "it", o.CountryCode())
var l Location
if err := l.Assign(o); err != nil {
t.Fatal(err)
}
assert.Equal(t, "Comici I", l.LocName)
assert.Equal(t, "Plan de Gralba, Trentino-Alto Adige, Italy", l.LocLabel)
assert.IsType(t, []string{}, l.Keywords())
assert.Equal(t, "südtirol", l.KeywordString())
})
t.Run("BerlinFernsehturm", func(t *testing.T) {
lat := 52.5208
lng := 13.40953
@ -50,6 +74,8 @@ func TestLocation_Assign(t *testing.T) {
assert.Equal(t, "Fernsehturm Berlin", l.LocName)
assert.Equal(t, "Berlin, Germany", l.LocLabel)
assert.IsType(t, []string{}, l.Keywords())
assert.Equal(t, "", l.KeywordString())
})
t.Run("SantaMonica", func(t *testing.T) {
@ -167,7 +193,7 @@ func TestLocation_Assign(t *testing.T) {
assert.Equal(t, "Indian Ocean", l.LocName)
assert.Equal(t, "", l.LocCategory)
assert.Equal(t, "", l.LocCity)
assert.Equal(t, "Unknown", l.LocCity)
// TODO: Should be zz for international waters, fixed in places server
// assert.Equal(t, "", l.LocCountry)
})
@ -179,7 +205,7 @@ func TestLocation_Unknown(t *testing.T) {
lng := 0.0
id := s2.Token(lat, lng)
l := NewLocation(id, "", "", "", "", "", "", "")
l := NewLocation(id, "", "", "", "", "", "", "", []string{})
assert.Equal(t, true, l.Unknown())
})
@ -188,7 +214,7 @@ func TestLocation_Unknown(t *testing.T) {
lng := 29.148046666666666
id := s2.Token(lat, lng)
l := NewLocation(id, "", "", "", "", "", "", "")
l := NewLocation(id, "", "", "", "", "", "", "", []string{})
assert.Equal(t, false, l.Unknown())
})
@ -200,12 +226,12 @@ func TestLocation_place(t *testing.T) {
lng := 0.0
id := s2.Token(lat, lng)
l := NewLocation(id, "", "", "", "", "", "", "")
l := NewLocation(id, "", "", "", "", "", "", "", []string{})
assert.Equal(t, "Unknown", l.label())
})
t.Run("Nürnberg, Bayern, Germany", func(t *testing.T) {
l := NewLocation("", "", "", "", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "", "", "", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "Unknown", l.label())
})
@ -213,7 +239,7 @@ func TestLocation_place(t *testing.T) {
func TestLocation_Name(t *testing.T) {
t.Run("Christkindlesmarkt", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "Christkindlesmarkt", l.Name())
})
@ -221,7 +247,7 @@ func TestLocation_Name(t *testing.T) {
func TestLocation_City(t *testing.T) {
t.Run("Nürnberg", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "Nürnberg", l.City())
})
@ -229,7 +255,7 @@ func TestLocation_City(t *testing.T) {
func TestLocation_State(t *testing.T) {
t.Run("Bayern", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "Bayern", l.State())
})
@ -237,7 +263,7 @@ func TestLocation_State(t *testing.T) {
func TestLocation_Category(t *testing.T) {
t.Run("test", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "test", "", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "test", "", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "test", l.Category())
})
@ -245,7 +271,7 @@ func TestLocation_Category(t *testing.T) {
func TestLocation_Source(t *testing.T) {
t.Run("source", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "source")
l := NewLocation("", "Christkindlesmarkt", "", "", "Nürnberg", "Bayern", "de", "source", []string{})
assert.Equal(t, "source", l.Source())
})
@ -253,7 +279,7 @@ func TestLocation_Source(t *testing.T) {
func TestLocation_Place(t *testing.T) {
t.Run("test-label", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "", "test-label", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "", "test-label", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "test-label", l.Label())
})
@ -261,7 +287,7 @@ func TestLocation_Place(t *testing.T) {
func TestLocation_CountryCode(t *testing.T) {
t.Run("de", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "test", "test-label", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "test", "test-label", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "de", l.CountryCode())
})
@ -269,14 +295,14 @@ func TestLocation_CountryCode(t *testing.T) {
func TestLocation_CountryName(t *testing.T) {
t.Run("Germany", func(t *testing.T) {
l := NewLocation("", "Christkindlesmarkt", "test", "test-label", "Nürnberg", "Bayern", "de", "")
l := NewLocation("", "Christkindlesmarkt", "test", "test-label", "Nürnberg", "Bayern", "de", "", []string{})
assert.Equal(t, "Germany", l.CountryName())
})
}
func TestLocation_QueryApi(t *testing.T) {
l := NewLocation("3", "Christkindlesmarkt", "test", "test-label", "Nürnberg", "Bayern", "de", "")
l := NewLocation("3", "Christkindlesmarkt", "test", "test-label", "Nürnberg", "Bayern", "de", "", []string{})
t.Run("xxx", func(t *testing.T) {
api := l.QueryApi("xxx")
assert.Error(t, api, "maps: reverse lookup disabled")

View file

@ -10,7 +10,6 @@ import (
"github.com/melihmucuk/geocache"
"github.com/photoprism/photoprism/pkg/s2"
"github.com/photoprism/photoprism/pkg/txt"
)
type Location struct {
@ -123,7 +122,7 @@ func (l Location) CountryCode() (result string) {
}
func (l Location) Keywords() (result []string) {
return txt.Keywords(l.LocDisplayName)
return result
}
func (l Location) Source() string {

View file

@ -26,7 +26,7 @@ type Location struct {
var ReverseLookupURL = "https://places.photoprism.org/v1/location/%s"
var client = &http.Client{Timeout: 30 * time.Second} // TODO: Change timeout if needed
func NewLocation(id string, lat float64, lng float64, name string, category string, place Place, cached bool) *Location {
func NewLocation(id string, lat, lng float64, name, category string, place Place, cached bool) *Location {
result := &Location{
ID: id,
LocLat: lat,
@ -68,7 +68,15 @@ func FindLocation(id string) (result Location, err error) {
return result, err
}
r, err := client.Do(req)
var r *http.Response
for i := 0; i < 3; i++ {
r, err = client.Do(req)
if err == nil {
break
}
}
if err != nil {
log.Errorf("places: %s", err.Error())
@ -135,7 +143,7 @@ func (l Location) Longitude() (result float64) {
}
func (l Location) Keywords() (result []string) {
return txt.Keywords(l.Label())
return txt.UniqueKeywords(l.Place.LocKeywords)
}
func (l Location) Source() string {

View file

@ -20,7 +20,6 @@ func TestFindLocation(t *testing.T) {
}
assert.False(t, l.Cached)
assert.Equal(t, "Alt-Berlin", l.Name())
assert.Equal(t, "Berlin", l.City())
assert.Equal(t, "de", l.CountryCode())
})
@ -35,7 +34,7 @@ func TestFindLocation(t *testing.T) {
t.Log(l)
})
t.Run("cached true", func(t *testing.T) {
var p = NewPlace("1", "", "", "", "de")
var p = NewPlace("1", "", "", "", "de", "")
location := NewLocation("54", 52.51961810676184, 13.40806264572578, "TestLocation", "test", p, true)
l, err := FindLocation(location.ID)
if err != nil {
@ -52,7 +51,7 @@ func TestFindLocation(t *testing.T) {
}
func TestLocationGetters(t *testing.T) {
var p = NewPlace("1", "testLabel", "berlin", "berlin", "de")
var p = NewPlace("1", "testLabel", "berlin", "berlin", "de", "foobar")
location := NewLocation("54", 52.51961810676184, 13.40806264572578, "TestLocation", "test", p, true)
t.Run("wrong id", func(t *testing.T) {
assert.Equal(t, "54", location.CellID())
@ -65,7 +64,7 @@ func TestLocationGetters(t *testing.T) {
assert.Equal(t, 52.51961810676184, location.Latitude())
assert.Equal(t, 13.40806264572578, location.Longitude())
assert.Equal(t, "places", location.Source())
assert.Equal(t, []string{"testlabel"}, location.Keywords())
assert.Equal(t, []string{"foobar"}, location.Keywords())
})
}

View file

@ -2,20 +2,22 @@ package places
// Place
type Place struct {
PlaceID string `json:"id"`
LocLabel string `json:"label"`
LocCity string `json:"city"`
LocState string `json:"state"`
LocCountry string `json:"country"`
PlaceID string `json:"id"`
LocLabel string `json:"label"`
LocCity string `json:"city"`
LocState string `json:"state"`
LocCountry string `json:"country"`
LocKeywords string `json:"keywords"`
}
func NewPlace(id string, label string, city string, state string, country string) Place {
func NewPlace(id, label, city, state, country, keywords string) Place {
result := Place{
PlaceID: id,
LocLabel: label,
LocCity: city,
LocState: state,
LocCountry: country,
PlaceID: id,
LocLabel: label,
LocCity: city,
LocState: state,
LocCountry: country,
LocKeywords: keywords,
}
return result

View file

@ -201,7 +201,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
}
if photo.TakenAt.IsZero() || photo.TakenAtLocal.IsZero() {
photo.SetTakenAt(m.DateCreated(), m.DateCreated(), time.UTC.String(), entity.SrcAuto)
photo.SetTakenAt(m.DateCreated(), m.DateCreated(), "", entity.SrcAuto)
}
if fileChanged || o.UpdateKeywords || o.UpdateLocation || o.UpdateTitle || photo.NoTitle() {

View file

@ -43,12 +43,12 @@ func TestFileType_Find(t *testing.T) {
result := TypeJpeg.Find("testdata/test (2).xmp", true)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("prefixUpper", func(t *testing.T) {
result := TypeJpeg.Find("testdata/catyellow.xmp", true)
t.Run("name upper", func(t *testing.T) {
result := TypeJpeg.Find("testdata/CATYELLOW.xmp", true)
assert.Equal(t, "testdata/CATYELLOW.jpg", result)
})
t.Run("prefixLower", func(t *testing.T) {
result := TypeJpeg.Find("testdata/CHAMELEON_LIME.xmp", true)
t.Run("name lower", func(t *testing.T) {
result := TypeJpeg.Find("testdata/chameleon_lime.xmp", true)
assert.Equal(t, "testdata/chameleon_lime.jpg", result)
})
}

View file

@ -12,6 +12,8 @@ photos
import
export
abc
val
tmp
xyz
jpg
jpeg

View file

@ -17,6 +17,8 @@ var Stopwords = map[string]bool{
"import": true,
"export": true,
"abc": true,
"val": true,
"tmp": true,
"xyz": true,
"jpg": true,
"jpeg": true,