Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
b8416f3d04
37 changed files with 305 additions and 266 deletions
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -102,7 +102,7 @@ class Photo extends RestModel {
|
|||
refreshFileAttr() {
|
||||
const file = this.mainFile();
|
||||
|
||||
if (!file) {
|
||||
if (!file || !file.FileHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
10
internal/config/testdata/config.yml
vendored
10
internal/config/testdata/config.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ", ")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ photos
|
|||
import
|
||||
export
|
||||
abc
|
||||
val
|
||||
tmp
|
||||
xyz
|
||||
jpg
|
||||
jpeg
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue