From 4c87a95e731f39345cb9ad5a34fa25d18c1aef9b Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Sun, 10 Jan 2021 15:59:43 +0100 Subject: [PATCH] Local media storage --- config.go | 6 +++--- http.go | 7 ++++--- media.go | 39 +++++++++++++++++++++++++++++++++++++++ micropub.go | 8 +++----- micropubMedia.go | 41 ++++++++++++++++++++++++++++++----------- 5 files changed, 79 insertions(+), 22 deletions(-) create mode 100644 media.go diff --git a/config.go b/config.go index e91f5d5..4e14fb4 100644 --- a/config.go +++ b/config.go @@ -247,10 +247,10 @@ func initConfig() error { if appConfig.Micropub.MediaStorage.MediaURL == "" || appConfig.Micropub.MediaStorage.BunnyStorageKey == "" || appConfig.Micropub.MediaStorage.BunnyStorageName == "" { - appConfig.Micropub.MediaStorage = nil - } else { - appConfig.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(appConfig.Micropub.MediaStorage.MediaURL, "/") + appConfig.Micropub.MediaStorage.BunnyStorageKey = "" + appConfig.Micropub.MediaStorage.BunnyStorageName = "" } + appConfig.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(appConfig.Micropub.MediaStorage.MediaURL, "/") } return nil } diff --git a/http.go b/http.go index cc9dd01..6929dc6 100644 --- a/http.go +++ b/http.go @@ -103,9 +103,7 @@ func buildHandler() (http.Handler, error) { mpRouter.Use(checkIndieAuth, middleware.NoCache, minifier.Middleware) mpRouter.Get("/", serveMicropubQuery) mpRouter.Post("/", serveMicropubPost) - if appConfig.Micropub.MediaStorage != nil { - mpRouter.Post(micropubMediaSubPath, serveMicropubMedia) - } + mpRouter.Post(micropubMediaSubPath, serveMicropubMedia) }) // Editor @@ -182,6 +180,9 @@ func buildHandler() (http.Handler, error) { r.With(cacheMiddleware).Get(path, serveStaticFile) } + // Media files + r.With(cacheMiddleware).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, serveMediaFile) + // Short paths r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath) diff --git a/media.go b/media.go new file mode 100644 index 0000000..cd1b44f --- /dev/null +++ b/media.go @@ -0,0 +1,39 @@ +package main + +import ( + "io" + "net/http" + "os" + "path/filepath" + + "github.com/go-chi/chi" +) + +const mediaFilePath = "data/media" + +func saveMediaFile(filename string, mediaFile io.Reader) (string, error) { + err := os.MkdirAll(mediaFilePath, 0644) + if err != nil { + return "", err + } + newFile, err := os.Create(filepath.Join(mediaFilePath, filename)) + if err != nil { + return "", err + } + _, err = io.Copy(newFile, mediaFile) + if err != nil { + return "", err + } + return "/m/" + filename, nil +} + +func serveMediaFile(w http.ResponseWriter, r *http.Request) { + f := filepath.Join(mediaFilePath, chi.URLParam(r, "file")) + _, err := os.Stat(f) + if err != nil { + serve404(w, r) + return + } + w.Header().Add("Cache-Control", "public,max-age=31536000,immutable") + http.ServeFile(w, r, f) +} diff --git a/micropub.go b/micropub.go index 54a07f4..943bbbb 100644 --- a/micropub.go +++ b/micropub.go @@ -26,11 +26,9 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) { case "config": w.Header().Add(contentType, contentTypeJSONUTF8) w.WriteHeader(http.StatusOK) - mc := µpubConfig{} - if appConfig.Micropub.MediaStorage != nil { - mc.MediaEndpoint = appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath - } - _ = json.NewEncoder(w).Encode(mc) + _ = json.NewEncoder(w).Encode(µpubConfig{ + MediaEndpoint: appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath, + }) case "source": var mf interface{} if urlString := r.URL.Query().Get("url"); urlString != "" { diff --git a/micropubMedia.go b/micropubMedia.go index aea7e7f..ece59fd 100644 --- a/micropubMedia.go +++ b/micropubMedia.go @@ -63,34 +63,53 @@ func serveMicropubMedia(w http.ResponseWriter, r *http.Request) { } } fileName += strings.ToLower(fileExtension) - location, err := appConfig.Micropub.MediaStorage.uploadToBunny(fileName, file) + // Save file + location, err := uploadFile(fileName, file) if err != nil { - serveError(w, r, "failed to upload original file: "+err.Error(), http.StatusInternalServerError) + serveError(w, r, "failed to save original file: "+err.Error(), http.StatusInternalServerError) return } - if appConfig.Micropub.MediaStorage.TinifyKey != "" { - compressedLocation, err := appConfig.Micropub.MediaStorage.tinify(location) + // Try to compress file + if ms := appConfig.Micropub.MediaStorage; ms != nil && ms.TinifyKey != "" { + compressedLocation, err := tinify(location, ms) if err != nil { serveError(w, r, "failed to compress file: "+err.Error(), http.StatusInternalServerError) return } else if compressedLocation != "" { location = compressedLocation + } else { + serveError(w, r, "No compressed location", http.StatusInternalServerError) } } http.Redirect(w, r, location, http.StatusCreated) } -func (mediaConf *configMicropubMedia) uploadToBunny(filename string, file multipart.File) (location string, err error) { - req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("https://storage.bunnycdn.com/%s/%s", url.PathEscape(mediaConf.BunnyStorageName), url.PathEscape(filename)), file) - req.Header.Add("AccessKey", mediaConf.BunnyStorageKey) +func uploadFile(filename string, f io.Reader) (string, error) { + ms := appConfig.Micropub.MediaStorage + if ms != nil && ms.BunnyStorageKey != "" && ms.BunnyStorageName != "" { + return uploadToBunny(filename, f, ms) + } + loc, err := saveMediaFile(filename, f) + if err != nil { + return "", err + } + if ms != nil && ms.MediaURL != "" { + return ms.MediaURL + loc, nil + } + return loc, nil +} + +func uploadToBunny(filename string, f io.Reader, config *configMicropubMedia) (location string, err error) { + req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("https://storage.bunnycdn.com/%s/%s", url.PathEscape(config.BunnyStorageName), url.PathEscape(filename)), f) + req.Header.Add("AccessKey", config.BunnyStorageKey) resp, err := http.DefaultClient.Do(req) if err != nil || resp.StatusCode != http.StatusCreated { return "", errors.New("failed to upload file to BunnyCDN") } - return mediaConf.MediaURL + "/" + filename, nil + return config.MediaURL + "/" + filename, nil } -func (mediaConf *configMicropubMedia) tinify(url string) (location string, err error) { +func tinify(url string, config *configMicropubMedia) (location string, err error) { fileExtension := func() string { spliced := strings.Split(url, ".") return spliced[len(spliced)-1] @@ -101,7 +120,7 @@ func (mediaConf *configMicropubMedia) tinify(url string) (location string, err e if !(i < len(supportedTypes) && supportedTypes[i] == strings.ToLower(fileExtension)) { return "", nil } - tfgo.SetKey(mediaConf.TinifyKey) + tfgo.SetKey(config.TinifyKey) s, err := tfgo.FromUrl(url) if err != nil { return "", err @@ -134,7 +153,7 @@ func (mediaConf *configMicropubMedia) tinify(url string) (location string, err e if err != nil { return "", err } - location, err = mediaConf.uploadToBunny(fileName+"."+fileExtension, file) + location, err = uploadFile(fileName+"."+fileExtension, file) return }