feat: add thumbnails
This commit is contained in:
parent
f09916f44e
commit
db4aae2e5b
2
go.mod
2
go.mod
@ -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
5
go.sum
@ -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=
|
||||
|
14
html/html.go
14
html/html.go
@ -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
|
||||
|
2
main.go
2
main.go
@ -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)
|
||||
|
@ -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
79
thumbnail/thumbnail.go
Normal 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())
|
||||
}
|
Loading…
Reference in New Issue
Block a user