Preview URL
parent
38788bb2e9
commit
2930c4ff62
|
@ -26,6 +26,7 @@ const (
|
||||||
attachment_type TEXT NOT NULL,
|
attachment_type TEXT NOT NULL,
|
||||||
attachment_size INT NOT NULL,
|
attachment_size INT NOT NULL,
|
||||||
attachment_expires INT NOT NULL,
|
attachment_expires INT NOT NULL,
|
||||||
|
attachment_preview_url TEXT NOT NULL,
|
||||||
attachment_url TEXT NOT NULL,
|
attachment_url TEXT NOT NULL,
|
||||||
published INT NOT NULL
|
published INT NOT NULL
|
||||||
);
|
);
|
||||||
|
@ -33,24 +34,24 @@ const (
|
||||||
COMMIT;
|
COMMIT;
|
||||||
`
|
`
|
||||||
insertMessageQuery = `
|
insertMessageQuery = `
|
||||||
INSERT INTO messages (id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, published)
|
INSERT INTO messages (id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url, published)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
||||||
selectMessagesSinceTimeQuery = `
|
selectMessagesSinceTimeQuery = `
|
||||||
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url
|
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND time >= ? AND published = 1
|
WHERE topic = ? AND time >= ? AND published = 1
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
`
|
`
|
||||||
selectMessagesSinceTimeIncludeScheduledQuery = `
|
selectMessagesSinceTimeIncludeScheduledQuery = `
|
||||||
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url
|
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND time >= ?
|
WHERE topic = ? AND time >= ?
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
`
|
`
|
||||||
selectMessagesDueQuery = `
|
selectMessagesDueQuery = `
|
||||||
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url
|
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE time <= ? AND published = 0
|
WHERE time <= ? AND published = 0
|
||||||
`
|
`
|
||||||
|
@ -124,13 +125,14 @@ func (c *sqliteCache) AddMessage(m *message) error {
|
||||||
}
|
}
|
||||||
published := m.Time <= time.Now().Unix()
|
published := m.Time <= time.Now().Unix()
|
||||||
tags := strings.Join(m.Tags, ",")
|
tags := strings.Join(m.Tags, ",")
|
||||||
var attachmentName, attachmentType, attachmentURL string
|
var attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
|
||||||
var attachmentSize, attachmentExpires int64
|
var attachmentSize, attachmentExpires int64
|
||||||
if m.Attachment != nil {
|
if m.Attachment != nil {
|
||||||
attachmentName = m.Attachment.Name
|
attachmentName = m.Attachment.Name
|
||||||
attachmentType = m.Attachment.Type
|
attachmentType = m.Attachment.Type
|
||||||
attachmentSize = m.Attachment.Size
|
attachmentSize = m.Attachment.Size
|
||||||
attachmentExpires = m.Attachment.Expires
|
attachmentExpires = m.Attachment.Expires
|
||||||
|
attachmentPreviewURL = m.Attachment.PreviewURL
|
||||||
attachmentURL = m.Attachment.URL
|
attachmentURL = m.Attachment.URL
|
||||||
}
|
}
|
||||||
_, err := c.db.Exec(
|
_, err := c.db.Exec(
|
||||||
|
@ -146,6 +148,7 @@ func (c *sqliteCache) AddMessage(m *message) error {
|
||||||
attachmentType,
|
attachmentType,
|
||||||
attachmentSize,
|
attachmentSize,
|
||||||
attachmentExpires,
|
attachmentExpires,
|
||||||
|
attachmentPreviewURL,
|
||||||
attachmentURL,
|
attachmentURL,
|
||||||
published,
|
published,
|
||||||
)
|
)
|
||||||
|
@ -231,8 +234,8 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var timestamp, attachmentSize, attachmentExpires int64
|
var timestamp, attachmentSize, attachmentExpires int64
|
||||||
var priority int
|
var priority int
|
||||||
var id, topic, msg, title, tagsStr, attachmentName, attachmentType, attachmentURL string
|
var id, topic, msg, title, tagsStr, attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
|
||||||
if err := rows.Scan(&id, ×tamp, &topic, &msg, &title, &priority, &tagsStr, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentURL); err != nil {
|
if err := rows.Scan(&id, ×tamp, &topic, &msg, &title, &priority, &tagsStr, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentPreviewURL, &attachmentURL); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var tags []string
|
var tags []string
|
||||||
|
@ -246,6 +249,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
Type: attachmentType,
|
Type: attachmentType,
|
||||||
Size: attachmentSize,
|
Size: attachmentSize,
|
||||||
Expires: attachmentExpires,
|
Expires: attachmentExpires,
|
||||||
|
PreviewURL: attachmentPreviewURL,
|
||||||
URL: attachmentURL,
|
URL: attachmentURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,9 @@ const (
|
||||||
DefaultAtSenderInterval = 10 * time.Second
|
DefaultAtSenderInterval = 10 * time.Second
|
||||||
DefaultMinDelay = 10 * time.Second
|
DefaultMinDelay = 10 * time.Second
|
||||||
DefaultMaxDelay = 3 * 24 * time.Hour
|
DefaultMaxDelay = 3 * 24 * time.Hour
|
||||||
DefaultMessageLimit = 4096
|
DefaultMessageLimit = 4096 // Bytes
|
||||||
DefaultAttachmentSizeLimit = 5 * 1024 * 1024
|
DefaultAttachmentSizeLimit = 15 * 1024 * 1024
|
||||||
|
DefaultAttachmentSizePreviewMax = 20 * 1024 * 1024 // Bytes
|
||||||
DefaultAttachmentExpiryDuration = 3 * time.Hour
|
DefaultAttachmentExpiryDuration = 3 * time.Hour
|
||||||
DefaultFirebaseKeepaliveInterval = 3 * time.Hour // Not too frequently to save battery
|
DefaultFirebaseKeepaliveInterval = 3 * time.Hour // Not too frequently to save battery
|
||||||
)
|
)
|
||||||
|
@ -48,6 +49,7 @@ type Config struct {
|
||||||
CacheDuration time.Duration
|
CacheDuration time.Duration
|
||||||
AttachmentCacheDir string
|
AttachmentCacheDir string
|
||||||
AttachmentSizeLimit int64
|
AttachmentSizeLimit int64
|
||||||
|
AttachmentSizePreviewMax int64
|
||||||
AttachmentExpiryDuration time.Duration
|
AttachmentExpiryDuration time.Duration
|
||||||
KeepaliveInterval time.Duration
|
KeepaliveInterval time.Duration
|
||||||
ManagerInterval time.Duration
|
ManagerInterval time.Duration
|
||||||
|
@ -88,6 +90,7 @@ func NewConfig() *Config {
|
||||||
CacheDuration: DefaultCacheDuration,
|
CacheDuration: DefaultCacheDuration,
|
||||||
AttachmentCacheDir: "",
|
AttachmentCacheDir: "",
|
||||||
AttachmentSizeLimit: DefaultAttachmentSizeLimit,
|
AttachmentSizeLimit: DefaultAttachmentSizeLimit,
|
||||||
|
AttachmentSizePreviewMax: DefaultAttachmentSizePreviewMax,
|
||||||
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
|
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
|
||||||
KeepaliveInterval: DefaultKeepaliveInterval,
|
KeepaliveInterval: DefaultKeepaliveInterval,
|
||||||
ManagerInterval: DefaultManagerInterval,
|
ManagerInterval: DefaultManagerInterval,
|
||||||
|
|
|
@ -34,6 +34,7 @@ type attachment struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Expires int64 `json:"expires"`
|
Expires int64 `json:"expires"`
|
||||||
|
PreviewURL string `json:"preview_url"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -233,6 +232,7 @@ func createFirebaseSubscriber(conf *Config) (subscriber, error) {
|
||||||
data["attachment_type"] = m.Attachment.Type
|
data["attachment_type"] = m.Attachment.Type
|
||||||
data["attachment_size"] = fmt.Sprintf("%d", m.Attachment.Size)
|
data["attachment_size"] = fmt.Sprintf("%d", m.Attachment.Size)
|
||||||
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
||||||
|
data["attachment_preview_url"] = m.Attachment.PreviewURL
|
||||||
data["attachment_url"] = m.Attachment.URL
|
data["attachment_url"] = m.Attachment.URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,9 +326,9 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request) error {
|
||||||
} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
|
||||||
return s.handleDocs(w, r)
|
return s.handleDocs(w, r)
|
||||||
} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
|
} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
|
||||||
return s.handleFile(w, r)
|
return s.withRateLimit(w, r, s.handleFile)
|
||||||
} else if r.Method == http.MethodGet && previewRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
|
} else if r.Method == http.MethodGet && previewRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
|
||||||
return s.handlePreview(w, r)
|
return s.withRateLimit(w, r, s.handlePreview)
|
||||||
} else if r.Method == http.MethodOptions {
|
} else if r.Method == http.MethodOptions {
|
||||||
return s.handleOptions(w, r)
|
return s.handleOptions(w, r)
|
||||||
} else if r.Method == http.MethodGet && topicPathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && topicPathRegex.MatchString(r.URL.Path) {
|
||||||
|
@ -384,7 +384,7 @@ func (s *Server) handleDocs(w http.ResponseWriter, r *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
||||||
if s.config.AttachmentCacheDir == "" {
|
if s.config.AttachmentCacheDir == "" {
|
||||||
return errHTTPInternalError
|
return errHTTPInternalError
|
||||||
}
|
}
|
||||||
|
@ -408,7 +408,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
|
func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request, _ *visitor) error {
|
||||||
if s.config.AttachmentCacheDir == "" {
|
if s.config.AttachmentCacheDir == "" {
|
||||||
return errHTTPInternalError
|
return errHTTPInternalError
|
||||||
}
|
}
|
||||||
|
@ -422,12 +422,12 @@ func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errHTTPNotFound
|
return errHTTPNotFound
|
||||||
}
|
}
|
||||||
if stat.Size() > 20*1024*1024 {
|
if stat.Size() > s.config.AttachmentSizePreviewMax {
|
||||||
return errHTTPInternalError
|
return errHTTPNotFoundTooLarge
|
||||||
}
|
}
|
||||||
img, err := imaging.Open(file)
|
img, err := imaging.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errHTTPNotFoundTooLarge
|
return err
|
||||||
}
|
}
|
||||||
var width, height int
|
var width, height int
|
||||||
if width >= height {
|
if width >= height {
|
||||||
|
@ -438,7 +438,7 @@ func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
|
||||||
width = int(float32(img.Bounds().Dx()) / float32(img.Bounds().Dy()) * float32(height))
|
width = int(float32(img.Bounds().Dx()) / float32(img.Bounds().Dy()) * float32(height))
|
||||||
}
|
}
|
||||||
preview := imaging.Resize(img, width, height, imaging.Lanczos)
|
preview := imaging.Resize(img, width, height, imaging.Lanczos)
|
||||||
return imaging.Encode(w, preview, imaging.PNG)
|
return imaging.Encode(w, preview, imaging.JPEG, imaging.JPEGQuality(80))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
@ -575,10 +575,11 @@ func (s *Server) writeAttachment(r *http.Request, v *visitor, m *message, body *
|
||||||
return errHTTPBadRequestInvalidMessage
|
return errHTTPBadRequestInvalidMessage
|
||||||
}
|
}
|
||||||
contentType := http.DetectContentType(body.PeakedBytes)
|
contentType := http.DetectContentType(body.PeakedBytes)
|
||||||
ext := ".bin"
|
ext := util.ExtensionByType(contentType)
|
||||||
exts, err := mime.ExtensionsByType(contentType)
|
fileURL := fmt.Sprintf("%s/file/%s%s", s.config.BaseURL, m.ID, ext)
|
||||||
if err == nil && len(exts) > 0 {
|
previewURL := ""
|
||||||
ext = exts[0]
|
if strings.HasPrefix(contentType, "image/") {
|
||||||
|
previewURL = fmt.Sprintf("%s/preview/%s%s", s.config.BaseURL, m.ID, ext)
|
||||||
}
|
}
|
||||||
filename := readParam(r, "x-filename", "filename", "file", "f")
|
filename := readParam(r, "x-filename", "filename", "file", "f")
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
|
@ -610,7 +611,8 @@ func (s *Server) writeAttachment(r *http.Request, v *visitor, m *message, body *
|
||||||
Type: contentType,
|
Type: contentType,
|
||||||
Size: size,
|
Size: size,
|
||||||
Expires: time.Now().Add(s.config.AttachmentExpiryDuration).Unix(),
|
Expires: time.Now().Add(s.config.AttachmentExpiryDuration).Unix(),
|
||||||
URL: fmt.Sprintf("%s/file/%s%s", s.config.BaseURL, m.ID, ext),
|
PreviewURL: previewURL,
|
||||||
|
URL: fileURL,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
15
util/util.go
15
util/util.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"mime"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -163,3 +164,17 @@ func ExpandHome(path string) string {
|
||||||
func ShortTopicURL(s string) string {
|
func ShortTopicURL(s string) string {
|
||||||
return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://")
|
return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtensionByType is a wrapper around mime.ExtensionByType with a few sensible corrections
|
||||||
|
func ExtensionByType(contentType string) string {
|
||||||
|
switch contentType {
|
||||||
|
case "image/jpeg":
|
||||||
|
return ".jpg"
|
||||||
|
default:
|
||||||
|
exts, err := mime.ExtensionsByType(contentType)
|
||||||
|
if err == nil && len(exts) > 0 {
|
||||||
|
return exts[0]
|
||||||
|
}
|
||||||
|
return ".bin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue