feat: add thumbnails

This commit is contained in:
Louis Dalibard 2024-06-10 21:36:26 +02:00
parent f09916f44e
commit db4aae2e5b
6 changed files with 132 additions and 7 deletions

2
go.mod
View File

@ -3,6 +3,7 @@ module leech
go 1.22.2
require (
github.com/disintegration/imaging v1.6.2
github.com/dustin/go-humanize v1.0.1
github.com/gofiber/fiber/v2 v2.52.4
)
@ -18,5 +19,6 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/sys v0.15.0 // indirect
)

5
go.sum
View File

@ -1,5 +1,7 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
@ -23,7 +25,10 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -2,6 +2,7 @@ package html
import (
"fmt"
"leech/thumbnail"
"strings"
"github.com/dustin/go-humanize"
@ -106,10 +107,7 @@ func FileListPage(req string, entries []Entry) string {
if dirEntry.Size != -1 {
formattedSize = humanize.Bytes(uint64(dirEntry.Size))
}
icon := "/assets/images/fileicon.png"
if dirEntry.IsDir {
icon = "/assets/images/diricon.png"
}
link := "/" + req + "/" + dirEntry.Name
if dirEntry.Name == ".." {
splitReq := strings.Split(req, "/")
@ -121,6 +119,14 @@ func FileListPage(req string, entries []Entry) string {
if !dirEntry.IsDir {
link = "/serve/" + req + "/" + dirEntry.Name
}
icon := "/thumb/" + req + "/" + dirEntry.Name
if !thumbnail.IsSupportedFileType(dirEntry.Name) {
icon = "/assets/images/fileicon.png"
}
if dirEntry.IsDir {
icon = "/assets/images/diricon.png"
}
body += fmt.Sprintf(`<a href="%s"><div class="entry"><img src="%s"></img><div class="entry-name">%s</div><div class="entry-size">%s</div></div></a>`, link, icon, dirEntry.Name, formattedSize)
}
return header + body + footer

View File

@ -31,6 +31,8 @@ func main() {
app.Static("/serve/"+dirName, dirToServe)
}
app.Use("/thumb", route.HandleThumb)
app.Use("/", route.HandleList)
app.Listen(config.Config.Host)

View File

@ -1,9 +1,10 @@
package route
import (
"fmt"
"errors"
"leech/config"
"leech/html"
"leech/thumbnail"
"net/url"
"os"
"path"
@ -38,7 +39,6 @@ func RecursivelyGetSize(completePath string) (int64, error) {
func HandleList(c *fiber.Ctx) error {
encodedReq := c.Path()[1:]
fmt.Println(encodedReq)
req, err := url.QueryUnescape(encodedReq)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
@ -68,7 +68,6 @@ func HandleList(c *fiber.Ctx) error {
} else {
pathSlice := strings.Split(req, "/")
pathBase, ok := config.Config.ServeDirs[pathSlice[0]]
fmt.Println(pathSlice[0])
if ok {
pathSlice[0] = pathBase
completePath := path.Join(pathSlice...)
@ -101,3 +100,35 @@ func HandleList(c *fiber.Ctx) error {
}
}
}
func HandleThumb(c *fiber.Ctx) error {
encodedReq := c.Path()[1:]
req, err := url.QueryUnescape(encodedReq)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
pathSlice := strings.Split(req, "/")
pathSlice = pathSlice[1:len(pathSlice)]
pathBase, ok := config.Config.ServeDirs[pathSlice[0]]
if ok {
pathSlice[0] = pathBase
completePath := path.Join(pathSlice...)
if file_info, err := os.Stat(completePath); err == nil {
if file_info.IsDir() {
return c.Status(fiber.StatusUnauthorized).SendString("Sorry that's a directory!")
}
thumbnail.GetThumbnail(c, completePath)
} else if errors.Is(err, os.ErrNotExist) {
return c.Status(fiber.StatusNotFound).SendString("Sorry can't find that!")
} else {
// Schrodinger: file may or may not exist. See err for details.
// Therefore, do *NOT* use !os.IsNotExist(err) to test for file existence
}
} else {
return c.Status(fiber.StatusNotFound).SendString("Sorry can't find that!")
}
return nil
}

79
thumbnail/thumbnail.go Normal file
View File

@ -0,0 +1,79 @@
package thumbnail
import (
"bytes"
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"slices"
"sync"
"github.com/disintegration/imaging"
"github.com/gofiber/fiber/v2"
)
var SupportedFileTypes = []string{".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG"}
var FileTypesMap = map[string]imaging.Format{
".png": imaging.PNG,
".PNG": imaging.PNG,
".jpg": imaging.JPEG,
".JPG": imaging.JPEG,
".jpeg": imaging.JPEG,
".JPEG": imaging.JPEG,
}
var thumbnailSize = 24
var thumbnailCache = map[string][]byte{}
var thumbnailCacheMutex = &sync.RWMutex{}
func IsSupportedFileType(completePath string) bool {
fileExt := filepath.Ext(completePath)
return slices.Contains(SupportedFileTypes, fileExt)
}
func GetThumbnail(c *fiber.Ctx, completePath string) {
c.Set(fiber.HeaderContentType, "image")
thumbnailCacheMutex.RLock()
bytesThumb, ok := thumbnailCache[completePath]
thumbnailCacheMutex.RUnlock()
if ok {
c.Write(bytesThumb)
return
}
fileExt := filepath.Ext(completePath)
if !slices.Contains(SupportedFileTypes, fileExt) {
return
}
f, err := os.Open(completePath)
if err != nil {
return
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return
}
// load images and make 64x64 thumbnails of them
thumbnail := imaging.Thumbnail(img, thumbnailSize, thumbnailSize, imaging.CatmullRom)
// create a new blank image
dst := imaging.New(thumbnailSize, thumbnailSize, color.NRGBA{0, 0, 0, 0})
// paste thumbnails into the new image side by side
dst = imaging.Paste(dst, thumbnail, image.Pt(0, 0))
// save the combined image to buffer
var buf bytes.Buffer
if FileTypesMap[fileExt] == imaging.PNG {
err = png.Encode(&buf, dst)
} else {
err = imaging.Encode(&buf, dst, FileTypesMap[fileExt])
}
if err != nil {
return
}
thumbnailCacheMutex.Lock()
thumbnailCache[completePath] = buf.Bytes()
thumbnailCacheMutex.Unlock()
c.Write(buf.Bytes())
}