mirror of
https://github.com/photoprism/photoprism.git
synced 2025-10-06 01:07:16 +08:00
Albums: Zip download #15
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
@@ -61,7 +61,7 @@
|
|||||||
small
|
small
|
||||||
title="Download"
|
title="Download"
|
||||||
color="teal accent-4"
|
color="teal accent-4"
|
||||||
@click.stop="batchDownload()"
|
@click.stop="downloadZip()"
|
||||||
class="p-photo-clipboard-download"
|
class="p-photo-clipboard-download"
|
||||||
>
|
>
|
||||||
<v-icon>save</v-icon>
|
<v-icon>save</v-icon>
|
||||||
@@ -212,10 +212,15 @@
|
|||||||
Notify.warning("Not implemented yet");
|
Notify.warning("Not implemented yet");
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
},
|
},
|
||||||
batchDownload() {
|
downloadZip() {
|
||||||
Notify.warning("Not implemented yet");
|
Api.post("zip", {"photos": this.selection}).then(this.onDownload.bind(this));
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
},
|
},
|
||||||
|
onDownload(r) {
|
||||||
|
console.log("onDownload", r);
|
||||||
|
Notify.success(r.data.message);
|
||||||
|
window.open("/api/v1/zip/" + r.data.filename, "_blank");
|
||||||
|
},
|
||||||
openDocs() {
|
openDocs() {
|
||||||
window.open('https://docs.photoprism.org/en/latest/', '_blank');
|
window.open('https://docs.photoprism.org/en/latest/', '_blank');
|
||||||
},
|
},
|
||||||
|
@@ -6,12 +6,12 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/models"
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/forms"
|
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
"github.com/photoprism/photoprism/internal/photoprism"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
@@ -19,24 +19,24 @@ import (
|
|||||||
// GET /api/v1/albums
|
// GET /api/v1/albums
|
||||||
func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
func GetAlbums(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.GET("/albums", func(c *gin.Context) {
|
router.GET("/albums", func(c *gin.Context) {
|
||||||
var form forms.AlbumSearchForm
|
var f form.AlbumSearch
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
err := c.MustBindWith(&form, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := search.Albums(form)
|
result, err := search.Albums(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("X-Result-Count", strconv.Itoa(form.Count))
|
c.Header("X-Result-Count", strconv.Itoa(f.Count))
|
||||||
c.Header("X-Result-Offset", strconv.Itoa(form.Offset))
|
c.Header("X-Result-Offset", strconv.Itoa(f.Offset))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
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
|
// POST /api/v1/albums
|
||||||
func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/albums", func(c *gin.Context) {
|
router.POST("/albums", func(c *gin.Context) {
|
||||||
@@ -70,14 +66,14 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := models.NewAlbum(params.AlbumName)
|
m := models.NewAlbum(f.AlbumName)
|
||||||
|
|
||||||
if res := conf.Db().Create(m); res.Error != nil {
|
if res := conf.Db().Create(m); res.Error != nil {
|
||||||
log.Error(res.Error.Error())
|
log.Error(res.Error.Error())
|
||||||
@@ -99,9 +95,9 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -116,7 +112,7 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Rename(params.AlbumName)
|
m.Rename(f.AlbumName)
|
||||||
conf.Db().Save(&m)
|
conf.Db().Save(&m)
|
||||||
|
|
||||||
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||||
@@ -192,14 +188,14 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(params.Photos) == 0 {
|
if len(f.Photos) == 0 {
|
||||||
log.Error("no photos selected")
|
log.Error("no photos selected")
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
return
|
return
|
||||||
@@ -213,13 +209,11 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("adding %d photos to album %s", len(params.Photos), a.AlbumName)
|
|
||||||
|
|
||||||
db := conf.Db()
|
db := conf.Db()
|
||||||
var added []*models.PhotoAlbum
|
var added []*models.PhotoAlbum
|
||||||
var failed []string
|
var failed []string
|
||||||
|
|
||||||
for _, photoUUID := range params.Photos {
|
for _, photoUUID := range f.Photos {
|
||||||
if p, err := search.FindPhotoByUUID(photoUUID); err != nil {
|
if p, err := search.FindPhotoByUUID(photoUUID); err != nil {
|
||||||
failed = append(failed, photoUUID)
|
failed = append(failed, photoUUID)
|
||||||
} else {
|
} else {
|
||||||
@@ -245,14 +239,14 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(params.Photos) == 0 {
|
if len(f.Photos) == 0 {
|
||||||
log.Error("no photos selected")
|
log.Error("no photos selected")
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
return
|
return
|
||||||
@@ -266,14 +260,12 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("adding %d photos to album %s", len(params.Photos), a.AlbumName)
|
|
||||||
|
|
||||||
db := conf.Db()
|
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))
|
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/jinzhu/gorm"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/models"
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PhotoUUIDs struct {
|
|
||||||
Photos []string `json:"photos"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/v1/batch/photos/delete
|
// POST /api/v1/batch/photos/delete
|
||||||
func BatchPhotosDelete(router *gin.RouterGroup, conf *config.Config) {
|
func BatchPhotosDelete(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/batch/photos/delete", func(c *gin.Context) {
|
router.POST("/batch/photos/delete", func(c *gin.Context) {
|
||||||
@@ -27,24 +24,24 @@ func BatchPhotosDelete(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
start := time.Now()
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(params.Photos) == 0 {
|
if len(f.Photos) == 0 {
|
||||||
log.Error("no photos selected")
|
log.Error("no photos selected")
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("deleting photos: %#v", params.Photos)
|
log.Infof("deleting photos: %#v", f.Photos)
|
||||||
|
|
||||||
db := conf.Db()
|
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)
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
@@ -62,24 +59,24 @@ func BatchPhotosPrivate(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
start := time.Now()
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(params.Photos) == 0 {
|
if len(f.Photos) == 0 {
|
||||||
log.Error("no photos selected")
|
log.Error("no photos selected")
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("marking photos as private: %#v", params.Photos)
|
log.Infof("marking photos as private: %#v", f.Photos)
|
||||||
|
|
||||||
db := conf.Db()
|
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)
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
@@ -97,25 +94,25 @@ func BatchPhotosStory(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
start := time.Now()
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(params.Photos) == 0 {
|
if len(f.Photos) == 0 {
|
||||||
log.Error("no photos selected")
|
log.Error("no photos selected")
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("marking photos as story: %#v", params.Photos)
|
log.Infof("marking photos as story: %#v", f.Photos)
|
||||||
|
|
||||||
db := conf.Db()
|
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)"),
|
"photo_story": gorm.Expr("IF (`photo_story`, 0, 1)"),
|
||||||
})
|
})
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
|
@@ -10,6 +10,10 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
"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
|
// GET /api/v1/download/:hash
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
|
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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/photoprism"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
@@ -15,24 +15,24 @@ import (
|
|||||||
// GET /api/v1/labels
|
// GET /api/v1/labels
|
||||||
func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
func GetLabels(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.GET("/labels", func(c *gin.Context) {
|
router.GET("/labels", func(c *gin.Context) {
|
||||||
var form forms.LabelSearchForm
|
var f form.LabelSearch
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
err := c.MustBindWith(&form, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := search.Labels(form)
|
result, err := search.Labels(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("X-Result-Count", strconv.Itoa(form.Count))
|
c.Header("X-Result-Count", strconv.Itoa(f.Count))
|
||||||
c.Header("X-Result-Offset", strconv.Itoa(form.Offset))
|
c.Header("X-Result-Offset", strconv.Itoa(f.Offset))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
c.JSON(http.StatusOK, result)
|
||||||
})
|
})
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/photoprism/photoprism/internal/forms"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/photoprism"
|
"github.com/photoprism/photoprism/internal/photoprism"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,24 +29,24 @@ import (
|
|||||||
// favorites: bool Find favorites only
|
// favorites: bool Find favorites only
|
||||||
func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.GET("/photos", func(c *gin.Context) {
|
router.GET("/photos", func(c *gin.Context) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
|
|
||||||
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
err := c.MustBindWith(&form, binding.Form)
|
err := c.MustBindWith(&f, binding.Form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := search.Photos(form)
|
result, err := search.Photos(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(400, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("X-Result-Count", strconv.Itoa(form.Count))
|
c.Header("X-Result-Count", strconv.Itoa(f.Count))
|
||||||
c.Header("X-Result-Offset", strconv.Itoa(form.Offset))
|
c.Header("X-Result-Offset", strconv.Itoa(f.Offset))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
c.JSON(http.StatusOK, result)
|
||||||
})
|
})
|
||||||
|
@@ -6,25 +6,21 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateSessionParams struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/v1/session
|
// POST /api/v1/session
|
||||||
func CreateSession(router *gin.RouterGroup, conf *config.Config) {
|
func CreateSession(router *gin.RouterGroup, conf *config.Config) {
|
||||||
router.POST("/session", func(c *gin.Context) {
|
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())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Password != conf.AdminPassword() {
|
if f.Password != conf.AdminPassword() {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": "Invalid password"})
|
c.AbortWithStatusJSON(400, gin.H{"error": "Invalid password"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -29,14 +29,14 @@ func Upload(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
subPath := c.Param("path")
|
subPath := c.Param("path")
|
||||||
|
|
||||||
form, err := c.MultipartForm()
|
f, err := c.MultipartForm()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
files := form.File["files"]
|
files := f.File["files"]
|
||||||
|
|
||||||
path := fmt.Sprintf("%s/upload/%s", conf.ImportPath(), subPath)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Query parameters for GET /api/v1/albums
|
// Query parameters for GET /api/v1/albums
|
||||||
type AlbumSearchForm struct {
|
type AlbumSearch struct {
|
||||||
Query string `form:"q"`
|
Query string `form:"q"`
|
||||||
|
|
||||||
Slug string `form:"slug"`
|
Slug string `form:"slug"`
|
||||||
@@ -27,7 +27,7 @@ type AlbumSearchForm struct {
|
|||||||
Order string `form:"order"`
|
Order string `form:"order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *AlbumSearchForm) ParseQueryString() (result error) {
|
func (f *AlbumSearch) ParseQueryString() (result error) {
|
||||||
var key, value []byte
|
var key, value []byte
|
||||||
var escaped, isKeyValue bool
|
var escaped, isKeyValue bool
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package forms
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -7,15 +7,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAlbumSearchForm(t *testing.T) {
|
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) {
|
func TestParseQueryStringAlbum(t *testing.T) {
|
||||||
|
|
||||||
t.Run("valid query", func(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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
|||||||
assert.Equal(t, 10, form.Count)
|
assert.Equal(t, 10, form.Count)
|
||||||
})
|
})
|
||||||
t.Run("valid query 2", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
|||||||
assert.Equal(t, "query text", form.Query)
|
assert.Equal(t, "query text", form.Query)
|
||||||
})
|
})
|
||||||
t.Run("query for invalid filter", func(t *testing.T) {
|
t.Run("query for invalid filter", func(t *testing.T) {
|
||||||
form := &AlbumSearchForm{Query: "xxx:false"}
|
form := &AlbumSearch{Query: "xxx:false"}
|
||||||
|
|
||||||
err := form.ParseQueryString()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
|||||||
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for favorites with invalid type", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
|
|||||||
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for count with invalid type", func(t *testing.T) {
|
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()
|
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:
|
Additional information can be found in our Developer Guide:
|
||||||
|
|
||||||
https://github.com/photoprism/photoprism/wiki
|
https://github.com/photoprism/photoprism/wiki
|
||||||
*/
|
*/
|
||||||
package forms
|
package form
|
@@ -1,4 +1,4 @@
|
|||||||
package forms
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
@@ -1,4 +1,4 @@
|
|||||||
package forms
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Query parameters for GET /api/v1/labels
|
// Query parameters for GET /api/v1/labels
|
||||||
type LabelSearchForm struct {
|
type LabelSearch struct {
|
||||||
Query string `form:"q"`
|
Query string `form:"q"`
|
||||||
|
|
||||||
Slug string `form:"slug"`
|
Slug string `form:"slug"`
|
||||||
@@ -28,7 +28,7 @@ type LabelSearchForm struct {
|
|||||||
Order string `form:"order"`
|
Order string `form:"order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *LabelSearchForm) ParseQueryString() (result error) {
|
func (f *LabelSearch) ParseQueryString() (result error) {
|
||||||
var key, value []byte
|
var key, value []byte
|
||||||
var escaped, isKeyValue bool
|
var escaped, isKeyValue bool
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package forms
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -7,15 +7,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestLabelSearchForm(t *testing.T) {
|
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) {
|
func TestParseQueryStringLabel(t *testing.T) {
|
||||||
|
|
||||||
t.Run("valid query", func(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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
|||||||
assert.Equal(t, "query text", form.Query)
|
assert.Equal(t, "query text", form.Query)
|
||||||
})
|
})
|
||||||
t.Run("valid query 2", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
|||||||
assert.Equal(t, "oldest", form.Order)
|
assert.Equal(t, "oldest", form.Order)
|
||||||
})
|
})
|
||||||
t.Run("query for invalid filter", func(t *testing.T) {
|
t.Run("query for invalid filter", func(t *testing.T) {
|
||||||
form := &LabelSearchForm{Query: "xxx:false"}
|
form := &LabelSearch{Query: "xxx:false"}
|
||||||
|
|
||||||
err := form.ParseQueryString()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
|||||||
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for favorites with invalid type", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ func TestParseQueryStringLabel(t *testing.T) {
|
|||||||
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for count with invalid type", func(t *testing.T) {
|
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()
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Query parameters for GET /api/v1/photos
|
// Query parameters for GET /api/v1/photos
|
||||||
type PhotoSearchForm struct {
|
type PhotoSearch struct {
|
||||||
Query string `form:"q"`
|
Query string `form:"q"`
|
||||||
|
|
||||||
Title string `form:"title"`
|
Title string `form:"title"`
|
||||||
@@ -47,7 +47,7 @@ type PhotoSearchForm struct {
|
|||||||
Order string `form:"order"`
|
Order string `form:"order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *PhotoSearchForm) ParseQueryString() (result error) {
|
func (f *PhotoSearch) ParseQueryString() (result error) {
|
||||||
var key, value []byte
|
var key, value []byte
|
||||||
var escaped, isKeyValue bool
|
var escaped, isKeyValue bool
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package forms
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -10,15 +10,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPhotoSearchForm(t *testing.T) {
|
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) {
|
func TestParseQueryString(t *testing.T) {
|
||||||
|
|
||||||
t.Run("valid query", func(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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, 33.45343166666667, form.Lat)
|
assert.Equal(t, 33.45343166666667, form.Lat)
|
||||||
})
|
})
|
||||||
t.Run("valid query 2", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, 33.45343166666667, form.Long)
|
assert.Equal(t, 33.45343166666667, form.Long)
|
||||||
})
|
})
|
||||||
t.Run("query for invalid filter", func(t *testing.T) {
|
t.Run("query for invalid filter", func(t *testing.T) {
|
||||||
form := &PhotoSearchForm{Query: "xxx:false"}
|
form := &PhotoSearch{Query: "xxx:false"}
|
||||||
|
|
||||||
err := form.ParseQueryString()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
assert.Equal(t, "unknown filter: Xxx", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for favorites with invalid type", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
assert.Equal(t, "not a bool value: Favorites", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for lat with invalid type", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, "strconv.ParseFloat: parsing \"cat\": invalid syntax", err.Error())
|
assert.Equal(t, "strconv.ParseFloat: parsing \"cat\": invalid syntax", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for dist with invalid type", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, "strconv.Atoi: parsing \"cat\": invalid syntax", err.Error())
|
assert.Equal(t, "strconv.Atoi: parsing \"cat\": invalid syntax", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for camera with invalid type", func(t *testing.T) {
|
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()
|
err := form.ParseQueryString()
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ func TestParseQueryString(t *testing.T) {
|
|||||||
assert.Equal(t, "strconv.Atoi: parsing \"cat\": invalid syntax", err.Error())
|
assert.Equal(t, "strconv.Atoi: parsing \"cat\": invalid syntax", err.Error())
|
||||||
})
|
})
|
||||||
t.Run("query for before with invalid type", func(t *testing.T) {
|
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()
|
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/gosimple/slug"
|
||||||
"github.com/jinzhu/gorm"
|
"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/models"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"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.
|
// Photos searches for photos based on a Form and returns a PhotoSearchResult slice.
|
||||||
func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult, err error) {
|
func (s *Search) Photos(f form.PhotoSearch) (results []PhotoSearchResult, err error) {
|
||||||
if err := form.ParseQueryString(); err != nil {
|
if err := f.ParseQueryString(); err != nil {
|
||||||
return results, err
|
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()
|
q := s.db.NewScope(nil).DB()
|
||||||
|
|
||||||
@@ -72,10 +72,10 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
|||||||
var label models.Label
|
var label models.Label
|
||||||
var labelIds []uint
|
var labelIds []uint
|
||||||
|
|
||||||
if form.Label != "" {
|
if f.Label != "" {
|
||||||
if result := s.db.First(&label, "label_slug = ?", strings.ToLower(form.Label)); result.Error != nil {
|
if result := s.db.First(&label, "label_slug = ?", strings.ToLower(f.Label)); result.Error != nil {
|
||||||
log.Errorf("search: label \"%s\" not found", form.Label)
|
log.Errorf("search: label \"%s\" not found", f.Label)
|
||||||
return results, fmt.Errorf("label \"%s\" not found", form.Label)
|
return results, fmt.Errorf("label \"%s\" not found", f.Label)
|
||||||
} else {
|
} else {
|
||||||
labelIds = append(labelIds, label.ID)
|
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")
|
q = q.Where("location_id > 0")
|
||||||
|
|
||||||
if form.Query != "" {
|
if f.Query != "" {
|
||||||
likeString := "%" + strings.ToLower(form.Query) + "%"
|
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||||
q = q.Where("LOWER(locations.loc_display_name) LIKE ?", likeString)
|
q = q.Where("LOWER(locations.loc_display_name) LIKE ?", likeString)
|
||||||
}
|
}
|
||||||
} else if form.Query != "" {
|
} else if f.Query != "" {
|
||||||
slugString := slug.Make(form.Query)
|
slugString := slug.Make(f.Query)
|
||||||
lowerString := strings.ToLower(form.Query)
|
lowerString := strings.ToLower(f.Query)
|
||||||
likeString := "%" + lowerString + "%"
|
likeString := "%" + lowerString + "%"
|
||||||
|
|
||||||
if result := s.db.First(&label, "label_slug = ?", slugString); result.Error != nil {
|
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)
|
q = q.Where("labels.label_slug = ? OR LOWER(photo_title) LIKE ? OR files.file_main_color = ?", slugString, likeString, lowerString)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("search: label \"%s\"", form.Query)
|
log.Infof("search: label \"%s\"", f.Query)
|
||||||
|
|
||||||
labelIds = append(labelIds, label.ID)
|
labelIds = append(labelIds, label.ID)
|
||||||
|
|
||||||
@@ -121,94 +121,94 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Album != "" {
|
if f.Album != "" {
|
||||||
q = q.Joins("JOIN photos_albums ON photos_albums.photo_uuid = photos.photo_uuid").Where("photos_albums.album_uuid = ?", form.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 {
|
if f.Camera > 0 {
|
||||||
q = q.Where("photos.camera_id = ?", form.Camera)
|
q = q.Where("photos.camera_id = ?", f.Camera)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Color != "" {
|
if f.Color != "" {
|
||||||
q = q.Where("files.file_main_color = ?", strings.ToLower(form.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")
|
q = q.Where("photos.photo_favorite = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Country != "" {
|
if f.Country != "" {
|
||||||
q = q.Where("locations.loc_country_code = ?", form.Country)
|
q = q.Where("locations.loc_country_code = ?", f.Country)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Title != "" {
|
if f.Title != "" {
|
||||||
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(form.Title)))
|
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Title)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Description != "" {
|
if f.Description != "" {
|
||||||
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(form.Description)))
|
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Description)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Notes != "" {
|
if f.Notes != "" {
|
||||||
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(form.Notes)))
|
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Notes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Hash != "" {
|
if f.Hash != "" {
|
||||||
q = q.Where("files.file_hash = ?", form.Hash)
|
q = q.Where("files.file_hash = ?", f.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Duplicate {
|
if f.Duplicate {
|
||||||
q = q.Where("files.file_duplicate = 1")
|
q = q.Where("files.file_duplicate = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Portrait {
|
if f.Portrait {
|
||||||
q = q.Where("files.file_portrait = 1")
|
q = q.Where("files.file_portrait = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Mono {
|
if f.Mono {
|
||||||
q = q.Where("files.file_chroma = 0")
|
q = q.Where("files.file_chroma = 0")
|
||||||
} else if form.Chroma > 9 {
|
} else if f.Chroma > 9 {
|
||||||
q = q.Where("files.file_chroma > ?", form.Chroma)
|
q = q.Where("files.file_chroma > ?", f.Chroma)
|
||||||
} else if form.Chroma > 0 {
|
} else if f.Chroma > 0 {
|
||||||
q = q.Where("files.file_chroma > 0 AND files.file_chroma <= ?", form.Chroma)
|
q = q.Where("files.file_chroma > 0 AND files.file_chroma <= ?", f.Chroma)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Fmin > 0 {
|
if f.Fmin > 0 {
|
||||||
q = q.Where("photos.photo_f_number >= ?", form.Fmin)
|
q = q.Where("photos.photo_f_number >= ?", f.Fmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Fmax > 0 {
|
if f.Fmax > 0 {
|
||||||
q = q.Where("photos.photo_f_number <= ?", form.Fmax)
|
q = q.Where("photos.photo_f_number <= ?", f.Fmax)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Dist == 0 {
|
if f.Dist == 0 {
|
||||||
form.Dist = 20
|
f.Dist = 20
|
||||||
} else if form.Dist > 1000 {
|
} else if f.Dist > 1000 {
|
||||||
form.Dist = 1000
|
f.Dist = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inaccurate distance search, but probably 'good enough' for now
|
// Inaccurate distance search, but probably 'good enough' for now
|
||||||
if form.Lat > 0 {
|
if f.Lat > 0 {
|
||||||
latMin := form.Lat - SearchRadius*float64(form.Dist)
|
latMin := f.Lat - SearchRadius*float64(f.Dist)
|
||||||
latMax := form.Lat + SearchRadius*float64(form.Dist)
|
latMax := f.Lat + SearchRadius*float64(f.Dist)
|
||||||
q = q.Where("photos.photo_lat BETWEEN ? AND ?", latMin, latMax)
|
q = q.Where("photos.photo_lat BETWEEN ? AND ?", latMin, latMax)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Long > 0 {
|
if f.Long > 0 {
|
||||||
longMin := form.Long - SearchRadius*float64(form.Dist)
|
longMin := f.Long - SearchRadius*float64(f.Dist)
|
||||||
longMax := form.Long + SearchRadius*float64(form.Dist)
|
longMax := f.Long + SearchRadius*float64(f.Dist)
|
||||||
q = q.Where("photos.photo_long BETWEEN ? AND ?", longMin, longMax)
|
q = q.Where("photos.photo_long BETWEEN ? AND ?", longMin, longMax)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.Before.IsZero() {
|
if !f.Before.IsZero() {
|
||||||
q = q.Where("photos.taken_at <= ?", form.Before.Format("2006-01-02"))
|
q = q.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.After.IsZero() {
|
if !f.After.IsZero() {
|
||||||
q = q.Where("photos.taken_at >= ?", form.After.Format("2006-01-02"))
|
q = q.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch form.Order {
|
switch f.Order {
|
||||||
case "newest":
|
case "newest":
|
||||||
q = q.Order("taken_at DESC")
|
q = q.Order("taken_at DESC")
|
||||||
case "oldest":
|
case "oldest":
|
||||||
@@ -219,8 +219,8 @@ func (s *Search) Photos(form forms.PhotoSearchForm) (results []PhotoSearchResult
|
|||||||
q = q.Order("taken_at DESC")
|
q = q.Order("taken_at DESC")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Count > 0 && form.Count <= 1000 {
|
if f.Count > 0 && f.Count <= 1000 {
|
||||||
q = q.Limit(form.Count).Offset(form.Offset)
|
q = q.Limit(f.Count).Offset(f.Offset)
|
||||||
} else {
|
} else {
|
||||||
q = q.Limit(100).Offset(0)
|
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
|
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.
|
// FindFileByID returns a mediafile given a certain ID.
|
||||||
func (s *Search) FindFileByID(id string) (file models.File, err error) {
|
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 {
|
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.
|
// Labels searches labels based on their name.
|
||||||
func (s *Search) Labels(form forms.LabelSearchForm) (results []LabelSearchResult, err error) {
|
func (s *Search) Labels(f form.LabelSearch) (results []LabelSearchResult, err error) {
|
||||||
if err := form.ParseQueryString(); err != nil {
|
if err := f.ParseQueryString(); err != nil {
|
||||||
return results, err
|
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()
|
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").
|
Where("labels.deleted_at IS NULL").
|
||||||
Group("labels.id")
|
Group("labels.id")
|
||||||
|
|
||||||
if form.Query != "" {
|
if f.Query != "" {
|
||||||
var labelIds []uint
|
var labelIds []uint
|
||||||
var categories []models.Category
|
var categories []models.Category
|
||||||
var label models.Label
|
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 {
|
if result := s.db.First(&label, "LOWER(label_name) LIKE LOWER(?)", f.Query); result.Error != nil {
|
||||||
log.Infof("search: label \"%s\" not found", form.Query)
|
log.Infof("search: label \"%s\" not found", f.Query)
|
||||||
|
|
||||||
q = q.Where("LOWER(labels.label_name) LIKE ?", likeString)
|
q = q.Where("LOWER(labels.label_name) LIKE ?", likeString)
|
||||||
} else {
|
} else {
|
||||||
@@ -340,31 +349,31 @@ func (s *Search) Labels(form forms.LabelSearchForm) (results []LabelSearchResult
|
|||||||
labelIds = append(labelIds, category.LabelID)
|
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)
|
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")
|
q = q.Where("labels.label_favorite = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Priority != 0 {
|
if f.Priority != 0 {
|
||||||
q = q.Where("labels.label_priority > ?", form.Priority)
|
q = q.Where("labels.label_priority > ?", f.Priority)
|
||||||
} else {
|
} else {
|
||||||
q = q.Where("labels.label_priority >= -1")
|
q = q.Where("labels.label_priority >= -1")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch form.Order {
|
switch f.Order {
|
||||||
case "slug":
|
case "slug":
|
||||||
q = q.Order("labels.label_favorite DESC, label_slug ASC")
|
q = q.Order("labels.label_favorite DESC, label_slug ASC")
|
||||||
default:
|
default:
|
||||||
q = q.Order("labels.label_favorite DESC, labels.label_priority DESC, label_count DESC, labels.created_at DESC")
|
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 {
|
if f.Count > 0 && f.Count <= 1000 {
|
||||||
q = q.Limit(form.Count).Offset(form.Offset)
|
q = q.Limit(f.Count).Offset(f.Offset)
|
||||||
} else {
|
} else {
|
||||||
q = q.Limit(100).Offset(0)
|
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.
|
// Albums searches albums based on their name.
|
||||||
func (s *Search) Albums(form forms.AlbumSearchForm) (results []AlbumSearchResult, err error) {
|
func (s *Search) Albums(f form.AlbumSearch) (results []AlbumSearchResult, err error) {
|
||||||
if err := form.ParseQueryString(); err != nil {
|
if err := f.ParseQueryString(); err != nil {
|
||||||
return results, err
|
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()
|
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").
|
Where("albums.deleted_at IS NULL").
|
||||||
Group("albums.id")
|
Group("albums.id")
|
||||||
|
|
||||||
if form.Query != "" {
|
if f.Query != "" {
|
||||||
likeString := "%" + strings.ToLower(form.Query) + "%"
|
likeString := "%" + strings.ToLower(f.Query) + "%"
|
||||||
q = q.Where("LOWER(albums.album_name) LIKE ?", likeString)
|
q = q.Where("LOWER(albums.album_name) LIKE ?", likeString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Favorites {
|
if f.Favorites {
|
||||||
q = q.Where("albums.album_favorite = 1")
|
q = q.Where("albums.album_favorite = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch form.Order {
|
switch f.Order {
|
||||||
case "slug":
|
case "slug":
|
||||||
q = q.Order("albums.album_favorite DESC, album_slug ASC")
|
q = q.Order("albums.album_favorite DESC, album_slug ASC")
|
||||||
default:
|
default:
|
||||||
q = q.Order("albums.album_favorite DESC, album_count DESC, albums.created_at DESC")
|
q = q.Order("albums.album_favorite DESC, album_count DESC, albums.created_at DESC")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Count > 0 && form.Count <= 1000 {
|
if f.Count > 0 && f.Count <= 1000 {
|
||||||
q = q.Limit(form.Count).Offset(form.Offset)
|
q = q.Limit(f.Count).Offset(f.Offset)
|
||||||
} else {
|
} else {
|
||||||
q = q.Limit(100).Offset(0)
|
q = q.Limit(100).Offset(0)
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"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) {
|
func TestSearch_Photos_Query(t *testing.T) {
|
||||||
@@ -16,12 +16,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
search := NewSearch(conf.OriginalsPath(), conf.Db())
|
search := NewSearch(conf.OriginalsPath(), conf.Db())
|
||||||
|
|
||||||
t.Run("normal query", func(t *testing.T) {
|
t.Run("normal query", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "animal"
|
f.Query = "animal"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -30,12 +30,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos[0])
|
t.Log(photos[0])
|
||||||
})
|
})
|
||||||
t.Run("label query", func(t *testing.T) {
|
t.Run("label query", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "label:dog"
|
f.Query = "label:dog"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -44,25 +44,25 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("invalid label query", func(t *testing.T) {
|
t.Run("invalid label query", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "label:xxx"
|
f.Query = "label:xxx"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
assert.Equal(t, err.Error(), "label \"xxx\" not found")
|
assert.Equal(t, err.Error(), "label \"xxx\" not found")
|
||||||
|
|
||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.location true", func(t *testing.T) {
|
t.Run("form.location true", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = ""
|
f.Query = ""
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
form.Location = true
|
f.Location = true
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -71,13 +71,13 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.camera", func(t *testing.T) {
|
t.Run("form.camera", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = ""
|
f.Query = ""
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
form.Camera = 2
|
f.Camera = 2
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -86,13 +86,13 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.color", func(t *testing.T) {
|
t.Run("form.color", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = ""
|
f.Query = ""
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
form.Color = "blue"
|
f.Color = "blue"
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -101,12 +101,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.favorites", func(t *testing.T) {
|
t.Run("form.favorites", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "favorites:true"
|
f.Query = "favorites:true"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -115,12 +115,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.country", func(t *testing.T) {
|
t.Run("form.country", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "country:de"
|
f.Query = "country:de"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -129,12 +129,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.title", func(t *testing.T) {
|
t.Run("form.title", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "title:Pug Dog"
|
f.Query = "title:Pug Dog"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -143,12 +143,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.description", func(t *testing.T) {
|
t.Run("form.description", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "description:xxx"
|
f.Query = "description:xxx"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -157,12 +157,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.notes", func(t *testing.T) {
|
t.Run("form.notes", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "notes:xxx"
|
f.Query = "notes:xxx"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -171,12 +171,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.hash", func(t *testing.T) {
|
t.Run("form.hash", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "hash:xxx"
|
f.Query = "hash:xxx"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -185,12 +185,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.duplicate", func(t *testing.T) {
|
t.Run("form.duplicate", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "duplicate:true"
|
f.Query = "duplicate:true"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -199,12 +199,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.portrait", func(t *testing.T) {
|
t.Run("form.portrait", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "portrait:true"
|
f.Query = "portrait:true"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -213,12 +213,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.mono", func(t *testing.T) {
|
t.Run("form.mono", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "mono:true"
|
f.Query = "mono:true"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -227,12 +227,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.chroma", func(t *testing.T) {
|
t.Run("form.chroma", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "chroma:50"
|
f.Query = "chroma:50"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -241,12 +241,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.fmin and Order:oldest", func(t *testing.T) {
|
t.Run("form.fmin and Order:oldest", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "Fmin:5 Order:oldest"
|
f.Query = "Fmin:5 Order:oldest"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -255,12 +255,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.fmax and Order:newest", func(t *testing.T) {
|
t.Run("form.fmax and Order:newest", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "Fmax:2 Order:newest"
|
f.Query = "Fmax:2 Order:newest"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -269,12 +269,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.Lat and form.Long and Order:imported", func(t *testing.T) {
|
t.Run("form.Lat and form.Long and Order:imported", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "Lat:33.45343166666667 Long:25.764711666666667 Dist:2000 Order:imported"
|
f.Query = "Lat:33.45343166666667 Long:25.764711666666667 Dist:2000 Order:imported"
|
||||||
form.Count = 3
|
f.Count = 3
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -283,12 +283,12 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
})
|
})
|
||||||
t.Run("form.Before and form.After", func(t *testing.T) {
|
t.Run("form.Before and form.After", func(t *testing.T) {
|
||||||
var form forms.PhotoSearchForm
|
var f form.PhotoSearch
|
||||||
form.Query = "Before:2005-01-01 After:2003-01-01"
|
f.Query = "Before:2005-01-01 After:2003-01-01"
|
||||||
form.Count = 5000
|
f.Count = 5000
|
||||||
form.Offset = 0
|
f.Offset = 0
|
||||||
|
|
||||||
photos, err := search.Photos(form)
|
photos, err := search.Photos(f)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@@ -23,6 +23,8 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||||||
|
|
||||||
api.GetThumbnail(v1, conf)
|
api.GetThumbnail(v1, conf)
|
||||||
api.GetDownload(v1, conf)
|
api.GetDownload(v1, conf)
|
||||||
|
api.CreateZip(v1, conf)
|
||||||
|
api.DownloadZip(v1, conf)
|
||||||
|
|
||||||
api.GetPhotos(v1, conf)
|
api.GetPhotos(v1, conf)
|
||||||
api.LikePhoto(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