Preview URL
This commit is contained in:
		
							parent
							
								
									38788bb2e9
								
							
						
					
					
						commit
						2930c4ff62
					
				
					 5 changed files with 63 additions and 38 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue