diff --git a/Server.go b/Server.go index 513f755..6eab63f 100644 --- a/Server.go +++ b/Server.go @@ -5,6 +5,7 @@ import ( "encoding/json" "log" "net/http" + "regexp" "strings" "time" @@ -92,6 +93,8 @@ const ( metadataUrlPrefix = `/info/` ) +var rxThumbUrl = regexp.MustCompile(`^/thumb/(.)/(.*)$`) + func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set(`Server`, SERVER_HEADER) w.Header().Set(`Access-Control-Allow-Origin`, `*`) // Blanket allow CORS @@ -110,6 +113,10 @@ func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else if r.Method == "GET" && strings.HasPrefix(r.URL.Path, metadataUrlPrefix) { this.handleInformation(w, r.URL.Path[len(metadataUrlPrefix):]) + } else if r.Method == "GET" && rxThumbUrl.MatchString(r.URL.Path) { + parts := rxThumbUrl.FindStringSubmatch(r.URL.Path) + this.handleThumb(w, r, parts[1][0], parts[2]) + } else if r.Method == "GET" && r.URL.Path == `/about` { this.handleAbout(w) diff --git a/thumb.go b/thumb.go new file mode 100644 index 0000000..4c16451 --- /dev/null +++ b/thumb.go @@ -0,0 +1,73 @@ +package contented + +import ( + "errors" + "fmt" + "log" + "net/http" + "path/filepath" + + "code.ivysaur.me/thumbnail" +) + +func thumbnailer(t byte) (*thumbnail.Thumbnailer, error) { + // Modelled on what imgur.com offers + // @ref https://api.imgur.com/models/image#thumbs + + const ( + cacheSize = 1 + outputFmt = thumbnail.OUTPUT_JPG + scaleFmt = thumbnail.SCALEFMT_BILINEAR + ) + + switch t { + case 's': + return thumbnail.NewThumbnailerEx(90, 90, cacheSize, outputFmt, thumbnail.ASPECT_CROP_TO_DIMENSIONS, scaleFmt), nil + case 'b': + return thumbnail.NewThumbnailerEx(160, 160, cacheSize, outputFmt, thumbnail.ASPECT_CROP_TO_DIMENSIONS, scaleFmt), nil + case 't': + return thumbnail.NewThumbnailerEx(160, 160, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil + case 'm': + return thumbnail.NewThumbnailerEx(340, 340, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil + case 'l': + return thumbnail.NewThumbnailerEx(640, 640, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil + case 'h': + return thumbnail.NewThumbnailerEx(1024, 1024, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil + default: + return nil, errors.New("Unsupported thumbnail type (should be s/b/t/m/l/h)") + } +} + +func (this *Server) handleThumb(w http.ResponseWriter, r *http.Request, thumbnailType byte, fileId string) { + err := this.handleThumbInternal(w, thumbnailType, fileId) + if err != nil { + log.Printf("%s Thumbnail failed: %s\n", this.remoteIP(r), err.Error()) + http.Error(w, "Couldn't provide content", 500) + } +} + +func (this *Server) handleThumbInternal(w http.ResponseWriter, thumbnailType byte, fileId string) error { + t, err := thumbnailer(thumbnailType) + if err != nil { + return err + } + + // Load metadata + m, err := this.Metadata(fileId) + if err != nil { + return err + } + + filePath := filepath.Join(this.opts.DataDirectory, m.FileHash) + thumb, err := t.RenderFile_NoCache_MimeType(filePath, m.MimeType) + if err != nil { + return err + } + + w.Header().Set(`Content-Length`, fmt.Sprintf("%d", len(thumb))) + w.Header().Set(`Content-Type`, `image/jpeg`) + w.WriteHeader(200) + w.Write(thumb) + + return nil +}