Preview URL
parent
38788bb2e9
commit
2930c4ff62
|
@ -26,6 +26,7 @@ const (
|
|||
attachment_type TEXT NOT NULL,
|
||||
attachment_size INT NOT NULL,
|
||||
attachment_expires INT NOT NULL,
|
||||
attachment_preview_url TEXT NOT NULL,
|
||||
attachment_url TEXT NOT NULL,
|
||||
published INT NOT NULL
|
||||
);
|
||||
|
@ -33,24 +34,24 @@ const (
|
|||
COMMIT;
|
||||
`
|
||||
insertMessageQuery = `
|
||||
INSERT INTO messages (id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, published)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
||||
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
|
||||
WHERE topic = ? AND time >= ? AND published = 1
|
||||
ORDER BY time ASC
|
||||
`
|
||||
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
|
||||
WHERE topic = ? AND time >= ?
|
||||
ORDER BY time ASC
|
||||
`
|
||||
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
|
||||
WHERE time <= ? AND published = 0
|
||||
`
|
||||
|
@ -124,13 +125,14 @@ func (c *sqliteCache) AddMessage(m *message) error {
|
|||
}
|
||||
published := m.Time <= time.Now().Unix()
|
||||
tags := strings.Join(m.Tags, ",")
|
||||
var attachmentName, attachmentType, attachmentURL string
|
||||
var attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
|
||||
var attachmentSize, attachmentExpires int64
|
||||
if m.Attachment != nil {
|
||||
attachmentName = m.Attachment.Name
|
||||
attachmentType = m.Attachment.Type
|
||||
attachmentSize = m.Attachment.Size
|
||||
attachmentExpires = m.Attachment.Expires
|
||||
attachmentPreviewURL = m.Attachment.PreviewURL
|
||||
attachmentURL = m.Attachment.URL
|
||||
}
|
||||
_, err := c.db.Exec(
|
||||
|
@ -146,6 +148,7 @@ func (c *sqliteCache) AddMessage(m *message) error {
|
|||
attachmentType,
|
||||
attachmentSize,
|
||||
attachmentExpires,
|
||||
attachmentPreviewURL,
|
||||
attachmentURL,
|
||||
published,
|
||||
)
|
||||
|
@ -231,8 +234,8 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
|||
for rows.Next() {
|
||||
var timestamp, attachmentSize, attachmentExpires int64
|
||||
var priority int
|
||||
var id, topic, msg, title, tagsStr, attachmentName, attachmentType, attachmentURL string
|
||||
if err := rows.Scan(&id, ×tamp, &topic, &msg, &title, &priority, &tagsStr, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentURL); err != nil {
|
||||
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, &attachmentPreviewURL, &attachmentURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tags []string
|
||||
|
@ -242,11 +245,12 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
|||
var att *attachment
|
||||
if attachmentName != "" && attachmentURL != "" {
|
||||
att = &attachment{
|
||||
Name: attachmentName,
|
||||
Type: attachmentType,
|
||||
Size: attachmentSize,
|
||||
Expires: attachmentExpires,
|
||||
URL: attachmentURL,
|
||||
Name: attachmentName,
|
||||
Type: attachmentType,
|
||||
Size: attachmentSize,
|
||||
Expires: attachmentExpires,
|
||||
PreviewURL: attachmentPreviewURL,
|
||||
URL: attachmentURL,
|
||||
}
|
||||
}
|
||||
messages = append(messages, &message{
|
||||
|
|
|
@ -13,8 +13,9 @@ const (
|
|||
DefaultAtSenderInterval = 10 * time.Second
|
||||
DefaultMinDelay = 10 * time.Second
|
||||
DefaultMaxDelay = 3 * 24 * time.Hour
|
||||
DefaultMessageLimit = 4096
|
||||
DefaultAttachmentSizeLimit = 5 * 1024 * 1024
|
||||
DefaultMessageLimit = 4096 // Bytes
|
||||
DefaultAttachmentSizeLimit = 15 * 1024 * 1024
|
||||
DefaultAttachmentSizePreviewMax = 20 * 1024 * 1024 // Bytes
|
||||
DefaultAttachmentExpiryDuration = 3 * time.Hour
|
||||
DefaultFirebaseKeepaliveInterval = 3 * time.Hour // Not too frequently to save battery
|
||||
)
|
||||
|
@ -48,6 +49,7 @@ type Config struct {
|
|||
CacheDuration time.Duration
|
||||
AttachmentCacheDir string
|
||||
AttachmentSizeLimit int64
|
||||
AttachmentSizePreviewMax int64
|
||||
AttachmentExpiryDuration time.Duration
|
||||
KeepaliveInterval time.Duration
|
||||
ManagerInterval time.Duration
|
||||
|
@ -88,6 +90,7 @@ func NewConfig() *Config {
|
|||
CacheDuration: DefaultCacheDuration,
|
||||
AttachmentCacheDir: "",
|
||||
AttachmentSizeLimit: DefaultAttachmentSizeLimit,
|
||||
AttachmentSizePreviewMax: DefaultAttachmentSizePreviewMax,
|
||||
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
|
||||
KeepaliveInterval: DefaultKeepaliveInterval,
|
||||
ManagerInterval: DefaultManagerInterval,
|
||||
|
|
|
@ -30,11 +30,12 @@ type message struct {
|
|||
}
|
||||
|
||||
type attachment struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
Expires int64 `json:"expires"`
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
Expires int64 `json:"expires"`
|
||||
PreviewURL string `json:"preview_url"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// messageEncoder is a function that knows how to encode a message
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -233,6 +232,7 @@ func createFirebaseSubscriber(conf *Config) (subscriber, error) {
|
|||
data["attachment_type"] = m.Attachment.Type
|
||||
data["attachment_size"] = fmt.Sprintf("%d", m.Attachment.Size)
|
||||
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
||||
data["attachment_preview_url"] = m.Attachment.PreviewURL
|
||||
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) {
|
||||
return s.handleDocs(w, r)
|
||||
} 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 != "" {
|
||||
return s.handlePreview(w, r)
|
||||
return s.withRateLimit(w, r, s.handlePreview)
|
||||
} else if r.Method == http.MethodOptions {
|
||||
return s.handleOptions(w, r)
|
||||
} 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
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
return errHTTPInternalError
|
||||
}
|
||||
|
@ -408,7 +408,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) error {
|
|||
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 == "" {
|
||||
return errHTTPInternalError
|
||||
}
|
||||
|
@ -422,12 +422,12 @@ func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
|
|||
if err != nil {
|
||||
return errHTTPNotFound
|
||||
}
|
||||
if stat.Size() > 20*1024*1024 {
|
||||
return errHTTPInternalError
|
||||
if stat.Size() > s.config.AttachmentSizePreviewMax {
|
||||
return errHTTPNotFoundTooLarge
|
||||
}
|
||||
img, err := imaging.Open(file)
|
||||
if err != nil {
|
||||
return errHTTPNotFoundTooLarge
|
||||
return err
|
||||
}
|
||||
var width, height int
|
||||
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))
|
||||
}
|
||||
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 {
|
||||
|
@ -575,10 +575,11 @@ func (s *Server) writeAttachment(r *http.Request, v *visitor, m *message, body *
|
|||
return errHTTPBadRequestInvalidMessage
|
||||
}
|
||||
contentType := http.DetectContentType(body.PeakedBytes)
|
||||
ext := ".bin"
|
||||
exts, err := mime.ExtensionsByType(contentType)
|
||||
if err == nil && len(exts) > 0 {
|
||||
ext = exts[0]
|
||||
ext := util.ExtensionByType(contentType)
|
||||
fileURL := fmt.Sprintf("%s/file/%s%s", s.config.BaseURL, m.ID, ext)
|
||||
previewURL := ""
|
||||
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")
|
||||
if filename == "" {
|
||||
|
@ -606,11 +607,12 @@ func (s *Server) writeAttachment(r *http.Request, v *visitor, m *message, body *
|
|||
}
|
||||
m.Message = fmt.Sprintf("You received a file: %s", filename) // May be overwritten later
|
||||
m.Attachment = &attachment{
|
||||
Name: filename,
|
||||
Type: contentType,
|
||||
Size: size,
|
||||
Expires: time.Now().Add(s.config.AttachmentExpiryDuration).Unix(),
|
||||
URL: fmt.Sprintf("%s/file/%s%s", s.config.BaseURL, m.ID, ext),
|
||||
Name: filename,
|
||||
Type: contentType,
|
||||
Size: size,
|
||||
Expires: time.Now().Add(s.config.AttachmentExpiryDuration).Unix(),
|
||||
PreviewURL: previewURL,
|
||||
URL: fileURL,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
15
util/util.go
15
util/util.go
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -163,3 +164,17 @@ func ExpandHome(path string) string {
|
|||
func ShortTopicURL(s string) string {
|
||||
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