mirror of
https://github.com/photoprism/photoprism.git
synced 2025-10-05 16:57:17 +08:00
Albums: Zip download #15
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
@@ -61,7 +61,7 @@
|
||||
small
|
||||
title="Download"
|
||||
color="teal accent-4"
|
||||
@click.stop="batchDownload()"
|
||||
@click.stop="downloadZip()"
|
||||
class="p-photo-clipboard-download"
|
||||
>
|
||||
<v-icon>save</v-icon>
|
||||
@@ -212,10 +212,15 @@
|
||||
Notify.warning("Not implemented yet");
|
||||
this.expanded = false;
|
||||
},
|
||||
batchDownload() {
|
||||
Notify.warning("Not implemented yet");
|
||||
downloadZip() {
|
||||
Api.post("zip", {"photos": this.selection}).then(this.onDownload.bind(this));
|
||||
this.expanded = false;
|
||||
},
|
||||
onDownload(r) {
|
||||
console.log("onDownload", r);
|
||||
Notify.success(r.data.message);
|
||||
window.open("/api/v1/zip/" + r.data.filename, "_blank");
|
||||
},
|
||||
openDocs() {
|
||||
window.open('https://docs.photoprism.org/en/latest/', '_blank');
|
||||
},
|
||||
|
@@ -6,12 +6,12 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/forms"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
)
|
||||
@@ -19,24 +19,24 @@ import (
|
||||
// GET /api/v1/albums
|
||||
func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/albums", func(c *gin.Context) {
|
||||
var form forms.AlbumSearchForm
|
||||
var f form.AlbumSearch
|
||||
|
||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||
err := c.MustBindWith(&form, binding.Form)
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Albums(form)
|
||||
result, err := search.Albums(f)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("X-Result-Count", strconv.Itoa(form.Count))
|
||||
c.Header("X-Result-Offset", strconv.Itoa(form.Offset))
|
||||
c.Header("X-Result-Count", strconv.Itoa(f.Count))
|
||||
c.Header("X-Result-Offset", strconv.Itoa(f.Offset))
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
})
|
||||
@@ -58,10 +58,6 @@ func GetAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
})
|
||||
}
|
||||
|
||||
type AlbumParams struct {
|
||||
AlbumName string `json:"AlbumName"`
|
||||
}
|
||||
|
||||
// POST /api/v1/albums
|
||||
func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/albums", func(c *gin.Context) {
|
||||
@@ -70,14 +66,14 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
var params AlbumParams
|
||||
var f form.Album
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
m := models.NewAlbum(params.AlbumName)
|
||||
m := models.NewAlbum(f.AlbumName)
|
||||
|
||||
if res := conf.Db().Create(m); res.Error != nil {
|
||||
log.Error(res.Error.Error())
|
||||
@@ -99,9 +95,9 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
var params AlbumParams
|
||||
var f form.Album
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
@@ -116,7 +112,7 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
m.Rename(params.AlbumName)
|
||||
m.Rename(f.AlbumName)
|
||||
conf.Db().Save(&m)
|
||||
|
||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||
@@ -192,14 +188,14 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
var params PhotoUUIDs
|
||||
var f form.PhotoUUIDs
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if len(params.Photos) == 0 {
|
||||
if len(f.Photos) == 0 {
|
||||
log.Error("no photos selected")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||
return
|
||||
@@ -213,13 +209,11 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("adding %d photos to album %s", len(params.Photos), a.AlbumName)
|
||||
|
||||
db := conf.Db()
|
||||
var added []*models.PhotoAlbum
|
||||
var failed []string
|
||||
|
||||
for _, photoUUID := range params.Photos {
|
||||
for _, photoUUID := range f.Photos {
|
||||
if p, err := search.FindPhotoByUUID(photoUUID); err != nil {
|
||||
failed = append(failed, photoUUID)
|
||||
} else {
|
||||
@@ -245,14 +239,14 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
var params PhotoUUIDs
|
||||
var f form.PhotoUUIDs
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if len(params.Photos) == 0 {
|
||||
if len(f.Photos) == 0 {
|
||||
log.Error("no photos selected")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||
return
|
||||
@@ -266,14 +260,12 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("adding %d photos to album %s", len(params.Photos), a.AlbumName)
|
||||
|
||||
db := conf.Db()
|
||||
|
||||
db.Where("album_uuid = ? AND photo_uuid IN (?)", a.AlbumUUID, params.Photos).Delete(&models.PhotoAlbum{})
|
||||
db.Where("album_uuid = ? AND photo_uuid IN (?)", a.AlbumUUID, f.Photos).Delete(&models.PhotoAlbum{})
|
||||
|
||||
event.Success(fmt.Sprintf("photos removed from %s", a.AlbumName))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "photos removed from album", "album": a, "photos": params.Photos})
|
||||
c.JSON(http.StatusOK, gin.H{"message": "photos removed from album", "album": a, "photos": f.Photos})
|
||||
})
|
||||
}
|
||||
|
@@ -7,16 +7,13 @@ import (
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/models"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PhotoUUIDs struct {
|
||||
Photos []string `json:"photos"`
|
||||
}
|
||||
|
||||
// POST /api/v1/batch/photos/delete
|
||||
func BatchPhotosDelete(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/batch/photos/delete", func(c *gin.Context) {
|
||||
@@ -27,24 +24,24 @@ func BatchPhotosDelete(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var params PhotoUUIDs
|
||||
var f form.PhotoUUIDs
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if len(params.Photos) == 0 {
|
||||
if len(f.Photos) == 0 {
|
||||
log.Error("no photos selected")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("deleting photos: %#v", params.Photos)
|
||||
log.Infof("deleting photos: %#v", f.Photos)
|
||||
|
||||
db := conf.Db()
|
||||
|
||||
db.Where("photo_uuid IN (?)", params.Photos).Delete(&models.Photo{})
|
||||
db.Where("photo_uuid IN (?)", f.Photos).Delete(&models.Photo{})
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
@@ -62,24 +59,24 @@ func BatchPhotosPrivate(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var params PhotoUUIDs
|
||||
var f form.PhotoUUIDs
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if len(params.Photos) == 0 {
|
||||
if len(f.Photos) == 0 {
|
||||
log.Error("no photos selected")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("marking photos as private: %#v", params.Photos)
|
||||
log.Infof("marking photos as private: %#v", f.Photos)
|
||||
|
||||
db := conf.Db()
|
||||
|
||||
db.Model(models.Photo{}).Where("photo_uuid IN (?)", params.Photos).UpdateColumn("photo_private", gorm.Expr("IF (`photo_private`, 0, 1)"))
|
||||
db.Model(models.Photo{}).Where("photo_uuid IN (?)", f.Photos).UpdateColumn("photo_private", gorm.Expr("IF (`photo_private`, 0, 1)"))
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
@@ -97,24 +94,24 @@ func BatchPhotosStory(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var params PhotoUUIDs
|
||||
var f form.PhotoUUIDs
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if len(params.Photos) == 0 {
|
||||
if len(f.Photos) == 0 {
|
||||
log.Error("no photos selected")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("marking photos as story: %#v", params.Photos)
|
||||
log.Infof("marking photos as story: %#v", f.Photos)
|
||||
|
||||
db := conf.Db()
|
||||
|
||||
db.Model(models.Photo{}).Where("photo_uuid IN (?)", params.Photos).Updates(map[string]interface{}{
|
||||
db.Model(models.Photo{}).Where("photo_uuid IN (?)", f.Photos).Updates(map[string]interface{}{
|
||||
"photo_story": gorm.Expr("IF (`photo_story`, 0, 1)"),
|
||||
})
|
||||
|
||||
|
@@ -10,6 +10,10 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
)
|
||||
|
||||
// TODO: GET /api/v1/dl/file/:hash
|
||||
// TODO: GET /api/v1/dl/photo/:uuid
|
||||
// TODO: GET /api/v1/dl/album/:uuid
|
||||
|
||||
// GET /api/v1/download/:hash
|
||||
//
|
||||
// Parameters:
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/forms"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
)
|
||||
@@ -15,24 +15,24 @@ import (
|
||||
// GET /api/v1/labels
|
||||
func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/labels", func(c *gin.Context) {
|
||||
var form forms.LabelSearchForm
|
||||
var f form.LabelSearch
|
||||
|
||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||
err := c.MustBindWith(&form, binding.Form)
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Labels(form)
|
||||
result, err := search.Labels(f)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("X-Result-Count", strconv.Itoa(form.Count))
|
||||
c.Header("X-Result-Offset", strconv.Itoa(form.Offset))
|
||||
c.Header("X-Result-Count", strconv.Itoa(f.Count))
|
||||
c.Header("X-Result-Offset", strconv.Itoa(f.Offset))
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
})
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/photoprism/photoprism/internal/forms"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
)
|
||||
|
||||
@@ -29,24 +29,24 @@ import (
|
||||
// favorites: bool Find favorites only
|
||||
func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/photos", func(c *gin.Context) {
|
||||
var form forms.PhotoSearchForm
|
||||
var f form.PhotoSearch
|
||||
|
||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||
err := c.MustBindWith(&form, binding.Form)
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := search.Photos(form)
|
||||
result, err := search.Photos(f)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("X-Result-Count", strconv.Itoa(form.Count))
|
||||
c.Header("X-Result-Offset", strconv.Itoa(form.Offset))
|
||||
c.Header("X-Result-Count", strconv.Itoa(f.Count))
|
||||
c.Header("X-Result-Offset", strconv.Itoa(f.Offset))
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
})
|
||||
|
@@ -6,25 +6,21 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
)
|
||||
|
||||
type CreateSessionParams struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// POST /api/v1/session
|
||||
func CreateSession(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/session", func(c *gin.Context) {
|
||||
var params CreateSessionParams
|
||||
var f form.Login
|
||||
|
||||
if err := c.BindJSON(¶ms); err != nil {
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if params.Password != conf.AdminPassword() {
|
||||
if f.Password != conf.AdminPassword() {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": "Invalid password"})
|
||||
return
|
||||
}
|
||||
|
@@ -29,14 +29,14 @@ func Upload(router *gin.RouterGroup, conf *config.Config) {
|
||||
start := time.Now()
|
||||
subPath := c.Param("path")
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
f, err := c.MultipartForm()
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
files := form.File["files"]
|
||||
files := f.File["files"]
|
||||
|
||||
path := fmt.Sprintf("%s/upload/%s", conf.ImportPath(), subPath)
|
||||
|
||||
|
148
internal/api/zip.go
Normal file
148
internal/api/zip.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
)
|
||||
|
||||
// POST /api/v1/zip
|
||||
func CreateZip(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/zip", func(c *gin.Context) {
|
||||
var f form.PhotoUUIDs
|
||||
start := time.Now()
|
||||
|
||||
if err := c.BindJSON(&f); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.Photos) == 0 {
|
||||
log.Error("no photos selected")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||
return
|
||||
}
|
||||
|
||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||
files, err := search.FindFilesByUUID(f.Photos, 1000, 0)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
zipPath := path.Join(conf.ExportPath(), "zip")
|
||||
zipDate := time.Now().Format("20060201-150405")
|
||||
zipBaseName := fmt.Sprintf("photos-%s.zip", zipDate)
|
||||
zipFileName := fmt.Sprintf("%s/%s", zipPath, zipBaseName)
|
||||
|
||||
if err := os.MkdirAll(zipPath, 0700); err != nil {
|
||||
log.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": util.UcFirst("failed to create zip directory")})
|
||||
return
|
||||
}
|
||||
|
||||
newZipFile, err := os.Create(zipFileName)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": util.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
||||
defer newZipFile.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(newZipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
for i, file := range files {
|
||||
fileName := fmt.Sprintf("%s/%s", conf.OriginalsPath(), file.FileName)
|
||||
fileSlug := slug.MakeLang(file.Photo.PhotoTitle, "en")
|
||||
fileAlias := fmt.Sprintf("%05d-%s.%s", i, fileSlug, file.FileType)
|
||||
|
||||
if util.Exists(fileName) {
|
||||
if err := addFileToZip(zipWriter, fileName, fileAlias); err != nil {
|
||||
log.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": util.UcFirst("failed to create zip file")})
|
||||
return
|
||||
}
|
||||
log.Infof("zip: added %s as %s", file.FileName, fileAlias)
|
||||
} else {
|
||||
log.Warnf("zip: %s is missing", file.FileName)
|
||||
file.FileMissing = true
|
||||
conf.Db().Save(&file)
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := int(time.Since(start).Seconds())
|
||||
|
||||
log.Infof("zip: archive %s created in %s", zipBaseName, time.Since(start))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("zip created in %d s", elapsed), "filename": zipBaseName})
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/zip/:filename
|
||||
func DownloadZip(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/zip/:filename", func(c *gin.Context) {
|
||||
zipBaseName := filepath.Base(c.Param("filename"))
|
||||
zipPath := path.Join(conf.ExportPath(), "zip")
|
||||
zipFileName := fmt.Sprintf("%s/%s", zipPath, zipBaseName)
|
||||
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", zipBaseName))
|
||||
|
||||
if !util.Exists(zipFileName) {
|
||||
log.Errorf("could not find zip file: %s", zipFileName)
|
||||
c.Data(404, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
c.File(zipFileName)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func addFileToZip(zipWriter *zip.Writer, fileName, fileAlias string) error {
|
||||
fileToZip, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileToZip.Close()
|
||||
|
||||
// Get the file information
|
||||
info, err := fileToZip.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Name = fileAlias
|
||||
|
||||
// Change to deflate to gain better compression
|
||||
// see http://golang.org/pkg/archive/zip/#pkg-constants
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, fileToZip)
|
||||
return err
|
||||
}
|
5
internal/form/album.go
Normal file
5
internal/form/album.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package form
|
||||
|
||||
type Album struct {
|
||||
AlbumName string `json:"AlbumName"`
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
// Query parameters for GET /api/v1/albums
|
||||
type AlbumSearchForm struct {
|
||||
type AlbumSearch struct {
|
||||
Query string `form:"q"`
|
||||
|
||||
Slug string `form:"slug"`
|
||||
@@ -27,7 +27,7 @@ type AlbumSearchForm struct {
|
||||
Order string `form:"order"`
|
||||
}
|
||||
|
||||
func (f *AlbumSearchForm) ParseQueryString() (result error) {
|
||||
func (f *AlbumSearch) ParseQueryString() (result error) {
|
||||
var key, value []byte
|
||||
var escaped, isKeyValue bool
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
)
|
||||
|
||||
func TestAlbumSearchForm(t *testing.T) {
|
||||
form := &AlbumSearchForm{}
|
||||
form := &AlbumSearch{}
|
||||
|
||||
assert.IsType(t, new(AlbumSearchForm), form)
|
||||
assert.IsType(t, new(AlbumSearch), form)
|
||||
}
|
||||
|
||||
func TestParseQueryStringAlbum(t *testing.T) {
|
||||
|
||||
t.Run("valid query", func(t *testing.T) {
|
||||
form := &AlbumSearchForm{Query: "slug:album1 favorites:true count:10"}
|
||||
form := &AlbumSearch{Query: "slug:album1 favorites:true count:10"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
||||
assert.Equal(t, 10, form.Count)
|
||||
})
|
||||
t.Run("valid query 2", func(t *testing.T) {
|
||||
form := &AlbumSearchForm{Query: "name:album1 favorites:false offset:100 order:newest query:\"query text\""}
|
||||
form := &AlbumSearch{Query: "name:album1 favorites:false offset:100 order:newest query:\"query text\""}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
||||
assert.Equal(t, "query text", form.Query)
|
||||
})
|
||||
t.Run("query for invalid filter", func(t *testing.T) {
|
||||
form := &AlbumSearchForm{Query: "xxx:false"}
|
||||
form := &AlbumSearch{Query: "xxx:false"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
||||
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
||||
})
|
||||
t.Run("query for favorites with invalid type", func(t *testing.T) {
|
||||
form := &AlbumSearchForm{Query: "favorites:cat"}
|
||||
form := &AlbumSearch{Query: "favorites:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
||||
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
||||
})
|
||||
t.Run("query for count with invalid type", func(t *testing.T) {
|
||||
form := &AlbumSearchForm{Query: "count:cat"}
|
||||
form := &AlbumSearch{Query: "count:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
Package forms contains tagged structs for input value validation.
|
||||
Package form contains tagged structs for input value validation.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
|
||||
https://github.com/photoprism/photoprism/wiki
|
||||
*/
|
||||
package forms
|
||||
package form
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
"os"
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
// Query parameters for GET /api/v1/labels
|
||||
type LabelSearchForm struct {
|
||||
type LabelSearch struct {
|
||||
Query string `form:"q"`
|
||||
|
||||
Slug string `form:"slug"`
|
||||
@@ -28,7 +28,7 @@ type LabelSearchForm struct {
|
||||
Order string `form:"order"`
|
||||
}
|
||||
|
||||
func (f *LabelSearchForm) ParseQueryString() (result error) {
|
||||
func (f *LabelSearch) ParseQueryString() (result error) {
|
||||
var key, value []byte
|
||||
var escaped, isKeyValue bool
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
)
|
||||
|
||||
func TestLabelSearchForm(t *testing.T) {
|
||||
form := &LabelSearchForm{}
|
||||
form := &LabelSearch{}
|
||||
|
||||
assert.IsType(t, new(LabelSearchForm), form)
|
||||
assert.IsType(t, new(LabelSearch), form)
|
||||
}
|
||||
|
||||
func TestParseQueryStringLabel(t *testing.T) {
|
||||
|
||||
t.Run("valid query", func(t *testing.T) {
|
||||
form := &LabelSearchForm{Query: "name:cat favorites:true count:10 priority:4 query:\"query text\""}
|
||||
form := &LabelSearch{Query: "name:cat favorites:true count:10 priority:4 query:\"query text\""}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
||||
assert.Equal(t, "query text", form.Query)
|
||||
})
|
||||
t.Run("valid query 2", func(t *testing.T) {
|
||||
form := &LabelSearchForm{Query: "slug:cat favorites:false offset:2 order:oldest"}
|
||||
form := &LabelSearch{Query: "slug:cat favorites:false offset:2 order:oldest"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
||||
assert.Equal(t, "oldest", form.Order)
|
||||
})
|
||||
t.Run("query for invalid filter", func(t *testing.T) {
|
||||
form := &LabelSearchForm{Query: "xxx:false"}
|
||||
form := &LabelSearch{Query: "xxx:false"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
||||
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
||||
})
|
||||
t.Run("query for favorites with invalid type", func(t *testing.T) {
|
||||
form := &LabelSearchForm{Query: "favorites:cat"}
|
||||
form := &LabelSearch{Query: "favorites:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
||||
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
||||
})
|
||||
t.Run("query for count with invalid type", func(t *testing.T) {
|
||||
form := &LabelSearchForm{Query: "count:cat"}
|
||||
form := &LabelSearch{Query: "count:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
6
internal/form/login.go
Normal file
6
internal/form/login.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package form
|
||||
|
||||
type Login struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
// Query parameters for GET /api/v1/photos
|
||||
type PhotoSearchForm struct {
|
||||
type PhotoSearch struct {
|
||||
Query string `form:"q"`
|
||||
|
||||
Title string `form:"title"`
|
||||
@@ -47,7 +47,7 @@ type PhotoSearchForm struct {
|
||||
Order string `form:"order"`
|
||||
}
|
||||
|
||||
func (f *PhotoSearchForm) ParseQueryString() (result error) {
|
||||
func (f *PhotoSearch) ParseQueryString() (result error) {
|
||||
var key, value []byte
|
||||
var escaped, isKeyValue bool
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package form
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -10,15 +10,15 @@ import (
|
||||
)
|
||||
|
||||
func TestPhotoSearchForm(t *testing.T) {
|
||||
form := &PhotoSearchForm{}
|
||||
form := &PhotoSearch{}
|
||||
|
||||
assert.IsType(t, new(PhotoSearchForm), form)
|
||||
assert.IsType(t, new(PhotoSearch), form)
|
||||
}
|
||||
|
||||
func TestParseQueryString(t *testing.T) {
|
||||
|
||||
t.Run("valid query", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{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 favorites:false dist:25000 lat:33.45343166666667"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, 33.45343166666667, form.Lat)
|
||||
})
|
||||
t.Run("valid query 2", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "chroma:600 description:\"test\" after:2018-01-15 duplicate:false favorites:true long:33.45343166666667"}
|
||||
form := &PhotoSearch{Query: "chroma:600 description:\"test\" after:2018-01-15 duplicate:false favorites:true long:33.45343166666667"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, 33.45343166666667, form.Long)
|
||||
})
|
||||
t.Run("query for invalid filter", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "xxx:false"}
|
||||
form := &PhotoSearch{Query: "xxx:false"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
||||
})
|
||||
t.Run("query for favorites with invalid type", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "favorites:cat"}
|
||||
form := &PhotoSearch{Query: "favorites:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
||||
})
|
||||
t.Run("query for lat with invalid type", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "lat:cat"}
|
||||
form := &PhotoSearch{Query: "lat:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, "strconv.ParseFloat: parsing \"cat\": invalid syntax", err.Error())
|
||||
})
|
||||
t.Run("query for dist with invalid type", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "dist:cat"}
|
||||
form := &PhotoSearch{Query: "dist:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, "strconv.Atoi: parsing \"cat\": invalid syntax", err.Error())
|
||||
})
|
||||
t.Run("query for camera with invalid type", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "camera:cat"}
|
||||
form := &PhotoSearch{Query: "camera:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestParseQueryString(t *testing.T) {
|
||||
assert.Equal(t, "strconv.Atoi: parsing \"cat\": invalid syntax", err.Error())
|
||||
})
|
||||
t.Run("query for before with invalid type", func(t *testing.T) {
|
||||
form := &PhotoSearchForm{Query: "before:cat"}
|
||||
form := &PhotoSearch{Query: "before:cat"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
5
internal/form/photo_uuids.go
Normal file
5
internal/form/photo_uuids.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package form
|
||||
|
||||
type PhotoUUIDs struct {
|
||||
Photos []string `json:"photos"`
|
||||
}
|
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/forms"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/models"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
)
|
||||
@@ -37,12 +37,12 @@ func NewSearch(originalsPath string, db *gorm.DB) *Search {
|
||||
}
|
||||
|
||||
// Photos searches for photos based on a Form and returns a PhotoSearchResult slice.
|
||||
func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult, err error) {
|
||||
if err := form.ParseQueryString(); err != nil {
|
||||
func (s *Search) Photos(f form.PhotoSearch) (results []PhotoSearchResult, err error) {
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", form))
|
||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
||||
|
||||
q := s.db.NewScope(nil).DB()
|
||||
|
||||
@@ -72,10 +72,10 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
||||
var label models.Label
|
||||
var labelIds []uint
|
||||
|
||||
if form.Label != "" {
|
||||
if result := s.db.First(&label, "label_slug = ?", strings.ToLower(form.Label)); result.Error != nil {
|
||||
log.Errorf("search: label \"%s\" not found", form.Label)
|
||||
return results, fmt.Errorf("label \"%s\" not found", form.Label)
|
||||
if f.Label != "" {
|
||||
if result := s.db.First(&label, "label_slug = ?", strings.ToLower(f.Label)); result.Error != nil {
|
||||
log.Errorf("search: label \"%s\" not found", f.Label)
|
||||
return results, fmt.Errorf("label \"%s\" not found", f.Label)
|
||||
} else {
|
||||
labelIds = append(labelIds, label.ID)
|
||||
|
||||
@@ -89,24 +89,24 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
||||
}
|
||||
}
|
||||
|
||||
if form.Location == true {
|
||||
if f.Location == true {
|
||||
q = q.Where("location_id > 0")
|
||||
|
||||
if form.Query != "" {
|
||||
likeString := "%" + strings.ToLower(form.Query) + "%"
|
||||
if f.Query != "" {
|
||||
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||
q = q.Where("LOWER(locations.loc_display_name) LIKE ?", likeString)
|
||||
}
|
||||
} else if form.Query != "" {
|
||||
slugString := slug.Make(form.Query)
|
||||
lowerString := strings.ToLower(form.Query)
|
||||
} else if f.Query != "" {
|
||||
slugString := slug.Make(f.Query)
|
||||
lowerString := strings.ToLower(f.Query)
|
||||
likeString := "%" + lowerString + "%"
|
||||
|
||||
if result := s.db.First(&label, "label_slug = ?", slugString); result.Error != nil {
|
||||
log.Infof("search: label \"%s\" not found", form.Query)
|
||||
log.Infof("search: label \"%s\" not found", f.Query)
|
||||
|
||||
q = q.Where("labels.label_slug = ? OR LOWER(photo_title) LIKE ? OR files.file_main_color = ?", slugString, likeString, lowerString)
|
||||
} else {
|
||||
log.Infof("search: label \"%s\"", form.Query)
|
||||
log.Infof("search: label \"%s\"", f.Query)
|
||||
|
||||
labelIds = append(labelIds, label.ID)
|
||||
|
||||
@@ -121,94 +121,94 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
||||
|
||||
}
|
||||
|
||||
if form.Album != "" {
|
||||
q = q.Joins("JOIN photos_albums ON photos_albums.photo_uuid = photos.photo_uuid").Where("photos_albums.album_uuid = ?", form.Album)
|
||||
if f.Album != "" {
|
||||
q = q.Joins("JOIN photos_albums ON photos_albums.photo_uuid = photos.photo_uuid").Where("photos_albums.album_uuid = ?", f.Album)
|
||||
}
|
||||
|
||||
if form.Camera > 0 {
|
||||
q = q.Where("photos.camera_id = ?", form.Camera)
|
||||
if f.Camera > 0 {
|
||||
q = q.Where("photos.camera_id = ?", f.Camera)
|
||||
}
|
||||
|
||||
if form.Color != "" {
|
||||
q = q.Where("files.file_main_color = ?", strings.ToLower(form.Color))
|
||||
if f.Color != "" {
|
||||
q = q.Where("files.file_main_color = ?", strings.ToLower(f.Color))
|
||||
}
|
||||
|
||||
if form.Favorites {
|
||||
if f.Favorites {
|
||||
q = q.Where("photos.photo_favorite = 1")
|
||||
}
|
||||
|
||||
if form.Country != "" {
|
||||
q = q.Where("locations.loc_country_code = ?", form.Country)
|
||||
if f.Country != "" {
|
||||
q = q.Where("locations.loc_country_code = ?", f.Country)
|
||||
}
|
||||
|
||||
if form.Title != "" {
|
||||
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(form.Title)))
|
||||
if f.Title != "" {
|
||||
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Title)))
|
||||
}
|
||||
|
||||
if form.Description != "" {
|
||||
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(form.Description)))
|
||||
if f.Description != "" {
|
||||
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Description)))
|
||||
}
|
||||
|
||||
if form.Notes != "" {
|
||||
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(form.Notes)))
|
||||
if f.Notes != "" {
|
||||
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Notes)))
|
||||
}
|
||||
|
||||
if form.Hash != "" {
|
||||
q = q.Where("files.file_hash = ?", form.Hash)
|
||||
if f.Hash != "" {
|
||||
q = q.Where("files.file_hash = ?", f.Hash)
|
||||
}
|
||||
|
||||
if form.Duplicate {
|
||||
if f.Duplicate {
|
||||
q = q.Where("files.file_duplicate = 1")
|
||||
}
|
||||
|
||||
if form.Portrait {
|
||||
if f.Portrait {
|
||||
q = q.Where("files.file_portrait = 1")
|
||||
}
|
||||
|
||||
if form.Mono {
|
||||
if f.Mono {
|
||||
q = q.Where("files.file_chroma = 0")
|
||||
} else if form.Chroma > 9 {
|
||||
q = q.Where("files.file_chroma > ?", form.Chroma)
|
||||
} else if form.Chroma > 0 {
|
||||
q = q.Where("files.file_chroma > 0 AND files.file_chroma <= ?", form.Chroma)
|
||||
} else if f.Chroma > 9 {
|
||||
q = q.Where("files.file_chroma > ?", f.Chroma)
|
||||
} else if f.Chroma > 0 {
|
||||
q = q.Where("files.file_chroma > 0 AND files.file_chroma <= ?", f.Chroma)
|
||||
}
|
||||
|
||||
if form.Fmin > 0 {
|
||||
q = q.Where("photos.photo_f_number >= ?", form.Fmin)
|
||||
if f.Fmin > 0 {
|
||||
q = q.Where("photos.photo_f_number >= ?", f.Fmin)
|
||||
}
|
||||
|
||||
if form.Fmax > 0 {
|
||||
q = q.Where("photos.photo_f_number <= ?", form.Fmax)
|
||||
if f.Fmax > 0 {
|
||||
q = q.Where("photos.photo_f_number <= ?", f.Fmax)
|
||||
}
|
||||
|
||||
if form.Dist == 0 {
|
||||
form.Dist = 20
|
||||
} else if form.Dist > 1000 {
|
||||
form.Dist = 1000
|
||||
if f.Dist == 0 {
|
||||
f.Dist = 20
|
||||
} else if f.Dist > 1000 {
|
||||
f.Dist = 1000
|
||||
}
|
||||
|
||||
// Inaccurate distance search, but probably 'good enough' for now
|
||||
if form.Lat > 0 {
|
||||
latMin := form.Lat - SearchRadius*float64(form.Dist)
|
||||
latMax := form.Lat + SearchRadius*float64(form.Dist)
|
||||
if f.Lat > 0 {
|
||||
latMin := f.Lat - SearchRadius*float64(f.Dist)
|
||||
latMax := f.Lat + SearchRadius*float64(f.Dist)
|
||||
q = q.Where("photos.photo_lat BETWEEN ? AND ?", latMin, latMax)
|
||||
}
|
||||
|
||||
if form.Long > 0 {
|
||||
longMin := form.Long - SearchRadius*float64(form.Dist)
|
||||
longMax := form.Long + SearchRadius*float64(form.Dist)
|
||||
if f.Long > 0 {
|
||||
longMin := f.Long - SearchRadius*float64(f.Dist)
|
||||
longMax := f.Long + SearchRadius*float64(f.Dist)
|
||||
q = q.Where("photos.photo_long BETWEEN ? AND ?", longMin, longMax)
|
||||
}
|
||||
|
||||
if !form.Before.IsZero() {
|
||||
q = q.Where("photos.taken_at <= ?", form.Before.Format("2006-01-02"))
|
||||
if !f.Before.IsZero() {
|
||||
q = q.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
if !form.After.IsZero() {
|
||||
q = q.Where("photos.taken_at >= ?", form.After.Format("2006-01-02"))
|
||||
if !f.After.IsZero() {
|
||||
q = q.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
switch form.Order {
|
||||
switch f.Order {
|
||||
case "newest":
|
||||
q = q.Order("taken_at DESC")
|
||||
case "oldest":
|
||||
@@ -219,8 +219,8 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
||||
q = q.Order("taken_at DESC")
|
||||
}
|
||||
|
||||
if form.Count > 0 && form.Count <= 1000 {
|
||||
q = q.Limit(form.Count).Offset(form.Offset)
|
||||
if f.Count > 0 && f.Count <= 1000 {
|
||||
q = q.Limit(f.Count).Offset(f.Offset)
|
||||
} else {
|
||||
q = q.Limit(100).Offset(0)
|
||||
}
|
||||
@@ -242,6 +242,15 @@ func (s *Search) FindFiles(limit int, offset int) (files []models.File, err erro
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// FindFilesByUUID
|
||||
func (s *Search) FindFilesByUUID(u []string, limit int, offset int) (files []models.File, err error) {
|
||||
if err := s.db.Where("(photo_uuid IN (?) AND file_primary = 1) OR file_uuid IN (?)", u, u).Preload("Photo").Limit(limit).Offset(offset).Find(&files).Error; err != nil {
|
||||
return files, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// FindFileByID returns a mediafile given a certain ID.
|
||||
func (s *Search) FindFileByID(id string) (file models.File, err error) {
|
||||
if err := s.db.Where("id = ?", id).Preload("Photo").First(&file).Error; err != nil {
|
||||
@@ -303,12 +312,12 @@ func (s *Search) FindLabelThumbBySlug(labelSlug string) (file models.File, err e
|
||||
}
|
||||
|
||||
// Labels searches labels based on their name.
|
||||
func (s *Search) Labels(form forms.LabelSearchForm) (results []LabelSearchResult, err error) {
|
||||
if err := form.ParseQueryString(); err != nil {
|
||||
func (s *Search) Labels(f form.LabelSearch) (results []LabelSearchResult, err error) {
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", form))
|
||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
||||
|
||||
q := s.db.NewScope(nil).DB()
|
||||
|
||||
@@ -320,15 +329,15 @@ func (s *Search) Labels(form forms.LabelSearchForm) (results []LabelSearchResult
|
||||
Where("labels.deleted_at IS NULL").
|
||||
Group("labels.id")
|
||||
|
||||
if form.Query != "" {
|
||||
if f.Query != "" {
|
||||
var labelIds []uint
|
||||
var categories []models.Category
|
||||
var label models.Label
|
||||
|
||||
likeString := "%" + strings.ToLower(form.Query) + "%"
|
||||
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||
|
||||
if result := s.db.First(&label, "LOWER(label_name) LIKE LOWER(?)", form.Query); result.Error != nil {
|
||||
log.Infof("search: label \"%s\" not found", form.Query)
|
||||
if result := s.db.First(&label, "LOWER(label_name) LIKE LOWER(?)", f.Query); result.Error != nil {
|
||||
log.Infof("search: label \"%s\" not found", f.Query)
|
||||
|
||||
q = q.Where("LOWER(labels.label_name) LIKE ?", likeString)
|
||||
} else {
|
||||
@@ -340,31 +349,31 @@ func (s *Search) Labels(form forms.LabelSearchForm) (results []LabelSearchResult
|
||||
labelIds = append(labelIds, category.LabelID)
|
||||
}
|
||||
|
||||
log.Infof("search: labels %#v", form.Query)
|
||||
log.Infof("search: labels %#v", f.Query)
|
||||
|
||||
q = q.Where("labels.id IN (?) OR LOWER(labels.label_name) LIKE ?", labelIds, likeString)
|
||||
}
|
||||
}
|
||||
|
||||
if form.Favorites {
|
||||
if f.Favorites {
|
||||
q = q.Where("labels.label_favorite = 1")
|
||||
}
|
||||
|
||||
if form.Priority != 0 {
|
||||
q = q.Where("labels.label_priority > ?", form.Priority)
|
||||
if f.Priority != 0 {
|
||||
q = q.Where("labels.label_priority > ?", f.Priority)
|
||||
} else {
|
||||
q = q.Where("labels.label_priority >= -1")
|
||||
}
|
||||
|
||||
switch form.Order {
|
||||
switch f.Order {
|
||||
case "slug":
|
||||
q = q.Order("labels.label_favorite DESC, label_slug ASC")
|
||||
default:
|
||||
q = q.Order("labels.label_favorite DESC, labels.label_priority DESC, label_count DESC, labels.created_at DESC")
|
||||
}
|
||||
|
||||
if form.Count > 0 && form.Count <= 1000 {
|
||||
q = q.Limit(form.Count).Offset(form.Offset)
|
||||
if f.Count > 0 && f.Count <= 1000 {
|
||||
q = q.Limit(f.Count).Offset(f.Offset)
|
||||
} else {
|
||||
q = q.Limit(100).Offset(0)
|
||||
}
|
||||
@@ -402,12 +411,12 @@ func (s *Search) FindAlbumThumbByUUID(albumUUID string) (file models.File, err e
|
||||
}
|
||||
|
||||
// Albums searches albums based on their name.
|
||||
func (s *Search) Albums(form forms.AlbumSearchForm) (results []AlbumSearchResult, err error) {
|
||||
if err := form.ParseQueryString(); err != nil {
|
||||
func (s *Search) Albums(f form.AlbumSearch) (results []AlbumSearchResult, err error) {
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", form))
|
||||
defer util.ProfileTime(time.Now(), fmt.Sprintf("search: %+v", f))
|
||||
|
||||
q := s.db.NewScope(nil).DB()
|
||||
|
||||
@@ -419,24 +428,24 @@ func (s *Search) Albums(form forms.AlbumSearchForm) (results []AlbumSearchResult
|
||||
Where("albums.deleted_at IS NULL").
|
||||
Group("albums.id")
|
||||
|
||||
if form.Query != "" {
|
||||
likeString := "%" + strings.ToLower(form.Query) + "%"
|
||||
if f.Query != "" {
|
||||
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||
q = q.Where("LOWER(albums.album_name) LIKE ?", likeString)
|
||||
}
|
||||
|
||||
if form.Favorites {
|
||||
if f.Favorites {
|
||||
q = q.Where("albums.album_favorite = 1")
|
||||
}
|
||||
|
||||
switch form.Order {
|
||||
switch f.Order {
|
||||
case "slug":
|
||||
q = q.Order("albums.album_favorite DESC, album_slug ASC")
|
||||
default:
|
||||
q = q.Order("albums.album_favorite DESC, album_count DESC, albums.created_at DESC")
|
||||
}
|
||||
|
||||
if form.Count > 0 && form.Count <= 1000 {
|
||||
q = q.Limit(form.Count).Offset(form.Offset)
|
||||
if f.Count > 0 && f.Count <= 1000 {
|
||||
q = q.Limit(f.Count).Offset(f.Offset)
|
||||
} else {
|
||||
q = q.Limit(100).Offset(0)
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/forms"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
)
|
||||
|
||||
func TestSearch_Photos_Query(t *testing.T) {
|
||||
@@ -16,12 +16,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
search := NewSearch(conf.OriginalsPath(), conf.Db())
|
||||
|
||||
t.Run("normal query", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "animal"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "animal"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -30,12 +30,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos[0])
|
||||
})
|
||||
t.Run("label query", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "label:dog"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "label:dog"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -44,25 +44,25 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("invalid label query", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "label:xxx"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "label:xxx"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
assert.Equal(t, err.Error(), "label \"xxx\" not found")
|
||||
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.location true", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = ""
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
form.Location = true
|
||||
var f form.PhotoSearch
|
||||
f.Query = ""
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
f.Location = true
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -71,13 +71,13 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.camera", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = ""
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
form.Camera = 2
|
||||
var f form.PhotoSearch
|
||||
f.Query = ""
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
f.Camera = 2
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -86,13 +86,13 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.color", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = ""
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
form.Color = "blue"
|
||||
var f form.PhotoSearch
|
||||
f.Query = ""
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
f.Color = "blue"
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -101,12 +101,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.favorites", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "favorites:true"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "favorites:true"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -115,12 +115,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.country", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "country:de"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "country:de"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -129,12 +129,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.title", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "title:Pug Dog"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "title:Pug Dog"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -143,12 +143,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.description", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "description:xxx"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "description:xxx"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -157,12 +157,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.notes", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "notes:xxx"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "notes:xxx"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -171,12 +171,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.hash", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "hash:xxx"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "hash:xxx"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -185,12 +185,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.duplicate", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "duplicate:true"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "duplicate:true"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -199,12 +199,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.portrait", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "portrait:true"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "portrait:true"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -213,12 +213,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.mono", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "mono:true"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "mono:true"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -227,12 +227,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.chroma", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "chroma:50"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "chroma:50"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -241,12 +241,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.fmin and Order:oldest", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "Fmin:5 Order:oldest"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "Fmin:5 Order:oldest"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -255,12 +255,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.fmax and Order:newest", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "Fmax:2 Order:newest"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "Fmax:2 Order:newest"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -269,12 +269,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.Lat and form.Long and Order:imported", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "Lat:33.45343166666667 Long:25.764711666666667 Dist:2000 Order:imported"
|
||||
form.Count = 3
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "Lat:33.45343166666667 Long:25.764711666666667 Dist:2000 Order:imported"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -283,12 +283,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||
t.Log(photos)
|
||||
})
|
||||
t.Run("form.Before and form.After", func(t *testing.T) {
|
||||
var form forms.PhotoSearchForm
|
||||
form.Query = "Before:2005-01-01 After:2003-01-01"
|
||||
form.Count = 5000
|
||||
form.Offset = 0
|
||||
var f form.PhotoSearch
|
||||
f.Query = "Before:2005-01-01 After:2003-01-01"
|
||||
f.Count = 5000
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(form)
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@@ -23,6 +23,8 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||
|
||||
api.GetThumbnail(v1, conf)
|
||||
api.GetDownload(v1, conf)
|
||||
api.CreateZip(v1, conf)
|
||||
api.DownloadZip(v1, conf)
|
||||
|
||||
api.GetPhotos(v1, conf)
|
||||
api.LikePhoto(v1, conf)
|
||||
|
61
internal/util/zip.go
Normal file
61
internal/util/zip.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ZipFiles compresses one or many files into a single zip archive file.
|
||||
// Param 1: filename is the output zip file's name.
|
||||
// Param 2: files is a list of files to add to the zip.
|
||||
func ZipFiles(filename string, files []string) error {
|
||||
newZipFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newZipFile.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(newZipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
// Add files to zip
|
||||
for _, file := range files {
|
||||
if err = AddFileToZip(zipWriter, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
|
||||
|
||||
fileToZip, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileToZip.Close()
|
||||
|
||||
// Get the file information
|
||||
info, err := fileToZip.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Change to deflate to gain better compression
|
||||
// see http://golang.org/pkg/archive/zip/#pkg-constants
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, fileToZip)
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user