Merge branch 'develop'

This commit is contained in:
Michael Mayer 2020-05-14 19:04:35 +02:00
commit 2516463517
44 changed files with 1101 additions and 226 deletions

View file

@ -54,6 +54,9 @@ class Config {
const type = ev.split(".")[1];
switch (type) {
case "videos":
this.values.count.videos += data.count;
break;
case "favorites":
this.values.count.favorites += data.count;
break;

View file

@ -55,9 +55,7 @@
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Photos</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Photos') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -65,37 +63,44 @@
<v-list-tile slot="activator" to="/photos" @click.stop="" class="p-navigation-photos">
<v-list-tile-content>
<v-list-tile-title>
<translate>Photos</translate>
<translate>{{ $gettext('Photos') }}</translate>
<span v-if="config.count.photos > 0" class="p-navigation-count">{{ config.count.photos }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile :to="{name: 'photos', query: { q: 'mono:true quality:3' }}" :exact="true" @click="">
<v-list-tile :to="{name: 'photos', query: { q: 'mono:true quality:3 photo:true' }}" :exact="true" @click="">
<v-list-tile-content>
<v-list-tile-title>
<translate>Monochrome</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Monochrome') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/review" @click="" v-if="$config.feature('review')">
<v-list-tile-content>
<v-list-tile-title>
<translate>Review</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Review') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/archive" @click="" class="p-navigation-archive" v-if="$config.feature('archive')">
<v-list-tile to="/archive" @click="" class="p-navigation-archive" v-show="$config.feature('archive')">
<v-list-tile-content>
<v-list-tile-title>
<translate>Archive</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Archive') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list-group>
<v-list-tile to="/videos" @click="" class="p-navigation-video">
<v-list-tile-action>
<v-icon>slideshow</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<span>{{ $gettext('Videos') }}</span>
<span v-show="config.count.videos > 0" class="p-navigation-count">{{ config.count.videos }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/favorites" @click="" class="p-navigation-favorites">
<v-list-tile-action>
<v-icon>favorite</v-icon>
@ -103,21 +108,21 @@
<v-list-tile-content>
<v-list-tile-title>
<translate>Favorites</translate>
<span v-if="config.count.favorites > 0" class="p-navigation-count">{{ config.count.favorites }}</span>
<span>{{ $gettext('Favorites') }}</span>
<span v-show="config.count.favorites > 0" class="p-navigation-count">{{ config.count.favorites }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/private" @click="" class="p-navigation-private" v-if="$config.feature('private')" >
<v-list-tile to="/private" @click="" class="p-navigation-private" v-show="$config.feature('private')" >
<v-list-tile-action>
<v-icon>lock</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<span v-translate>Private</span>
<span v-if="config.count.private > 0" class="p-navigation-count">{{ config.count.private }}</span>
<span>{{ $gettext('Private') }}</span>
<span v-show="config.count.private > 0" class="p-navigation-count">{{ config.count.private }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -129,7 +134,7 @@
<v-list-tile-content>
<v-list-tile-title>
<translate>Albums</translate>
<span>{{ $gettext('Albums') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -138,7 +143,7 @@
<v-list-tile slot="activator" to="/albums" @click.stop="">
<v-list-tile-content>
<v-list-tile-title>
<translate>Albums</translate>
<span>{{ $gettext('Albums') }}</span>
<span v-if="config.count.albums > 0" class="p-navigation-count">{{ config.count.albums }}</span>
</v-list-tile-title>
</v-list-tile-content>
@ -154,36 +159,36 @@
</v-list-tile>
</v-list-group>
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-if="$config.feature('labels')">
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-show="$config.feature('labels')">
<v-list-tile-action>
<v-icon>label</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Labels</translate>
<span v-if="config.count.labels > 0"
<span>{{ $gettext('Labels') }}</span>
<span v-show="config.count.labels > 0"
class="p-navigation-count">{{ config.count.labels }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile :to="{ name: 'places' }" @click="" class="p-navigation-places"
v-if="$config.feature('places')">
v-show="$config.feature('places')">
<v-list-tile-action>
<v-icon>place</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Places</translate>
<span v-if="config.count.places > 0"
<span>{{ $gettext('Places') }}</span>
<span v-show="config.count.places > 0"
class="p-navigation-count">{{ config.count.places }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<!-- v-list-tile to="/discover" @click="" class="p-navigation-discover" v-if="config.experimental">
<!-- v-list-tile to="/discover" @click="" class="p-navigation-discover" v-show="config.experimental">
<v-list-tile-action>
<v-icon>color_lens</v-icon>
</v-list-tile-action>
@ -222,43 +227,43 @@
<v-list-tile-content>
<v-list-tile-title>
<translate>Library</translate>
<span>{{ $gettext('Library') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/settings" @click="" class="p-navigation-settings" v-if="!config.disableSettings">
<v-list-tile to="/settings" @click="" class="p-navigation-settings" v-show="!config.disableSettings">
<v-list-tile-action>
<v-icon>settings</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Settings</translate>
<span>{{ $gettext('Settings') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="logout" class="p-navigation-logout" v-if="!public && auth">
<v-list-tile @click="logout" class="p-navigation-logout" v-show="!public && auth">
<v-list-tile-action>
<v-icon>power_settings_new</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Logout</translate>
<span>{{ $gettext('Logout') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/login" @click="" class="p-navigation-login" v-if="!auth">
<v-list-tile to="/login" @click="" class="p-navigation-login" v-show="!auth">
<v-list-tile-action>
<v-icon>lock</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Login</translate>
<span>{{ $gettext('Login') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>

View file

@ -68,12 +68,12 @@
<v-icon v-else color="accent lighten-3" class="t-like t-off">favorite_border</v-icon>
</v-btn>
<v-btn v-if="photo.PhotoVideo" color="white" :ripple="false"
<v-btn v-if="photo.PhotoVideo && photo.isMP4()" color="white" :ripple="false"
outline large fab absolute class="p-photo-play opacity-75" :depressed="false"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-play">play_arrow</v-icon>
</v-btn>
<v-btn v-else-if="photo.Files.length > 1" :ripple="false"
<v-btn v-else-if="!photo.PhotoVideo && photo.Files.length > 1" :ripple="false"
icon flat large absolute class="p-photo-merged opacity-75"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-burst">burst_mode</v-icon>

View file

@ -32,7 +32,7 @@
flat icon large absolute class="p-photo-select">
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
</v-btn>
<v-btn v-else-if="!selection.length && props.item.PhotoVideo" :ripple="false"
<v-btn v-else-if="!selection.length && props.item.PhotoVideo && props.item.isMP4()" :ripple="false"
flat icon large absolute class="p-photo-play opacity-75"
@click.stop.prevent="openPhoto(props.index, true)">
<v-icon color="white" class="action-play">play_arrow</v-icon>
@ -144,7 +144,7 @@
} else if(this.photos[index]) {
let photo = this.photos[index];
if(photo.PhotoVideo) {
if(photo.PhotoVideo && photo.isMP4()) {
this.openPhoto(index, true);
} else {
this.openPhoto(index, false);

View file

@ -67,12 +67,12 @@
<v-icon v-else color="accent lighten-3" class="t-like t-off">favorite_border</v-icon>
</v-btn>
<v-btn v-if="photo.PhotoVideo" color="white"
<v-btn v-if="photo.PhotoVideo && photo.isMP4()" color="white"
outline fab absolute class="p-photo-play opacity-75" :depressed="false" :ripple="false"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-play">play_arrow</v-icon>
</v-btn>
<v-btn v-else-if="photo.Files.length > 1" :ripple="false"
<v-btn v-else-if="!photo.PhotoVideo && photo.Files.length > 1" :ripple="false"
icon flat small absolute class="p-photo-merged opacity-75"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-burst">burst_mode</v-icon>

View file

@ -105,6 +105,14 @@ class Photo extends RestModel {
this.FileHeight = file.FileHeight;
}
isMP4() {
if (!this.Files) {
return false;
}
return this.Files.findIndex(f => f.FileType === "mp4") !== -1;
}
videoFile() {
if (!this.Files) {
return false;

View file

@ -142,7 +142,11 @@
}
if (showMerged && this.results[index].PhotoVideo) {
Event.publish("dialog.video", {play: this.results[index], album: null});
if(this.results[index].isMP4()) {
Event.publish("dialog.video", {play: this.results[index], album: this.album});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([this.results[index]]), 0)
} else {

View file

@ -115,16 +115,16 @@
computed: {
context: function () {
if (!this.staticFilter) {
return "photos"
return "photos";
}
if (this.staticFilter.archived) {
return "archive"
} else if (this.staticFilter.favorites) {
return "favorites"
return "archive";
} else if (this.staticFilter.favorite) {
return "favorites";
}
return ""
return "";
}
},
methods: {
@ -179,7 +179,11 @@
}
if (showMerged && this.results[index].PhotoVideo) {
Event.publish("dialog.video", {play: this.results[index], album: null});
if(this.results[index].isMP4()) {
Event.publish("dialog.video", {play: this.results[index], album: null});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([this.results[index]]), 0)
} else {

View file

@ -49,7 +49,14 @@ export default [
path: "/favorites",
component: Photos,
meta: {title: "Favorites", auth: true},
props: {staticFilter: {favorites: true}},
props: {staticFilter: {favorite: true}},
},
{
name: "videos",
path: "/videos",
component: Photos,
meta: {title: "Videos", auth: true},
props: {staticFilter: {video: true}},
},
{
name: "review",

View file

@ -0,0 +1,29 @@
package api
import (
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"net/http"
"testing"
)
func TestGetImportOptions(t *testing.T) {
t.Run("successful request", func(t *testing.T) {
app, router, conf := NewApiTest()
GetImportOptions(router, conf)
r := PerformRequest(app, "GET", "/api/v1/import")
assert.True(t, gjson.Get(r.Body.String(), "dirs").IsArray())
assert.Equal(t, http.StatusOK, r.Code)
})
}
func TestCancelImport(t *testing.T) {
t.Run("successful request", func(t *testing.T) {
app, router, conf := NewApiTest()
CancelImport(router, conf)
r := PerformRequest(app, "DELETE", "/api/v1/import")
val := gjson.Get(r.Body.String(), "message")
assert.Equal(t, "import canceled", val.String())
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -0,0 +1,29 @@
package api
import (
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"net/http"
"testing"
)
func TestGetIndexingOptions(t *testing.T) {
t.Run("successful request", func(t *testing.T) {
app, router, conf := NewApiTest()
GetIndexingOptions(router, conf)
r := PerformRequest(app, "GET", "/api/v1/index")
assert.True(t, gjson.Get(r.Body.String(), "dirs").IsArray())
assert.Equal(t, http.StatusOK, r.Code)
})
}
func TestCancelIndex(t *testing.T) {
t.Run("successful request", func(t *testing.T) {
app, router, conf := NewApiTest()
CancelIndexing(router, conf)
r := PerformRequest(app, "DELETE", "/api/v1/index")
val := gjson.Get(r.Body.String(), "message")
assert.Equal(t, "indexing canceled", val.String())
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -64,14 +64,17 @@ func TestLikeLabel(t *testing.T) {
t.Run("like existing label", func(t *testing.T) {
app, router, ctx := NewApiTest()
GetLabels(router, ctx)
r2 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=updatelabel")
val := gjson.Get(r2.Body.String(), `#(LabelSlug=="updatelabel").LabelFavorite`)
r2 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=likeLabel")
t.Log(r2.Body.String())
val := gjson.Get(r2.Body.String(), `#(LabelSlug=="likeLabel").LabelFavorite`)
assert.Equal(t, "false", val.String())
LikeLabel(router, ctx)
r := PerformRequest(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c7/like")
r := PerformRequest(app, "POST", "/api/v1/labels/lt9k3pw1wowuy3c9/like")
t.Log(r.Body.String())
assert.Equal(t, http.StatusOK, r.Code)
r3 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=updatelabel")
val2 := gjson.Get(r3.Body.String(), `#(LabelSlug=="updatelabel").LabelFavorite`)
r3 := PerformRequest(app, "GET", "/api/v1/labels?count=1&q=likeLabel")
t.Log(r3.Body.String())
val2 := gjson.Get(r3.Body.String(), `#(LabelSlug=="likeLabel").LabelFavorite`)
assert.Equal(t, "true", val2.String())
})

View file

@ -26,7 +26,7 @@ import (
// offset: int Result offset
// 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
// favorite: bool Find favorites only
func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
router.GET("/photos", func(c *gin.Context) {
if Unauthorized(c, conf) {

View file

@ -0,0 +1,34 @@
package api
import (
"github.com/stretchr/testify/assert"
"net/http"
"testing"
)
func TestGetVideo(t *testing.T) {
t.Run("invalid hash", func(t *testing.T) {
app, router, conf := NewApiTest()
GetVideo(router, conf)
r := PerformRequest(app, "GET", "/api/v1/videos/xxx/mp4")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("invalid type", func(t *testing.T) {
app, router, conf := NewApiTest()
GetVideo(router, conf)
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd831/xxx")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("file for video not found", func(t *testing.T) {
app, router, conf := NewApiTest()
GetVideo(router, conf)
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd831/mp4")
assert.Equal(t, http.StatusOK, r.Code)
})
t.Run("file with error", func(t *testing.T) {
app, router, conf := NewApiTest()
GetVideo(router, conf)
r := PerformRequest(app, "GET", "/api/v1/videos/acad9168fa6acc5c5c2965ddf6ec465ca42fd832/mp4")
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -0,0 +1,28 @@
package api
import (
"github.com/stretchr/testify/assert"
"net/http"
"testing"
)
func TestWebsocket(t *testing.T) {
t.Run("bad request", func(t *testing.T) {
app, router, conf := NewApiTest()
Websocket(router, conf)
r := PerformRequest(app, "GET", "/api/v1/ws")
assert.Equal(t, http.StatusBadRequest, r.Code)
})
t.Run("router nil", func(t *testing.T) {
app, _, conf := NewApiTest()
Websocket(nil, conf)
r := PerformRequest(app, "GET", "/api/v1/ws")
assert.Equal(t, http.StatusNotFound, r.Code)
})
t.Run("conf nil", func(t *testing.T) {
app, router, _ := NewApiTest()
Websocket(router, nil)
r := PerformRequest(app, "GET", "/api/v1/ws")
assert.Equal(t, http.StatusNotFound, r.Code)
})
}

View file

@ -59,6 +59,7 @@ func (c *Config) PublicClientConfig() ClientConfig {
var count = struct {
Photos uint `json:"photos"`
Videos uint `json:"videos"`
Hidden uint `json:"hidden"`
Favorites uint `json:"favorites"`
Private uint `json:"private"`
@ -132,6 +133,7 @@ func (c *Config) ClientConfig() ClientConfig {
var count = struct {
Photos uint `json:"photos"`
Videos uint `json:"videos"`
Hidden uint `json:"hidden"`
Favorites uint `json:"favorites"`
Private uint `json:"private"`
@ -143,7 +145,7 @@ func (c *Config) ClientConfig() ClientConfig {
}{}
db.Table("photos").
Select("SUM(photo_quality = -1) AS hidden, SUM(photo_quality >= 0) AS photos, SUM(photo_favorite) AS favorites, SUM(photo_private) AS private").
Select("SUM(photo_video = 1 AND photo_quality >= 0) AS videos, SUM(photo_quality = -1) AS hidden, SUM(photo_quality >= 0) AS photos, SUM(photo_favorite) AS favorites, SUM(photo_private) AS private").
Where("deleted_at IS NULL").
Take(&count)

View file

@ -193,7 +193,7 @@ var FileFixtures = map[string]File{
FileDiff: 800,
FileChroma: 4,
FileNotes: "",
FileError: "",
FileError: "Error",
Share: []FileShare{},
Sync: []FileSync{},
Links: []Link{},
@ -281,6 +281,123 @@ var FileFixtures = map[string]File{
UpdatedIn: 0,
DeletedAt: nil,
},
"Photo18.jpg": {
ID: 1000007,
Photo: nil, // no pointer here because related photo is deleted
PhotoID: 1000018,
PhotoUUID: "pt9jtdre2lvl0y25",
FileUUID: "ft6es39w45bnlqdw",
FileName: "Photo18.jpg",
OriginalName: "Photo18.jpg",
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd820",
FileModified: time.Date(2017, 1, 6, 2, 6, 51, 0, time.UTC),
FileSize: 500,
FileType: "jpg",
FileMime: "image/jpg",
FilePrimary: true,
FileSidecar: false,
FileVideo: false,
FileMissing: false,
FileDuplicate: false,
FilePortrait: false,
FileWidth: 1200,
FileHeight: 1600,
FileOrientation: 6,
FileAspectRatio: 0.75,
FileMainColor: "green",
FileColors: "266111000",
FileLuminance: "DC42844C8",
FileDiff: 800,
FileChroma: 0,
FileNotes: "",
FileError: "",
Share: []FileShare{},
Sync: []FileSync{},
Links: []Link{},
CreatedAt: time.Date(2018, 1, 1, 2, 6, 51, 0, time.UTC),
CreatedIn: 2,
UpdatedAt: time.Date(2029, 3, 28, 14, 6, 0, 0, time.UTC),
UpdatedIn: 0,
DeletedAt: nil,
},
"Video.mp4": {
ID: 1000008,
Photo: PhotoFixtures.Pointer("Photo10"),
PhotoID: 1000010,
PhotoUUID: "pt9jtdre2lvl0y17",
FileUUID: "ft71s39w45bnlqdw",
FileName: "Video.mp4",
OriginalName: "Video.mp4",
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd831",
FileModified: time.Date(2017, 1, 6, 2, 6, 51, 0, time.UTC),
FileSize: 500,
FileType: "mp4",
FileMime: "video/mp4",
FilePrimary: true,
FileSidecar: false,
FileVideo: true,
FileMissing: false,
FileDuplicate: false,
FilePortrait: false,
FileWidth: 1200,
FileHeight: 1600,
FileOrientation: 6,
FileAspectRatio: 0.75,
FileMainColor: "green",
FileColors: "266111000",
FileLuminance: "DC42844C8",
FileDiff: 800,
FileChroma: 0,
FileNotes: "",
FileError: "",
Share: []FileShare{},
Sync: []FileSync{},
Links: []Link{},
CreatedAt: time.Date(2018, 1, 1, 2, 6, 51, 0, time.UTC),
CreatedIn: 2,
UpdatedAt: time.Date(2029, 3, 28, 14, 6, 0, 0, time.UTC),
UpdatedIn: 0,
DeletedAt: nil,
},
"VideoWithError.mp4": {
ID: 1000009,
Photo: PhotoFixtures.Pointer("Photo10"),
PhotoID: 1000010,
PhotoUUID: "pt9jtdre2lvl0y17",
FileUUID: "ft72s39w45bnlqdw",
FileName: "VideoError.mp4",
OriginalName: "VideoError.mp4",
FileHash: "acad9168fa6acc5c5c2965ddf6ec465ca42fd832",
FileModified: time.Date(2017, 1, 6, 2, 6, 51, 0, time.UTC),
FileSize: 500,
FileType: "mp4",
FileMime: "video/mp4",
FilePrimary: false,
FileSidecar: false,
FileVideo: true,
FileMissing: false,
FileDuplicate: false,
FilePortrait: false,
FileWidth: 1200,
FileHeight: 1600,
FileOrientation: 6,
FileAspectRatio: 0.75,
FileMainColor: "green",
FileColors: "266111000",
FileLuminance: "DC42844C8",
FileDiff: 800,
FileChroma: 0,
FileNotes: "",
FileError: "Error",
Share: []FileShare{},
Sync: []FileSync{},
Links: []Link{},
CreatedAt: time.Date(2018, 1, 1, 2, 6, 51, 0, time.UTC),
CreatedIn: 2,
UpdatedAt: time.Date(2029, 3, 28, 14, 6, 0, 0, time.UTC),
UpdatedIn: 0,
DeletedAt: nil,
},
}
var FileFixturesExampleJPG = FileFixtures["exampleFileName.jpg"]

View file

@ -27,7 +27,7 @@ var FileShareFixtures = FileShareMap{
FileID: 1000000,
AccountID: 1000000,
RemoteName: "name for remote",
Status: "test",
Status: FileShareShared,
Error: "",
Errors: 0,
File: nil,
@ -39,7 +39,7 @@ var FileShareFixtures = FileShareMap{
FileID: 1000000,
AccountID: 1000001,
RemoteName: "name for remote",
Status: "test",
Status: FileShareNew,
Error: "",
Errors: 0,
File: nil,

View file

@ -0,0 +1,36 @@
package entity
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestFileShareMap_Get(t *testing.T) {
t.Run("get existing fileshare", func(t *testing.T) {
r := FileShareFixtures.Get("FileShare1", 0, 0, "")
assert.Equal(t, uint(1000000), r.AccountID)
assert.Equal(t, "name for remote", r.RemoteName)
assert.IsType(t, FileShare{}, r)
})
t.Run("get not existing fileshare", func(t *testing.T) {
r := FileShareFixtures.Get("FileShareXXX", 123, 888, "new remote name")
assert.Equal(t, uint(888), r.AccountID)
assert.Equal(t, "new remote name", r.RemoteName)
assert.IsType(t, FileShare{}, r)
})
}
func TestFileShareMap_Pointer(t *testing.T) {
t.Run("get existing fileshare pointer", func(t *testing.T) {
r := FileShareFixtures.Pointer("FileShare1", 0, 0, "")
assert.Equal(t, uint(1000000), r.AccountID)
assert.Equal(t, "name for remote", r.RemoteName)
assert.IsType(t, &FileShare{}, r)
})
t.Run("get not existing fileshare pointer", func(t *testing.T) {
r := FileShareFixtures.Pointer("FileShareYYY", 456, 889, "new remote name for pointer")
assert.Equal(t, uint(889), r.AccountID)
assert.Equal(t, "new remote name for pointer", r.RemoteName)
assert.IsType(t, &FileShare{}, r)
})
}

View file

@ -0,0 +1,36 @@
package entity
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestFileSyncMap_Get(t *testing.T) {
t.Run("get existing filesync", func(t *testing.T) {
r := FileSyncFixtures.Get("FileSync1", 0, "")
assert.Equal(t, uint(1000000), r.AccountID)
assert.Equal(t, "name for remote sync", r.RemoteName)
assert.IsType(t, FileSync{}, r)
})
t.Run("get not existing filesync", func(t *testing.T) {
r := FileSyncFixtures.Get("FileSyncXXX", 123, "new remote name for sync")
assert.Equal(t, uint(123), r.AccountID)
assert.Equal(t, "new remote name for sync", r.RemoteName)
assert.IsType(t, FileSync{}, r)
})
}
func TestFileSyncMap_Pointer(t *testing.T) {
t.Run("get existing filesync pointer", func(t *testing.T) {
r := FileSyncFixtures.Pointer("FileSync1", 0, "")
assert.Equal(t, uint(1000000), r.AccountID)
assert.Equal(t, "name for remote sync", r.RemoteName)
assert.IsType(t, &FileSync{}, r)
})
t.Run("get not existing filesync pointer", func(t *testing.T) {
r := FileSyncFixtures.Pointer("FileSyncYYY", 456, "new remote name for sync pointer")
assert.Equal(t, uint(456), r.AccountID)
assert.Equal(t, "new remote name for sync pointer", r.RemoteName)
assert.IsType(t, &FileSync{}, r)
})
}

View file

@ -51,6 +51,7 @@ func TestFile_DownloadFileName(t *testing.T) {
}
func TestFile_Changed(t *testing.T) {
var deletedAt = time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC)
t.Run("different modified times", func(t *testing.T) {
file := &File{Photo: nil, FileType: "jpg", FileSize: 500, FileModified: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC)}
time := time.Date(2020, 01, 15, 0, 0, 0, 0, time.UTC)
@ -66,6 +67,11 @@ func TestFile_Changed(t *testing.T) {
time := time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC)
assert.Equal(t, false, file.Changed(500, time))
})
t.Run("deleted", func(t *testing.T) {
file := &File{Photo: nil, FileType: "jpg", FileSize: 500, FileModified: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), DeletedAt: &deletedAt}
time := time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC)
assert.Equal(t, true, file.Changed(500, time))
})
}
func TestFile_Purge(t *testing.T) {
@ -74,3 +80,28 @@ func TestFile_Purge(t *testing.T) {
assert.Equal(t, nil, file.Purge())
})
}
func TestFile_AllFilesMissing(t *testing.T) {
t.Run("true", func(t *testing.T) {
photo := &Photo{TakenAtLocal: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), PhotoTitle: ""}
file := &File{Photo: photo, FileType: "jpg", PhotoUUID: "123", FileUUID: "123", FileMissing: true}
file2 := &File{Photo: photo, FileType: "jpg", PhotoUUID: "123", FileUUID: "456", FileMissing: true}
assert.True(t, file.AllFilesMissing())
assert.NotEmpty(t, file2)
})
//TODO test false
/*t.Run("false", func(t *testing.T) {
file := FileFixturesExampleJPG
assert.False(t, file.AllFilesMissing())
assert.NotEmpty(t, file)
})*/
}
func TestFile_Save(t *testing.T) {
t.Run("record not found", func(t *testing.T) {
file := &File{Photo: nil, FileType: "jpg", PhotoUUID: "123", FileUUID: "123"}
err := file.Save()
assert.Equal(t, "record not found", err.Error())
})
//TODO test success
}

View file

@ -158,6 +158,24 @@ var LabelFixtures = LabelMap{
DeletedAt: nil,
New: false,
},
"likeLabel": {
ID: 1000007,
LabelUUID: "lt9k3pw1wowuy3c9",
LabelSlug: "likeLabel",
CustomSlug: "likeLabel",
LabelName: "likeLabel",
LabelPriority: 3,
LabelFavorite: false,
LabelDescription: "",
LabelNotes: "",
PhotoCount: 1,
LabelCategories: []*Label{},
Links: []Link{},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: nil,
New: false,
},
}
// CreateLabelFixtures inserts known entities into the database for testing.

View file

@ -0,0 +1,34 @@
package entity
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestLensMap_Get(t *testing.T) {
t.Run("get existing lens", func(t *testing.T) {
r := LensFixtures.Get("lens-f-380")
assert.Equal(t, uint(1000000), r.ID)
assert.Equal(t, "lens-f-380", r.LensSlug)
assert.IsType(t, Lens{}, r)
})
t.Run("get not existing lens", func(t *testing.T) {
r := LensFixtures.Get("Lens 123")
assert.Equal(t, "lens-123", r.LensSlug)
assert.IsType(t, Lens{}, r)
})
}
func TestLensMap_Pointer(t *testing.T) {
t.Run("get existing lens pointer", func(t *testing.T) {
r := LensFixtures.Pointer("lens-f-380")
assert.Equal(t, uint(1000000), r.ID)
assert.Equal(t, "lens-f-380", r.LensSlug)
assert.IsType(t, &Lens{}, r)
})
t.Run("get not existing lens pointer", func(t *testing.T) {
r := LensFixtures.Pointer("Lens new")
assert.Equal(t, "lens-new", r.LensSlug)
assert.IsType(t, &Lens{}, r)
})
}

View file

@ -5,6 +5,7 @@ import (
)
var editTime = time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC)
var deleteTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
type PhotoMap map[string]Photo
@ -35,7 +36,7 @@ var PhotoFixtures = PhotoMap{
TitleSrc: "",
PhotoPath: "2790/02",
PhotoName: "19800101_000002_D640C559",
PhotoQuality: 3,
PhotoQuality: 4,
PhotoResolution: 2,
PhotoFavorite: false,
PhotoPrivate: false,
@ -65,7 +66,9 @@ var PhotoFixtures = PhotoMap{
Location: nil,
Place: PlaceFixtures.Pointer("teotihuacan"),
Links: []Link{},
Keywords: []Keyword{},
Keywords: []Keyword{
KeywordFixtures.Get("bridge"),
},
Albums: []Album{
AlbumFixtures.Get("holiday-2030"),
},
@ -295,7 +298,7 @@ var PhotoFixtures = PhotoMap{
PhotoQuality: 3,
PhotoResolution: 2,
PhotoFavorite: false,
PhotoPrivate: false,
PhotoPrivate: true,
PhotoVideo: false,
PhotoLat: -21.342636,
PhotoLng: 55.466944,
@ -913,11 +916,64 @@ var PhotoFixtures = PhotoMap{
Keywords: []Keyword{},
Albums: []Album{},
Files: []File{},
Labels: []PhotoLabel{LabelFixtures.PhotoLabel(10000015, "landscape", 20, "image")},
CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC),
UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
EditedAt: nil,
DeletedAt: nil,
Labels: []PhotoLabel{
LabelFixtures.PhotoLabel(10000015, "landscape", 20, "image"),
LabelFixtures.PhotoLabel(10000018, "likeLabel", 20, "image")},
CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC),
UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
EditedAt: nil,
DeletedAt: nil,
},
"Photo18": {
ID: 1000018,
PhotoUUID: "pt9jtdre2lvl0y25",
TakenAt: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
TakenAtLocal: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC),
TakenSrc: "",
PhotoTitle: "ArchivedChroma0",
TitleSrc: "",
PhotoPath: "",
PhotoName: "",
PhotoQuality: 0,
PhotoResolution: 0,
PhotoFavorite: true,
PhotoPrivate: false,
PhotoVideo: false,
PhotoLat: 1.234,
PhotoLng: 4.321,
PhotoAltitude: 3,
PhotoIso: 0,
PhotoFocalLength: 0,
PhotoFNumber: 0,
PhotoExposure: "",
CameraID: 0,
CameraSerial: "",
CameraSrc: "",
LensID: 0,
PlaceID: "",
LocationID: "",
LocationSrc: "location",
TimeZone: "",
PhotoCountry: "",
PhotoYear: 0,
PhotoMonth: 0,
Description: DescriptionFixtures.Get("lake", 1000015),
DescriptionSrc: "location",
Camera: CameraFixtures.Pointer("canon-eos-6d"),
Lens: LensFixtures.Pointer("lens-f-380"),
Location: &LocationFixturesMexico,
Place: PlaceFixtures.Pointer("teotihuacan"),
Links: []Link{},
Keywords: []Keyword{},
Albums: []Album{},
Files: []File{},
Labels: []PhotoLabel{
LabelFixtures.PhotoLabel(10000018, "landscape", 20, "image"),
LabelFixtures.PhotoLabel(10000018, "likeLabel", 20, "image")},
CreatedAt: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC),
UpdatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
EditedAt: nil,
DeletedAt: &deleteTime,
},
}

View file

@ -11,6 +11,10 @@ var PhotoKeywordFixtures = PhotoKeywordMap{
PhotoID: 1000001,
KeywordID: 1000001,
},
"3": {
PhotoID: 1000000,
KeywordID: 1000000,
},
}
// CreatePhotoKeywordFixtures inserts known entities into the database for testing.

View file

@ -2,14 +2,14 @@ package form
// AlbumSearch represents search form fields for "/api/v1/albums".
type AlbumSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
Favorites bool `form:"favorites"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
Favorite bool `form:"favorite"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
}
func (f *AlbumSearch) GetQuery() string {

View file

@ -16,7 +16,7 @@ func TestAlbumSearchForm(t *testing.T) {
func TestParseQueryStringAlbum(t *testing.T) {
t.Run("valid query", func(t *testing.T) {
form := &AlbumSearch{Query: "slug:album1 favorites:true count:10"}
form := &AlbumSearch{Query: "slug:album1 favorite:true count:10"}
err := form.ParseQueryString()
@ -27,11 +27,11 @@ func TestParseQueryStringAlbum(t *testing.T) {
}
assert.Equal(t, "album1", form.Slug)
assert.Equal(t, true, form.Favorites)
assert.Equal(t, true, form.Favorite)
assert.Equal(t, 10, form.Count)
})
t.Run("valid query 2", func(t *testing.T) {
form := &AlbumSearch{Query: "name:album1 favorites:false offset:100 order:newest query:\"query text\""}
form := &AlbumSearch{Query: "name:album1 favorite:false offset:100 order:newest query:\"query text\""}
err := form.ParseQueryString()
@ -42,7 +42,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
}
assert.Equal(t, "album1", form.Name)
assert.Equal(t, false, form.Favorites)
assert.Equal(t, false, form.Favorite)
assert.Equal(t, 100, form.Offset)
assert.Equal(t, "newest", form.Order)
assert.Equal(t, "query text", form.Query)
@ -74,7 +74,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
assert.Equal(t, "unknown filter: Xxx", err.Error())
})
t.Run("query for favorites with uncommon bool value", func(t *testing.T) {
form := &AlbumSearch{Query: "favorites:cat"}
form := &AlbumSearch{Query: "favorite:cat"}
err := form.ParseQueryString()
@ -82,7 +82,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
t.Fatal("err should be nil")
}
assert.True(t, form.Favorites)
assert.True(t, form.Favorite)
})
t.Run("query for count with invalid type", func(t *testing.T) {
form := &AlbumSearch{Query: "count:cat"}

View file

@ -2,15 +2,15 @@ package form
// PhotoSearch represents search form fields for "/api/v1/labels".
type LabelSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
All bool `form:"all"`
Favorites bool `form:"favorites"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
All bool `form:"all"`
Favorite bool `form:"favorite"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
}
func (f *LabelSearch) GetQuery() string {

View file

@ -16,7 +16,7 @@ func TestLabelSearchForm(t *testing.T) {
func TestParseQueryStringLabel(t *testing.T) {
t.Run("valid query", func(t *testing.T) {
form := &LabelSearch{Query: "name:cat favorites:true count:10 all:false query:\"query text\""}
form := &LabelSearch{Query: "name:cat favorite:true count:10 all:false query:\"query text\""}
err := form.ParseQueryString()
@ -26,13 +26,13 @@ func TestParseQueryStringLabel(t *testing.T) {
t.Fatal("err should be nil")
}
assert.Equal(t, "cat", form.Name)
assert.Equal(t, true, form.Favorites)
assert.Equal(t, true, form.Favorite)
assert.Equal(t, 10, form.Count)
assert.Equal(t, false, form.All)
assert.Equal(t, "query text", form.Query)
})
t.Run("valid query 2", func(t *testing.T) {
form := &LabelSearch{Query: "slug:cat favorites:false offset:2 order:oldest"}
form := &LabelSearch{Query: "slug:cat favorite:false offset:2 order:oldest"}
err := form.ParseQueryString()
@ -42,7 +42,7 @@ func TestParseQueryStringLabel(t *testing.T) {
t.Fatal("err should be nil")
}
assert.Equal(t, "cat", form.Slug)
assert.Equal(t, false, form.Favorites)
assert.Equal(t, false, form.Favorite)
assert.Equal(t, 2, form.Offset)
assert.Equal(t, "oldest", form.Order)
})
@ -73,7 +73,7 @@ func TestParseQueryStringLabel(t *testing.T) {
assert.Equal(t, "unknown filter: Xxx", err.Error())
})
t.Run("query for favorites with uncommon bool value", func(t *testing.T) {
form := &LabelSearch{Query: "favorites:0.99"}
form := &LabelSearch{Query: "favorite:0.99"}
err := form.ParseQueryString()
@ -81,7 +81,7 @@ func TestParseQueryStringLabel(t *testing.T) {
t.Fatal("err should be nil")
}
assert.True(t, form.Favorites)
assert.True(t, form.Favorite)
})
t.Run("query for count with invalid type", func(t *testing.T) {
form := &LabelSearch{Query: "count:2019-01-15"}

View file

@ -10,6 +10,8 @@ type PhotoSearch struct {
ID string `form:"id"`
Title string `form:"title"`
Hash string `form:"hash"`
Video bool `form:"video"`
Photo bool `form:"photo"`
Duplicate bool `form:"duplicate"`
Archived bool `form:"archived"`
Error bool `form:"error"`
@ -25,24 +27,24 @@ type PhotoSearch struct {
Location bool `form:"location"`
Album string `form:"album"`
Label string `form:"label"`
Country string `form:"country"`
Year uint `form:"year"`
Month uint `form:"month"`
Color string `form:"color"`
Quality int `form:"quality"`
Review bool `form:"review"`
Camera int `form:"camera"`
Lens int `form:"lens"`
Before time.Time `form:"before" time_format:"2006-01-02"`
After time.Time `form:"after" time_format:"2006-01-02"`
Favorites bool `form:"favorites"`
Public bool `form:"public"`
Private bool `form:"private"`
Safe bool `form:"safe"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Merged bool `form:"merged"`
Country string `form:"country"`
Year uint `form:"year"`
Month uint `form:"month"`
Color string `form:"color"`
Quality int `form:"quality"`
Review bool `form:"review"`
Camera int `form:"camera"`
Lens int `form:"lens"`
Before time.Time `form:"before" time_format:"2006-01-02"`
After time.Time `form:"after" time_format:"2006-01-02"`
Favorite bool `form:"favorite"`
Public bool `form:"public"`
Private bool `form:"private"`
Safe bool `form:"safe"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Merged bool `form:"merged"`
}
func (f *PhotoSearch) GetQuery() string {

View file

@ -18,7 +18,7 @@ func TestPhotoSearchForm(t *testing.T) {
func TestParseQueryString(t *testing.T) {
t.Run("valid query", func(t *testing.T) {
form := &PhotoSearch{Query: "label:cat query:\"fooBar baz\" before:2019-01-15 camera:23 favorites:false dist:25000 lat:33.45343166666667"}
form := &PhotoSearch{Query: "label:cat query:\"fooBar baz\" before:2019-01-15 camera:23 favorite:false dist:25000 lat:33.45343166666667"}
err := form.ParseQueryString()
@ -32,12 +32,12 @@ func TestParseQueryString(t *testing.T) {
assert.Equal(t, "foobar baz", form.Query)
assert.Equal(t, 23, form.Camera)
assert.Equal(t, time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), form.Before)
assert.Equal(t, false, form.Favorites)
assert.Equal(t, false, form.Favorite)
assert.Equal(t, uint(0x61a8), form.Dist)
assert.Equal(t, float32(33.45343), form.Lat)
})
t.Run("valid query 2", func(t *testing.T) {
form := &PhotoSearch{Query: "chroma:200 title:\"test\" after:2018-01-15 duplicate:false favorites:true lng:33.45343166666667"}
form := &PhotoSearch{Query: "chroma:200 title:\"test\" after:2018-01-15 duplicate:false favorite:true lng:33.45343166666667"}
err := form.ParseQueryString()
@ -80,7 +80,7 @@ func TestParseQueryString(t *testing.T) {
assert.Equal(t, "unknown filter: Xxx", err.Error())
})
t.Run("query for favorites with uncommon bool value", func(t *testing.T) {
form := &PhotoSearch{Query: "favorites:cat"}
form := &PhotoSearch{Query: "favorite:cat"}
err := form.ParseQueryString()
@ -88,7 +88,7 @@ func TestParseQueryString(t *testing.T) {
t.Fatal("err should NOT be nil")
}
assert.True(t, form.Favorites)
assert.True(t, form.Favorite)
})
t.Run("query for lat with invalid type", func(t *testing.T) {
form := &PhotoSearch{Query: "lat:cat"}

View file

@ -357,6 +357,12 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
})
}
if photo.PhotoVideo {
event.Publish("count.videos", event.Data{
"count": 1,
})
}
event.EntitiesCreated("photos", []entity.Photo{photo})
}

View file

@ -556,6 +556,11 @@ func (m *MediaFile) IsVideo() bool {
return m.MediaType() == fs.MediaVideo
}
// IsPlayableVideo returns true if this is a supported video file format.
func (m *MediaFile) IsPlayableVideo() bool {
return m.MediaType() == fs.MediaVideo && m.HasFileType(fs.TypeMP4)
}
// IsPhoto returns true if this file is a photo / image.
func (m *MediaFile) IsPhoto() bool {
return m.IsJpeg() || m.IsRaw() || m.IsHEIF() || m.IsImageOther()

View file

@ -85,7 +85,7 @@ func Albums(f form.AlbumSearch) (results []AlbumResult, err error) {
s = s.Where("LOWER(albums.album_name) LIKE ?", likeString)
}
if f.Favorites {
if f.Favorite {
s = s.Where("albums.album_favorite = 1")
}

View file

@ -67,7 +67,7 @@ func TestAlbums(t *testing.T) {
})
t.Run("favorites true", func(t *testing.T) {
query := form.NewAlbumSearch("favorites:true count:10000")
query := form.NewAlbumSearch("favorite:true count:10000")
result, err := Albums(query)
@ -102,14 +102,14 @@ func TestAlbums(t *testing.T) {
})
t.Run("search for existing ID", func(t *testing.T) {
f := form.AlbumSearch{
Query: "",
ID: "at9lxuqxpogaaba7",
Slug: "",
Name: "",
Favorites: false,
Count: 0,
Offset: 0,
Order: "",
Query: "",
ID: "at9lxuqxpogaaba7",
Slug: "",
Name: "",
Favorite: false,
Count: 0,
Offset: 0,
Order: "",
}
result, err := Albums(f)

View file

@ -4,11 +4,12 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestFileShares(t *testing.T) {
t.Run("search for id and status", func(t *testing.T) {
r, err := FileShares(uint(1000001), "test")
r, err := FileShares(uint(1000001), "new")
if err != nil {
t.Fatal(err)
}
@ -21,21 +22,18 @@ func TestFileShares(t *testing.T) {
}
func TestExpiredFileShares(t *testing.T) {
//TODO Find way to not overwrite updated at in test db
/*t.Run("expired file share exists", func(t *testing.T) {
t.Log(entity.AccountFixtureWebdavDummy.ShareExpires)
time.Sleep(10 * time.Second)
t.Run("expired file share exists", func(t *testing.T) {
time.Sleep(2 * time.Second)
r, err := ExpiredFileShares(entity.AccountFixtureWebdavDummy)
if err != nil {
t.Fatal(err)
}
t.Logf("%+v", r)
assert.LessOrEqual(t, 1, len(r))
for _, r := range r {
assert.IsType(t, entity.FileShare{}, r)
}
})*/
})
t.Run("expired file does not exists", func(t *testing.T) {
r, err := ExpiredFileShares(entity.AccountFixtureWebdavDummy2)
if err != nil {

View file

@ -27,7 +27,7 @@ func TestGeo(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, "Neckarbrücke", result[0].PhotoTitle)
assert.LessOrEqual(t, 2, len(result))
})

View file

@ -67,7 +67,7 @@ func Labels(f form.LabelSearch) (results []LabelResult, err error) {
}
}
if f.Favorites {
if f.Favorite {
s = s.Where("labels.label_favorite = 1")
}

View file

@ -35,8 +35,34 @@ func TestLabels(t *testing.T) {
}
})
t.Run("search for cow", func(t *testing.T) {
query := form.NewLabelSearch("Query:cow Count:1005 Order:slug")
result, err := Labels(query)
if err != nil {
t.Fatal(err)
}
t.Logf("results: %+v", result)
assert.LessOrEqual(t, 1, len(result))
for _, r := range result {
assert.IsType(t, LabelResult{}, r)
assert.NotEmpty(t, r.ID)
assert.NotEmpty(t, r.LabelName)
assert.NotEmpty(t, r.LabelSlug)
assert.NotEmpty(t, r.CustomSlug)
if fix, ok := entity.LabelFixtures[r.LabelSlug]; ok {
assert.Equal(t, fix.LabelName, r.LabelName)
assert.Equal(t, fix.LabelSlug, r.LabelSlug)
assert.Equal(t, fix.CustomSlug, r.CustomSlug)
}
}
})
t.Run("search for favorites", func(t *testing.T) {
query := form.NewLabelSearch("Favorites:true Count:15")
query := form.NewLabelSearch("Favorite:true Count:15")
result, err := Labels(query)
if err != nil {
@ -83,15 +109,15 @@ func TestLabels(t *testing.T) {
t.Run("search for ID", func(t *testing.T) {
f := form.LabelSearch{
Query: "",
ID: "lt9k3pw1wowuy3c4",
Slug: "",
Name: "",
All: false,
Favorites: false,
Count: 0,
Offset: 0,
Order: "",
Query: "",
ID: "lt9k3pw1wowuy3c4",
Slug: "",
Name: "",
All: false,
Favorite: false,
Count: 0,
Offset: 0,
Order: "",
}
result, err := Labels(f)

View file

@ -24,6 +24,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
// s.LogMode(true)
// Main search query, avoids (slow) left joins.
s = s.Table("photos").
Select(`photos.*,
files.id AS file_id, files.file_uuid, files.file_primary, files.file_missing, files.file_name, files.file_hash,
@ -33,12 +34,12 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
cameras.camera_make, cameras.camera_model,
lenses.lens_make, lenses.lens_model,
places.loc_label, places.loc_city, places.loc_state, places.loc_country`).
Joins("JOIN files ON files.photo_id = photos.id AND files.file_missing = 0 AND files.deleted_at IS NULL AND (files.file_type = 'jpg' OR files.file_video)").
Joins("JOIN cameras ON cameras.id = photos.camera_id").
Joins("JOIN lenses ON lenses.id = photos.lens_id").
Joins("JOIN places ON photos.place_id = places.id").
Group("photos.id, files.id")
Joins("JOIN files ON photos.id = files.photo_id AND files.file_missing = 0 AND files.deleted_at IS NULL").
Joins("JOIN cameras ON photos.camera_id = cameras.id").
Joins("JOIN lenses ON photos.lens_id = lenses.id").
Joins("JOIN places ON photos.place_id = places.id")
// Shortcut for known photo ids.
if f.ID != "" {
s = s.Where("photos.photo_uuid = ?", f.ID)
s = s.Order("files.file_primary DESC")
@ -54,6 +55,16 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
return results, len(results), nil
}
// Filter by media type.
if f.Video {
s = s.Where("(files.file_type = 'jpg' OR files.file_video = 1) AND photos.photo_video = 1")
} else if f.Photo {
s = s.Where("files.file_type = 'jpg' AND photos.photo_video = 1")
} else {
s = s.Where("(files.file_type = 'jpg' OR files.file_video = 1)")
}
// Filter by label, label category and keywords.
var categories []entity.Category
var label entity.Label
var labels []entity.Label
@ -77,6 +88,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
}
}
// Filter by location.
if f.Location == true {
s = s.Where("location_id > 0")
@ -116,6 +128,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
}
}
// Filter by status.
if f.Archived {
s = s.Where("photos.deleted_at IS NOT NULL")
} else {
@ -134,6 +147,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
}
}
// Filter by additional flags and metadata.
if f.Error {
s = s.Where("files.file_error <> ''")
}
@ -162,7 +176,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
s = s.Where("files.file_main_color = ?", strings.ToLower(f.Color))
}
if f.Favorites {
if f.Favorite {
s = s.Where("photos.photo_favorite = 1")
}
@ -212,7 +226,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
f.Dist = 5000
}
// Inaccurate distance search, but probably 'good enough' for now
// Filter by distance (approximation).
if f.Lat > 0 {
latMin := f.Lat - SearchRadius*float32(f.Dist)
latMax := f.Lat + SearchRadius*float32(f.Dist)
@ -233,6 +247,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
s = s.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
}
// Set sort order for results.
switch f.Order {
case entity.SortOrderRelevance:
if f.Label != "" {

View file

@ -10,7 +10,7 @@ import (
)
func TestPhotos(t *testing.T) {
t.Run("normal query", func(t *testing.T) {
t.Run("search all", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 10
@ -35,6 +35,36 @@ func TestPhotos(t *testing.T) {
}
}
})
t.Run("search for ID and merged", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.ID = "pt9jtdre2lvl0yh7"
f.Merged = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for ID with merged false", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.ID = "pt9jtdre2lvl0yh7"
f.Merged = false
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("label query dog", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "label:dog"
@ -45,7 +75,6 @@ func TestPhotos(t *testing.T) {
assert.Equal(t, "label dog not found", err.Error())
assert.Empty(t, photos)
//t.Logf("results: %+v", photos)
})
t.Run("label query landscape", func(t *testing.T) {
var f form.PhotoSearch
@ -89,7 +118,143 @@ func TestPhotos(t *testing.T) {
}
assert.LessOrEqual(t, 3, len(photos))
})
t.Run("form.location true and keyword", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "bridge"
f.Count = 10
f.Offset = 0
f.Location = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("query too short", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "a"
f.Count = 5000
f.Offset = 0
f.Location = false
photos, _, err := Photos(f)
assert.Equal(t, "query too short", err.Error())
assert.Empty(t, photos)
})
t.Run("search for keyword", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "bridge"
f.Count = 5000
f.Offset = 0
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 2, len(photos))
})
t.Run("search for label in query", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "flower"
f.Count = 5000
f.Offset = 0
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for archived", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Archived = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for private", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Private = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for public", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Public = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 3, len(photos))
})
t.Run("search for review", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Review = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for quality", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Quality = 4
f.Private = false
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for file error", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Error = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("form.camera", func(t *testing.T) {
var f form.PhotoSearch
@ -123,7 +288,7 @@ func TestPhotos(t *testing.T) {
})
t.Run("form.favorites", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "favorites:true"
f.Query = "favorite:true"
f.Count = 10
f.Offset = 0
@ -213,9 +378,10 @@ func TestPhotos(t *testing.T) {
})
t.Run("form.mono", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "mono:false"
f.Query = "mono:true"
f.Count = 10
f.Offset = 0
f.Archived = true
photos, _, err := Photos(f)
@ -223,7 +389,7 @@ func TestPhotos(t *testing.T) {
t.Fatal(err)
}
assert.LessOrEqual(t, 4, len(photos))
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("form.chroma >9 Order:similar", func(t *testing.T) {
var f form.PhotoSearch
@ -352,53 +518,6 @@ func TestPhotos(t *testing.T) {
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.LessOrEqual(t, 1, len(photos))
})
t.Run("search for private, archived, review", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Private = true
f.Archived = true
f.Review = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.Empty(t, photos)
})
t.Run("search for archived and public", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.Archived = true
f.Public = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}
assert.Empty(t, photos)
//TODO create test fixture
})
t.Run("search for ID", func(t *testing.T) {
var f form.PhotoSearch
f.Query = ""
f.Count = 5000
f.Offset = 0
f.ID = "pt9jtdre2lvl0yh7"
f.Merged = true
photos, _, err := Photos(f)
if err != nil {
t.Fatal(err)
}

View file

@ -10,37 +10,130 @@ import (
)
func TestResampleOptions(t *testing.T) {
method, filter, format := ResampleOptions(ResamplePng, ResampleFillCenter, ResampleDefault)
t.Run("ResamplePng, FillCenter", func(t *testing.T) {
method, filter, format := ResampleOptions(ResamplePng, ResampleFillCenter, ResampleDefault)
assert.Equal(t, ResampleFillCenter, method)
assert.Equal(t, imaging.Lanczos.Support, filter.Support)
assert.Equal(t, fs.TypePng, format)
assert.Equal(t, ResampleFillCenter, method)
assert.Equal(t, imaging.Lanczos.Support, filter.Support)
assert.Equal(t, fs.TypePng, format)
})
t.Run("ResampleNearestNeighbor, FillTopLeft", func(t *testing.T) {
method, filter, format := ResampleOptions(ResampleNearestNeighbor, ResampleFillTopLeft)
assert.Equal(t, ResampleFillTopLeft, method)
assert.Equal(t, imaging.NearestNeighbor.Support, filter.Support)
assert.Equal(t, fs.TypeJpeg, format)
})
t.Run("ResampleNearestNeighbor, FillBottomRight", func(t *testing.T) {
method, filter, format := ResampleOptions(ResampleNearestNeighbor, ResampleFillBottomRight)
assert.Equal(t, ResampleFillBottomRight, method)
assert.Equal(t, imaging.NearestNeighbor.Support, filter.Support)
assert.Equal(t, fs.TypeJpeg, format)
})
}
func TestResample(t *testing.T) {
tile50 := Types["tile_50"]
t.Run("tile50 options", func(t *testing.T) {
tile50 := Types["tile_50"]
src := "testdata/example.jpg"
src := "testdata/example.jpg"
assert.FileExists(t, src)
assert.FileExists(t, src)
img, err := imaging.Open(src, imaging.AutoOrientation(true))
img, err := imaging.Open(src, imaging.AutoOrientation(true))
if err != nil {
t.Fatal(err)
}
if err != nil {
t.Fatal(err)
}
bounds := img.Bounds()
bounds := img.Bounds()
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
result := *Resample(&img, tile50.Width, tile50.Height, tile50.Options...)
result := *Resample(&img, tile50.Width, tile50.Height, tile50.Options...)
boundsNew := result.Bounds()
boundsNew := result.Bounds()
assert.Equal(t, 50, boundsNew.Max.X)
assert.Equal(t, 50, boundsNew.Max.Y)
assert.Equal(t, 50, boundsNew.Max.X)
assert.Equal(t, 50, boundsNew.Max.Y)
})
t.Run("left_224 options", func(t *testing.T) {
left_224 := Types["left_224"]
src := "testdata/example.jpg"
assert.FileExists(t, src)
img, err := imaging.Open(src, imaging.AutoOrientation(true))
if err != nil {
t.Fatal(err)
}
bounds := img.Bounds()
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
result := *Resample(&img, left_224.Width, left_224.Height, left_224.Options...)
boundsNew := result.Bounds()
assert.Equal(t, 224, boundsNew.Max.X)
assert.Equal(t, 224, boundsNew.Max.Y)
})
t.Run("right_224 options", func(t *testing.T) {
right_224 := Types["right_224"]
src := "testdata/example.jpg"
assert.FileExists(t, src)
img, err := imaging.Open(src, imaging.AutoOrientation(true))
if err != nil {
t.Fatal(err)
}
bounds := img.Bounds()
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
result := *Resample(&img, right_224.Width, right_224.Height, right_224.Options...)
boundsNew := result.Bounds()
assert.Equal(t, 224, boundsNew.Max.X)
assert.Equal(t, 224, boundsNew.Max.Y)
})
t.Run("fit_1280 options", func(t *testing.T) {
fit_1280 := Types["fit_1280"]
src := "testdata/example.jpg"
assert.FileExists(t, src)
img, err := imaging.Open(src, imaging.AutoOrientation(true))
if err != nil {
t.Fatal(err)
}
bounds := img.Bounds()
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
result := *Resample(&img, fit_1280.Width, fit_1280.Height, fit_1280.Options...)
boundsNew := result.Bounds()
assert.Equal(t, 750, boundsNew.Max.X)
assert.Equal(t, 500, boundsNew.Max.Y)
})
}
func TestPostfix(t *testing.T) {
@ -75,6 +168,38 @@ func TestFilename(t *testing.T) {
assert.Equal(t, "testdata/1/2/3/123456789098765432_720x720_fit.jpg", result)
})
t.Run("invalid width", func(t *testing.T) {
colorThumb := Types["colors"]
result, err := Filename("123456789098765432", "testdata", -2, colorThumb.Height, colorThumb.Options...)
assert.Equal(t, "resample: width exceeds limit (-2)", err.Error())
assert.Empty(t, result)
})
t.Run("invalid height", func(t *testing.T) {
colorThumb := Types["colors"]
result, err := Filename("123456789098765432", "testdata", colorThumb.Width, -3, colorThumb.Options...)
assert.Equal(t, "resample: height exceeds limit (-3)", err.Error())
assert.Empty(t, result)
})
t.Run("invalid hash", func(t *testing.T) {
colorThumb := Types["colors"]
result, err := Filename("12", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
assert.Equal(t, "resample: file hash is empty or too short (12)", err.Error())
assert.Empty(t, result)
})
t.Run("invalid thumb path", func(t *testing.T) {
colorThumb := Types["colors"]
result, err := Filename("123456789098765432", "", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
assert.Equal(t, "resample: folder is empty", err.Error())
assert.Empty(t, result)
})
}
func TestFromFile(t *testing.T) {
@ -107,6 +232,14 @@ func TestFromFile(t *testing.T) {
assert.Equal(t, "", fileName)
assert.Error(t, err)
})
t.Run("empty filename", func(t *testing.T) {
colorThumb := Types["colors"]
fileName, err := FromFile("", "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
assert.Equal(t, "", fileName)
assert.Equal(t, "resample: image filename is empty or too short ()", err.Error())
})
}
func TestFromCache(t *testing.T) {
@ -136,6 +269,25 @@ func TestFromCache(t *testing.T) {
assert.Equal(t, "", fileName)
assert.Error(t, err)
})
t.Run("invalid hash", func(t *testing.T) {
tile50 := Types["tile_50"]
src := "testdata/example.jpg"
assert.FileExists(t, src)
fileName, err := FromCache(src, "12", "testdata", tile50.Width, tile50.Height, tile50.Options...)
assert.Equal(t, "resample: file hash is empty or too short (12)", err.Error())
assert.Empty(t, fileName)
})
t.Run("empty filename", func(t *testing.T) {
tile50 := Types["tile_50"]
fileName, err := FromCache("", "193456789098765432", "testdata", tile50.Width, tile50.Height, tile50.Options...)
assert.Equal(t, "resample: image filename is empty or too short ()", err.Error())
assert.Empty(t, fileName)
})
}
func TestCreate(t *testing.T) {
@ -176,4 +328,52 @@ func TestCreate(t *testing.T) {
assert.Equal(t, 500, boundsNew.Max.X)
assert.Equal(t, 500, boundsNew.Max.Y)
})
t.Run("invalid width", func(t *testing.T) {
tile500 := Types["tile_500"]
src := "testdata/example.jpg"
dst := "testdata/example.tile_500.jpg"
assert.FileExists(t, src)
assert.NoFileExists(t, dst)
img, err := imaging.Open(src, imaging.AutoOrientation(true))
if err != nil {
t.Fatal(err)
}
bounds := img.Bounds()
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
resized, err := Create(&img, dst, -5, tile500.Height, tile500.Options...)
assert.Equal(t, "resample: width has an invalid value (-5)", err.Error())
t.Log(resized)
})
t.Run("invalid height", func(t *testing.T) {
tile500 := Types["tile_500"]
src := "testdata/example.jpg"
dst := "testdata/example.tile_500.jpg"
assert.FileExists(t, src)
assert.NoFileExists(t, dst)
img, err := imaging.Open(src, imaging.AutoOrientation(true))
if err != nil {
t.Fatal(err)
}
bounds := img.Bounds()
assert.Equal(t, 750, bounds.Max.X)
assert.Equal(t, 500, bounds.Max.Y)
resized, err := Create(&img, dst, tile500.Width, -3, tile500.Options...)
assert.Equal(t, "resample: height has an invalid value (-3)", err.Error())
t.Log(resized)
})
}

View file

@ -39,3 +39,18 @@ func TestType_SkipPreRender(t *testing.T) {
Size = 3840
Limit = 3840
}
func TestResampleFilter_Imaging(t *testing.T) {
t.Run("Blackman", func(t *testing.T) {
r := ResampleBlackman.Imaging()
assert.Equal(t, float64(3), r.Support)
})
t.Run("Cubic", func(t *testing.T) {
r := ResampleCubic.Imaging()
assert.Equal(t, float64(2), r.Support)
})
t.Run("Linear", func(t *testing.T) {
r := ResampleLinear.Imaging()
assert.Equal(t, float64(1), r.Support)
})
}

View file

@ -11,6 +11,7 @@ import (
)
func Dirs(root string, recursive bool) (result []string, err error) {
result = []string{}
ignore := NewIgnoreList(".ppignore", true, false)
mutex := sync.Mutex{}