diff --git a/frontend/src/common/gallery.js b/frontend/src/common/gallery.js
index 1947a844d..f32874487 100644
--- a/frontend/src/common/gallery.js
+++ b/frontend/src/common/gallery.js
@@ -12,7 +12,11 @@ class Gallery {
}
createPhotoSizes(photo) {
- const result = {};
+ const result = {
+ title: photo.PhotoTitle,
+ download_url: photo.getDownloadUrl(),
+ };
+
const thumbs = window.appConfig.thumbnails;
for (let i = 0; i < thumbs.length; i++) {
@@ -22,7 +26,6 @@ class Gallery {
src: photo.getThumbnailUrl(thumbs[i].Name),
w: size.width,
h: size.height,
- title: photo.PhotoTitle,
};
}
@@ -55,24 +58,25 @@ class Gallery {
const shareButtons = [
- {id:"download", label:"Download image", url:"foo", download:true},
+ {id: "download", label: "Download image", url: "{{raw_image_url}}", download: true},
];
const options = {
index: index,
history: false,
- preload: [1,1],
+ preload: [1, 1],
focus: true,
modal: true,
closeEl: true,
captionEl: true,
fullscreenEl: true,
zoomEl: true,
- shareEl: false,
+ shareEl: true,
shareButtons: shareButtons,
counterEl: false,
arrowEl: true,
preloaderEl: true,
+ getImageURLForShare: function() { return gallery.currItem.download_url},
};
let photosWithSizes = this.photosWithSizes();
@@ -115,7 +119,6 @@ class Gallery {
item.src = item[nextSize].src;
item.w = item[nextSize].w;
item.h = item[nextSize].h;
- item.title = item[nextSize].title;
previousSize = nextSize;
});
diff --git a/frontend/src/model/photo.js b/frontend/src/model/photo.js
index 9ec656846..84f7e9a92 100644
--- a/frontend/src/model/photo.js
+++ b/frontend/src/model/photo.js
@@ -38,6 +38,10 @@ class Photo extends Abstract {
return "/api/v1/thumbnails/" + this.FileHash + "/" + type;
}
+ getDownloadUrl() {
+ return "/api/v1/download/" + this.FileHash;
+ }
+
getThumbnailSrcset() {
const result = [];
diff --git a/internal/api/download.go b/internal/api/download.go
new file mode 100644
index 000000000..badc53b51
--- /dev/null
+++ b/internal/api/download.go
@@ -0,0 +1,48 @@
+package api
+
+import (
+ "fmt"
+
+ "github.com/photoprism/photoprism/internal/config"
+ "github.com/photoprism/photoprism/internal/util"
+ log "github.com/sirupsen/logrus"
+
+ "github.com/gin-gonic/gin"
+ "github.com/photoprism/photoprism/internal/photoprism"
+)
+
+// GET /api/v1/download/:hash
+//
+// Parameters:
+// hash: string The file hash as returned by the search API
+func GetDownload(router *gin.RouterGroup, conf *config.Config) {
+ router.GET("/download/:hash", func(c *gin.Context) {
+ fileHash := c.Param("hash")
+
+ search := photoprism.NewSearch(conf.OriginalsPath(), conf.Db())
+ file, err := search.FindFileByHash(fileHash)
+
+ if err != nil {
+ c.AbortWithStatusJSON(404, gin.H{"error": err.Error()})
+ return
+ }
+
+ fileName := fmt.Sprintf("%s/%s", conf.OriginalsPath(), file.FileName)
+
+ if !util.Exists(fileName) {
+ log.Errorf("could not find original: %s", fileHash)
+ c.Data(404, "image/svg+xml", photoIconSvg)
+
+ // Set missing flag so that the file doesn't show up in search results anymore
+ file.FileMissing = true
+ conf.Db().Save(&file)
+ return
+ }
+
+ downloadFileName := file.DownloadFileName(conf.Db())
+
+ c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", downloadFileName))
+
+ c.File(fileName)
+ })
+}
diff --git a/internal/api/svg.go b/internal/api/svg.go
new file mode 100644
index 000000000..f52be2dd7
--- /dev/null
+++ b/internal/api/svg.go
@@ -0,0 +1,7 @@
+package api
+
+var photoIconSvg = []byte(`
+`)
diff --git a/internal/api/thumbnails.go b/internal/api/thumbnails.go
index 929328f9c..1d97c99d6 100644
--- a/internal/api/thumbnails.go
+++ b/internal/api/thumbnails.go
@@ -11,12 +11,6 @@ import (
"github.com/photoprism/photoprism/internal/photoprism"
)
-var photoIconSvg = []byte(`
-`)
-
// GET /api/v1/thumbnails/:hash/:type
//
// Parameters:
diff --git a/internal/models/file.go b/internal/models/file.go
index 52f4852a9..37051e63a 100644
--- a/internal/models/file.go
+++ b/internal/models/file.go
@@ -1,28 +1,44 @@
package models
import (
+ "fmt"
+
+ "github.com/gosimple/slug"
"github.com/jinzhu/gorm"
)
// An image or sidecar file that belongs to a photo
type File struct {
gorm.Model
- Photo *Photo
- PhotoID uint
- FilePrimary bool
- FileMissing bool
- FileDuplicate bool
- FileName string `gorm:"type:varchar(512);index"` // max 3072 bytes / 4 bytes for utf8mb4 = 768 chars
- FileType string `gorm:"type:varchar(32)"`
- FileMime string `gorm:"type:varchar(64)"`
- FileWidth int
- FileHeight int
- FileOrientation int
- FileAspectRatio float64
- FileMainColor string
- FileColors string
- FileLuminance string
- FileSaturation uint
- FileHash string `gorm:"type:varchar(128);unique_index"`
- FileNotes string `gorm:"type:text"`
+ Photo *Photo
+ PhotoID uint
+ FilePrimary bool
+ FileMissing bool
+ FileDuplicate bool
+ FileName string `gorm:"type:varchar(512);index"` // max 3072 bytes / 4 bytes for utf8mb4 = 768 chars
+ FileOriginalName string
+ FileType string `gorm:"type:varchar(32)"`
+ FileMime string `gorm:"type:varchar(64)"`
+ FileWidth int
+ FileHeight int
+ FileOrientation int
+ FileAspectRatio float64
+ FileMainColor string
+ FileColors string
+ FileLuminance string
+ FileSaturation uint
+ FileHash string `gorm:"type:varchar(128);unique_index"`
+ FileNotes string `gorm:"type:text"`
+}
+
+func (f *File) DownloadFileName(db *gorm.DB) string {
+ var photo Photo
+
+ db.Model(f).Related(&photo)
+
+ name := slug.MakeLang(photo.PhotoTitle, "en")
+
+ result := fmt.Sprintf("%s.%s", name, f.FileType)
+
+ return result
}
diff --git a/internal/server/routes.go b/internal/server/routes.go
index 37e550b1a..f00f166b1 100644
--- a/internal/server/routes.go
+++ b/internal/server/routes.go
@@ -20,6 +20,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
{
api.GetPhotos(v1, conf)
api.GetThumbnail(v1, conf)
+ api.GetDownload(v1, conf)
api.LikePhoto(v1, conf)
api.DislikePhoto(v1, conf)
}