More io streaming

This commit is contained in:
Jan-Lukas Else 2022-02-11 19:39:07 +01:00
parent 3d5cb1ab4a
commit 6e767e3612
13 changed files with 115 additions and 149 deletions

View File

@ -1,8 +1,8 @@
package main package main
import ( import (
"bytes"
"context" "context"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
@ -181,12 +181,9 @@ func (c *cache) getCache(key string, next http.Handler, r *http.Request) (item *
next.ServeHTTP(recorder, cr) next.ServeHTTP(recorder, cr)
// Cache values from recorder // Cache values from recorder
result := recorder.Result() result := recorder.Result()
body, _ := io.ReadAll(result.Body) eTag := sha256.New()
body, _ := io.ReadAll(io.TeeReader(result.Body, eTag))
_ = result.Body.Close() _ = result.Body.Close()
eTag := result.Header.Get("ETag")
if eTag == "" {
eTag, _ = getSHA256(bytes.NewReader(body))
}
lastMod := time.Now() lastMod := time.Now()
if lm := result.Header.Get(lastModified); lm != "" { if lm := result.Header.Get(lastModified); lm != "" {
if parsedTime, te := dateparse.ParseLocal(lm); te == nil { if parsedTime, te := dateparse.ParseLocal(lm); te == nil {
@ -202,7 +199,7 @@ func (c *cache) getCache(key string, next http.Handler, r *http.Request) (item *
item = &cacheItem{ item = &cacheItem{
expiration: exp, expiration: exp,
creationTime: lastMod, creationTime: lastMod,
eTag: eTag, eTag: fmt.Sprintf("%x", eTag.Sum(nil)),
code: result.StatusCode, code: result.StatusCode,
header: result.Header, header: result.Header,
body: body, body: body,

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -45,37 +44,34 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) {
continue continue
} }
// Create preview // Create preview
preview, err := a.createMarkdownPreview(blog, string(message)) w, err := c.Writer(ctx, ws.MessageText)
if err != nil { if err != nil {
preview = []byte(err.Error()) break
} }
// Write preview to socket a.createMarkdownPreview(w, blog, string(message))
err = c.Write(ctx, ws.MessageText, preview) err = w.Close()
if err != nil { if err != nil {
break break
} }
} }
} }
func (a *goBlog) createMarkdownPreview(blog string, markdown string) (rendered []byte, err error) { func (a *goBlog) createMarkdownPreview(w io.Writer, blog string, markdown string) {
p := &post{ p := &post{
Blog: blog, Blog: blog,
Content: markdown, Content: markdown,
} }
err = a.computeExtraPostParameters(p) err := a.computeExtraPostParameters(p)
if err != nil { if err != nil {
return nil, err _, _ = io.WriteString(w, err.Error())
return
} }
if t := p.Title(); t != "" { if t := p.Title(); t != "" {
p.RenderedTitle = a.renderMdTitle(t) p.RenderedTitle = a.renderMdTitle(t)
} }
// Render post // Render post
buf := bufferpool.Get() hb := newHtmlBuilder(w)
hb := newHtmlBuilder(buf)
a.renderEditorPreview(hb, a.cfg.Blogs[blog], p) a.renderEditorPreview(hb, a.cfg.Blogs[blog], p)
rendered = buf.Bytes()
bufferpool.Put(buf)
return
} }
func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) { func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
@ -94,7 +90,9 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
}, },
}) })
case "updatepost": case "updatepost":
jsonBytes, err := json.Marshal(map[string]interface{}{ jsonBuf := bufferpool.Get()
defer bufferpool.Put(jsonBuf)
err := json.NewEncoder(jsonBuf).Encode(map[string]interface{}{
"action": actionUpdate, "action": actionUpdate,
"url": r.FormValue("url"), "url": r.FormValue("url"),
"replace": map[string][]string{ "replace": map[string][]string{
@ -107,7 +105,7 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusInternalServerError) a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
req, err := http.NewRequest(http.MethodPost, "", bytes.NewReader(jsonBytes)) req, err := http.NewRequest(http.MethodPost, "", jsonBuf)
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError) a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
@ -138,25 +136,15 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
originalGpx, err := io.ReadAll(file) gpx, err := io.ReadAll(a.min.Reader(contenttype.XML, file))
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
minifiedGpx, err := a.min.MinifyString(contenttype.XML, string(originalGpx))
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
resultBytes, err := yaml.Marshal(map[string]string{
"gpx": minifiedGpx,
})
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
w.Header().Set(contentType, contenttype.TextUTF8) w.Header().Set(contentType, contenttype.TextUTF8)
_, _ = w.Write(resultBytes) _ = yaml.NewEncoder(w).Encode(map[string]string{
"gpx": string(gpx),
})
default: default:
a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest) a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest)
} }

View File

@ -1,7 +1,8 @@
package main package main
import ( import (
"embed" "io"
"io/fs"
"net/http" "net/http"
"path" "path"
"strings" "strings"
@ -9,24 +10,23 @@ import (
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
) )
func (a *goBlog) serveFs(fs embed.FS, basePath string) http.HandlerFunc { func (a *goBlog) serveFs(f fs.FS, basePath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
fileName := strings.TrimPrefix(r.URL.Path, basePath) fileName := strings.TrimPrefix(r.URL.Path, basePath)
fb, err := fs.ReadFile(fileName) file, err := f.Open(fileName)
if err != nil { if err != nil {
a.serve404(w, r) a.serve404(w, r)
return return
} }
var read io.Reader = file
switch path.Ext(fileName) { switch path.Ext(fileName) {
case ".js": case ".js":
w.Header().Set(contentType, contenttype.JS) w.Header().Set(contentType, contenttype.JSUTF8)
_, _ = a.min.Write(w, contenttype.JSUTF8, fb) read = a.min.Reader(contenttype.JS, read)
case ".css": case ".css":
w.Header().Set(contentType, contenttype.CSS) w.Header().Set(contentType, contenttype.CSSUTF8)
_, _ = a.min.Write(w, contenttype.CSSUTF8, fb) read = a.min.Reader(contenttype.CSS, read)
default:
w.Header().Set(contentType, http.DetectContentType(fb))
_, _ = w.Write(fb)
} }
_, _ = io.Copy(w, read)
} }
} }

View File

@ -3,12 +3,15 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"net/http" "net/http"
"github.com/carlmjohnson/requests" "github.com/carlmjohnson/requests"
"go.goblog.app/app/pkgs/bufferpool"
) )
const defaultCompressionWidth = 2000 const defaultCompressionWidth = 2000
@ -167,14 +170,12 @@ func (cf *cloudflare) compress(url string, upload mediaStorageSaveFunc, hc *http
return uploadCompressedFile(fileExtension, &imgBuffer, upload) return uploadCompressedFile(fileExtension, &imgBuffer, upload)
} }
func uploadCompressedFile(fileExtension string, imgBuffer *bytes.Buffer, upload mediaStorageSaveFunc) (string, error) { func uploadCompressedFile(fileExtension string, r io.Reader, upload mediaStorageSaveFunc) (string, error) {
// Create reader from buffer // Copy file to temporary buffer to generate hash and filename
imgReader := bytes.NewReader(imgBuffer.Bytes()) hash := sha256.New()
// Get hash of compressed file tempBuffer := bufferpool.Get()
fileName, err := getSHA256(imgReader) defer bufferpool.Put(tempBuffer)
if err != nil { _, _ = io.Copy(io.MultiWriter(tempBuffer, hash), r)
return "", err // Upload buffer
} return upload(fmt.Sprintf("%x.%s", hash.Sum(nil), fileExtension), tempBuffer)
// Upload compressed file
return upload(fileName+"."+fileExtension, imgReader)
} }

View File

@ -1,11 +1,11 @@
package main package main
import ( import (
"crypto/sha256"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"os"
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -14,13 +14,9 @@ import (
func Test_compress(t *testing.T) { func Test_compress(t *testing.T) {
fakeFileContent := "Test" fakeFileContent := "Test"
fakeFileName := filepath.Join(t.TempDir(), "test.jpg") hash := sha256.New()
err := os.WriteFile(fakeFileName, []byte(fakeFileContent), 0666) io.WriteString(hash, fakeFileContent)
require.Nil(t, err) fakeSha256 := fmt.Sprintf("%x", hash.Sum(nil))
fakeFile, err := os.Open(fakeFileName)
require.Nil(t, err)
fakeSha256, err := getSHA256(fakeFile)
require.Nil(t, err)
var uf mediaStorageSaveFunc = func(filename string, f io.Reader) (location string, err error) { var uf mediaStorageSaveFunc = func(filename string, f io.Reader) (location string, err error) {
return "https://example.com/" + filename, nil return "https://example.com/" + filename, nil
@ -32,7 +28,7 @@ func Test_compress(t *testing.T) {
assert.Equal(t, "https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=2000,h=3000/https://example.com/original.jpg", r.URL.String()) assert.Equal(t, "https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=2000,h=3000/https://example.com/original.jpg", r.URL.String())
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(fakeFileContent)) _, _ = io.WriteString(rw, fakeFileContent)
})) }))
cf := &cloudflare{} cf := &cloudflare{}
@ -51,7 +47,7 @@ func Test_compress(t *testing.T) {
defer r.Body.Close() defer r.Body.Close()
var requestJson map[string]interface{} var requestJson map[string]interface{}
err = json.Unmarshal(requestBody, &requestJson) err := json.Unmarshal(requestBody, &requestJson)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, requestJson) require.NotNil(t, requestJson)
@ -59,7 +55,7 @@ func Test_compress(t *testing.T) {
assert.Equal(t, "https://example.com/original.jpg", requestJson["url"]) assert.Equal(t, "https://example.com/original.jpg", requestJson["url"])
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(fakeFileContent)) _, _ = io.WriteString(rw, fakeFileContent)
})) }))
cf := &shortpixel{"testkey"} cf := &shortpixel{"testkey"}

View File

@ -28,11 +28,11 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
switch r.URL.Query().Get("q") { switch r.URL.Query().Get("q") {
case "config": case "config":
w.Header().Set(contentType, contenttype.JSONUTF8) w.Header().Set(contentType, contenttype.JSONUTF8)
w.WriteHeader(http.StatusOK) mw := a.min.Writer(contenttype.JSON, w)
b, _ := json.Marshal(&micropubConfig{ defer mw.Close()
_ = json.NewEncoder(mw).Encode(&micropubConfig{
MediaEndpoint: a.getFullAddress(micropubPath + micropubMediaSubPath), MediaEndpoint: a.getFullAddress(micropubPath + micropubMediaSubPath),
}) })
_, _ = a.min.Write(w, contenttype.JSON, b)
case "source": case "source":
var mf interface{} var mf interface{}
if urlString := r.URL.Query().Get("url"); urlString != "" { if urlString := r.URL.Query().Get("url"); urlString != "" {
@ -65,9 +65,9 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
mf = list mf = list
} }
w.Header().Set(contentType, contenttype.JSONUTF8) w.Header().Set(contentType, contenttype.JSONUTF8)
w.WriteHeader(http.StatusOK) mw := a.min.Writer(contenttype.JSON, w)
b, _ := json.Marshal(mf) defer mw.Close()
_, _ = a.min.Write(w, contenttype.JSON, b) _ = json.NewEncoder(mw).Encode(mf)
case "category": case "category":
allCategories := []string{} allCategories := []string{}
for blog := range a.cfg.Blogs { for blog := range a.cfg.Blogs {
@ -79,11 +79,11 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
allCategories = append(allCategories, values...) allCategories = append(allCategories, values...)
} }
w.Header().Set(contentType, contenttype.JSONUTF8) w.Header().Set(contentType, contenttype.JSONUTF8)
w.WriteHeader(http.StatusOK) mw := a.min.Writer(contenttype.JSON, w)
b, _ := json.Marshal(map[string]interface{}{ defer mw.Close()
_ = json.NewEncoder(mw).Encode(map[string]interface{}{
"categories": allCategories, "categories": allCategories,
}) })
_, _ = a.min.Write(w, contenttype.JSON, b)
default: default:
a.serve404(w, r) a.serve404(w, r)
} }

View File

@ -1,43 +1,51 @@
package main package main
import ( import (
"crypto/sha256"
"fmt"
"io"
"mime" "mime"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strings" "strings"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
) )
const micropubMediaSubPath = "/media" const micropubMediaSubPath = "/media"
func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) { func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
// Check scope
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "media") { if !strings.Contains(r.Context().Value(indieAuthScope).(string), "media") {
a.serveError(w, r, "media scope missing", http.StatusForbidden) a.serveError(w, r, "media scope missing", http.StatusForbidden)
return return
} }
// Check if request is multipart
if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.MultipartForm) { if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.MultipartForm) {
a.serveError(w, r, "wrong content-type", http.StatusBadRequest) a.serveError(w, r, "wrong content-type", http.StatusBadRequest)
return return
} }
// Parse multipart form
err := r.ParseMultipartForm(0) err := r.ParseMultipartForm(0)
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
// Get file
file, header, err := r.FormFile("file") file, header, err := r.FormFile("file")
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest) a.serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
defer func() { _ = file.Close() }() // Read the file into temporary buffer and generate sha256 hash
hashFile, _, _ := r.FormFile("file") hash := sha256.New()
defer func() { _ = hashFile.Close() }() buffer := bufferpool.Get()
fileName, err := getSHA256(hashFile) defer bufferpool.Put(buffer)
if err != nil { _, _ = io.Copy(buffer, io.TeeReader(file, hash))
a.serveError(w, r, err.Error(), http.StatusInternalServerError) _ = file.Close()
return _ = r.Body.Close()
} // Get file extension
fileExtension := filepath.Ext(header.Filename) fileExtension := filepath.Ext(header.Filename)
if len(fileExtension) == 0 { if len(fileExtension) == 0 {
// Find correct file extension if original filename does not contain one // Find correct file extension if original filename does not contain one
@ -49,9 +57,10 @@ func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
} }
} }
} }
fileName += strings.ToLower(fileExtension) // Generate the file name
fileName := fmt.Sprintf("%x%s", hash.Sum(nil), fileExtension)
// Save file // Save file
location, err := a.saveMediaFile(fileName, file) location, err := a.saveMediaFile(fileName, buffer)
if err != nil { if err != nil {
a.serveError(w, r, "failed to save original file: "+err.Error(), http.StatusInternalServerError) a.serveError(w, r, "failed to save original file: "+err.Error(), http.StatusInternalServerError)
return return

View File

@ -8,7 +8,10 @@ import (
) )
func (a *goBlog) serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) { func (a *goBlog) serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
b, _ := json.Marshal(map[string]interface{}{ w.Header().Set(contentType, contenttype.JSONUTF8)
mw := a.min.Writer(contenttype.JSON, w)
defer mw.Close()
_ = json.NewEncoder(mw).Encode(map[string]interface{}{
"links": []map[string]interface{}{ "links": []map[string]interface{}{
{ {
"href": a.getFullAddress("/nodeinfo"), "href": a.getFullAddress("/nodeinfo"),
@ -16,15 +19,16 @@ func (a *goBlog) serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
}, },
}, },
}) })
w.Header().Set(contentType, contenttype.JSONUTF8)
_, _ = a.min.Write(w, contenttype.JSON, b)
} }
func (a *goBlog) serveNodeInfo(w http.ResponseWriter, r *http.Request) { func (a *goBlog) serveNodeInfo(w http.ResponseWriter, r *http.Request) {
localPosts, _ := a.db.countPosts(&postsRequestConfig{ localPosts, _ := a.db.countPosts(&postsRequestConfig{
status: statusPublished, status: statusPublished,
}) })
b, _ := json.Marshal(map[string]interface{}{ mw := a.min.Writer(contenttype.JSON, w)
defer mw.Close()
w.Header().Set(contentType, contenttype.JSONUTF8)
_ = json.NewEncoder(mw).Encode(map[string]interface{}{
"version": "2.1", "version": "2.1",
"software": map[string]interface{}{ "software": map[string]interface{}{
"name": "goblog", "name": "goblog",
@ -43,6 +47,4 @@ func (a *goBlog) serveNodeInfo(w http.ResponseWriter, r *http.Request) {
}, },
"metadata": map[string]interface{}{}, "metadata": map[string]interface{}{},
}) })
w.Header().Set(contentType, contenttype.JSONUTF8)
_, _ = a.min.Write(w, contenttype.JSON, b)
} }

View File

@ -38,19 +38,19 @@ func (m *Minifier) Get() *minify.M {
} }
func (m *Minifier) Write(w io.Writer, mediatype string, b []byte) (int, error) { func (m *Minifier) Write(w io.Writer, mediatype string, b []byte) (int, error) {
mw := m.Get().Writer(mediatype, w) mw := m.Writer(mediatype, w)
defer mw.Close() defer mw.Close()
return mw.Write(b) return mw.Write(b)
} }
func (m *Minifier) MinifyBytes(mediatype string, b []byte) ([]byte, error) {
return m.Get().Bytes(mediatype, b)
}
func (m *Minifier) MinifyString(mediatype string, s string) (string, error) {
return m.Get().String(mediatype, s)
}
func (m *Minifier) Minify(mediatype string, w io.Writer, r io.Reader) error { func (m *Minifier) Minify(mediatype string, w io.Writer, r io.Reader) error {
return m.Get().Minify(mediatype, w, r) return m.Get().Minify(mediatype, w, r)
} }
func (m *Minifier) Writer(mediatype string, w io.Writer) io.WriteCloser {
return m.Get().Writer(mediatype, w)
}
func (m *Minifier) Reader(mediatype string, r io.Reader) io.Reader {
return m.Get().Reader(mediatype, r)
}

View File

@ -40,10 +40,10 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
// Render // Render
buf := bufferpool.Get() buf := bufferpool.Get()
minWriter := a.min.Get().Writer(contenttype.HTML, buf) mw := a.min.Writer(contenttype.HTML, buf)
hb := newHtmlBuilder(minWriter) hb := newHtmlBuilder(mw)
f(hb, data) f(hb, data)
_ = minWriter.Close() _ = mw.Close()
_, _ = buf.WriteTo(w) _, _ = buf.WriteTo(w)
bufferpool.Put(buf) bufferpool.Put(buf)
} }

View File

@ -2,6 +2,8 @@ package main
import ( import (
"bytes" "bytes"
"crypto/sha256"
"fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
@ -55,37 +57,26 @@ func (a *goBlog) initTemplateAssets() error {
func (a *goBlog) compileAsset(name string, read io.Reader) (string, error) { func (a *goBlog) compileAsset(name string, read io.Reader) (string, error) {
ext := path.Ext(name) ext := path.Ext(name)
compiledExt := ext
var contentBuffer bytes.Buffer
switch ext { switch ext {
case ".js": case ".js":
if err := a.min.Minify(contenttype.JS, &contentBuffer, read); err != nil { read = a.min.Reader(contenttype.JS, read)
return "", err
}
case ".css": case ".css":
if err := a.min.Minify(contenttype.CSS, &contentBuffer, read); err != nil { read = a.min.Reader(contenttype.CSS, read)
return "", err
}
case ".xml", ".xsl": case ".xml", ".xsl":
if err := a.min.Minify(contenttype.XML, &contentBuffer, read); err != nil { read = a.min.Reader(contenttype.XML, read)
return "", err
}
default:
if _, err := io.Copy(&contentBuffer, read); err != nil {
return "", err
}
} }
// Hashes // Read file
hash, err := getSHA256(bytes.NewReader(contentBuffer.Bytes())) hash := sha256.New()
body, err := io.ReadAll(io.TeeReader(read, hash))
if err != nil { if err != nil {
return "", err return "", err
} }
// File name // File name
compiledFileName := hash + compiledExt compiledFileName := fmt.Sprintf("%x%s", hash.Sum(nil), ext)
// Create struct // Create struct
a.assetFiles[compiledFileName] = &assetFile{ a.assetFiles[compiledFileName] = &assetFile{
contentType: mime.TypeByExtension(compiledExt), contentType: mime.TypeByExtension(ext),
body: contentBuffer.Bytes(), body: body,
} }
return compiledFileName, err return compiledFileName, err
} }

14
tts.go
View File

@ -3,8 +3,10 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt"
"html" "html"
"io" "io"
"log" "log"
@ -105,18 +107,14 @@ func (a *goBlog) createPostTTSAudio(p *post) error {
} }
// Merge partsBuffers into final buffer // Merge partsBuffers into final buffer
var final bytes.Buffer final := new(bytes.Buffer)
if err := mp3merge.MergeMP3(&final, partsBuffers...); err != nil { hash := sha256.New()
if err := mp3merge.MergeMP3(io.MultiWriter(final, hash), partsBuffers...); err != nil {
return err return err
} }
// Save audio // Save audio
audioReader := bytes.NewReader(final.Bytes()) loc, err := a.saveMediaFile(fmt.Sprintf("%x.mp3", hash.Sum(nil)), final)
fileHash, err := getSHA256(audioReader)
if err != nil {
return err
}
loc, err := a.saveMediaFile(fileHash+".mp3", audioReader)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -217,21 +216,6 @@ func urlHasExt(rawUrl string, allowed ...string) (ext string, has bool) {
return ext, funk.ContainsString(allowed, strings.ToLower(ext)) return ext, funk.ContainsString(allowed, strings.ToLower(ext))
} }
// Get SHA-256 hash
func getSHA256(file io.ReadSeeker) (hash string, err error) {
if _, err = file.Seek(0, 0); err != nil {
return "", err
}
h := sha256.New()
if _, err = io.Copy(h, file); err != nil {
return "", err
}
if _, err = file.Seek(0, 0); err != nil {
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
func mBytesString(size int64) string { func mBytesString(size int64) string {
return fmt.Sprintf("%.2f MB", datasize.ByteSize(size).MBytes()) return fmt.Sprintf("%.2f MB", datasize.ByteSize(size).MBytes())
} }