mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-19 18:13:41 -05:00
media: add support for generating avatar thumbnails
This commit is contained in:
parent
66c850717a
commit
1b5467cf0e
8 changed files with 189 additions and 40 deletions
|
@ -25,6 +25,7 @@ require (
|
||||||
github.com/coder/websocket v1.8.12 // indirect
|
github.com/coder/websocket v1.8.12 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||||
|
github.com/disintegration/imaging v1.6.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
|
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
@ -63,6 +64,7 @@ require (
|
||||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
|
go.mau.fi/webp v0.2.0 // indirect
|
||||||
go.mau.fi/zeroconfig v0.1.3 // indirect
|
go.mau.fi/zeroconfig v0.1.3 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||||
|
|
|
@ -44,6 +44,8 @@ github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
|
github.com/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
|
||||||
|
@ -166,6 +168,8 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||||
|
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
|
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||||
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
@ -177,6 +181,7 @@ golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/buckket/go-blurhash v1.1.0
|
github.com/buckket/go-blurhash v1.1.0
|
||||||
github.com/chzyer/readline v1.5.1
|
github.com/chzyer/readline v1.5.1
|
||||||
github.com/coder/websocket v1.8.12
|
github.com/coder/websocket v1.8.12
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8
|
github.com/gabriel-vasile/mimetype v1.4.8
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
@ -18,6 +19,7 @@ require (
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/yuin/goldmark v1.7.8
|
github.com/yuin/goldmark v1.7.8
|
||||||
go.mau.fi/util v0.8.4
|
go.mau.fi/util v0.8.4
|
||||||
|
go.mau.fi/webp v0.2.0
|
||||||
go.mau.fi/zeroconfig v0.1.3
|
go.mau.fi/zeroconfig v0.1.3
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.32.0
|
||||||
golang.org/x/image v0.23.0
|
golang.org/x/image v0.23.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -22,6 +22,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
@ -68,12 +70,15 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
|
||||||
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||||
|
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
|
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||||
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
@ -84,6 +89,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|
|
@ -36,16 +36,17 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/buckket/go-blurhash"
|
"github.com/buckket/go-blurhash"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/hlog"
|
"github.com/rs/zerolog/hlog"
|
||||||
_ "golang.org/x/image/webp"
|
|
||||||
|
|
||||||
"go.mau.fi/util/exhttp"
|
"go.mau.fi/util/exhttp"
|
||||||
"go.mau.fi/util/ffmpeg"
|
"go.mau.fi/util/ffmpeg"
|
||||||
"go.mau.fi/util/jsontime"
|
"go.mau.fi/util/jsontime"
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/util/random"
|
"go.mau.fi/util/random"
|
||||||
|
cwebp "go.mau.fi/webp"
|
||||||
|
_ "golang.org/x/image/webp"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/crypto/attachment"
|
"maunium.net/go/mautrix/crypto/attachment"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
@ -59,7 +60,7 @@ var ErrBadGateway = mautrix.RespError{
|
||||||
StatusCode: http.StatusBadGateway,
|
StatusCode: http.StatusBadGateway,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWriter, r *http.Request, entry *database.Media, force bool) bool {
|
func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWriter, r *http.Request, entry *database.Media, force, useThumbnail bool) bool {
|
||||||
if !entry.UseCache() {
|
if !entry.UseCache() {
|
||||||
if force {
|
if force {
|
||||||
mautrix.MNotFound.WithMessage("Media not found in cache").Write(w)
|
mautrix.MNotFound.WithMessage("Media not found in cache").Write(w)
|
||||||
|
@ -67,11 +68,12 @@ func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWr
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
etag := entry.ETag(useThumbnail)
|
||||||
if entry.Error != nil {
|
if entry.Error != nil {
|
||||||
w.Header().Set("Mau-Cached-Error", "true")
|
w.Header().Set("Mau-Cached-Error", "true")
|
||||||
entry.Error.Write(w)
|
entry.Error.Write(w)
|
||||||
return true
|
return true
|
||||||
} else if r.Header.Get("If-None-Match") == entry.ETag() {
|
} else if etag != "" && r.Header.Get("If-None-Match") == etag {
|
||||||
w.WriteHeader(http.StatusNotModified)
|
w.WriteHeader(http.StatusNotModified)
|
||||||
return true
|
return true
|
||||||
} else if entry.MimeType != "" && r.URL.Query().Has("fallback") && !isAllowedAvatarMime(entry.MimeType) {
|
} else if entry.MimeType != "" && r.URL.Query().Has("fallback") && !isAllowedAvatarMime(entry.MimeType) {
|
||||||
|
@ -79,7 +81,36 @@ func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWr
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
log := zerolog.Ctx(ctx)
|
log := zerolog.Ctx(ctx)
|
||||||
cacheFile, err := os.Open(gmx.cacheEntryToPath(entry.Hash[:]))
|
hash := entry.Hash
|
||||||
|
if useThumbnail {
|
||||||
|
if entry.ThumbnailError != "" {
|
||||||
|
log.Debug().Str(zerolog.ErrorFieldName, entry.ThumbnailError).Msg("Returning cached thumbnail error")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if entry.ThumbnailHash == nil {
|
||||||
|
err := gmx.generateAvatarThumbnail(entry, thumbnailMaxSize)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Failed to generate avatar thumbnail")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash = entry.ThumbnailHash
|
||||||
|
}
|
||||||
|
cacheFile, err := os.Open(gmx.cacheEntryToPath(hash[:]))
|
||||||
|
if useThumbnail && errors.Is(err, os.ErrNotExist) {
|
||||||
|
err = gmx.generateAvatarThumbnail(entry, thumbnailMaxSize)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
// Fall through to next error handler
|
||||||
|
} else if err != nil {
|
||||||
|
log.Err(err).Msg("Failed to generate avatar thumbnail")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
cacheFile, err = os.Open(gmx.cacheEntryToPath(hash[:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) && !force {
|
if errors.Is(err, os.ErrNotExist) && !force {
|
||||||
return false
|
return false
|
||||||
|
@ -91,7 +122,7 @@ func (gmx *Gomuks) downloadMediaFromCache(ctx context.Context, w http.ResponseWr
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = cacheFile.Close()
|
_ = cacheFile.Close()
|
||||||
}()
|
}()
|
||||||
cacheEntryToHeaders(w, entry)
|
cacheEntryToHeaders(w, entry, useThumbnail)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, err = io.Copy(w, cacheFile)
|
_, err = io.Copy(w, cacheFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,13 +136,79 @@ func (gmx *Gomuks) cacheEntryToPath(hash []byte) string {
|
||||||
return filepath.Join(gmx.CacheDir, "media", hashPath[0:2], hashPath[2:4], hashPath[4:])
|
return filepath.Join(gmx.CacheDir, "media", hashPath[0:2], hashPath[2:4], hashPath[4:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func cacheEntryToHeaders(w http.ResponseWriter, entry *database.Media) {
|
func cacheEntryToHeaders(w http.ResponseWriter, entry *database.Media, thumbnail bool) {
|
||||||
|
if thumbnail {
|
||||||
|
w.Header().Set("Content-Type", "image/webp")
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(entry.ThumbnailSize, 10))
|
||||||
|
w.Header().Set("Content-Disposition", "inline; filename=thumbnail.webp")
|
||||||
|
} else {
|
||||||
w.Header().Set("Content-Type", entry.MimeType)
|
w.Header().Set("Content-Type", entry.MimeType)
|
||||||
w.Header().Set("Content-Length", strconv.FormatInt(entry.Size, 10))
|
w.Header().Set("Content-Length", strconv.FormatInt(entry.Size, 10))
|
||||||
w.Header().Set("Content-Disposition", mime.FormatMediaType(entry.ContentDisposition(), map[string]string{"filename": entry.FileName}))
|
w.Header().Set("Content-Disposition", mime.FormatMediaType(entry.ContentDisposition(), map[string]string{"filename": entry.FileName}))
|
||||||
|
}
|
||||||
w.Header().Set("Content-Security-Policy", "sandbox; default-src 'none'; script-src 'none'; media-src 'self';")
|
w.Header().Set("Content-Security-Policy", "sandbox; default-src 'none'; script-src 'none'; media-src 'self';")
|
||||||
w.Header().Set("Cache-Control", "max-age=2592000, immutable")
|
w.Header().Set("Cache-Control", "max-age=2592000, immutable")
|
||||||
w.Header().Set("ETag", entry.ETag())
|
w.Header().Set("ETag", entry.ETag(thumbnail))
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnailMaxSize = 80
|
||||||
|
|
||||||
|
func (gmx *Gomuks) generateAvatarThumbnail(entry *database.Media, size int) error {
|
||||||
|
cacheFile, err := os.Open(gmx.cacheEntryToPath(entry.Hash[:]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open full file: %w", err)
|
||||||
|
}
|
||||||
|
img, _, err := image.Decode(cacheFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode image: %w", err)
|
||||||
|
}
|
||||||
|
//bounds := img.Bounds()
|
||||||
|
//origWidth := bounds.Dx()
|
||||||
|
//origHeight := bounds.Dy()
|
||||||
|
//var width, height int
|
||||||
|
//if origWidth == origHeight {
|
||||||
|
// width = size
|
||||||
|
// height = size
|
||||||
|
//} else if origWidth > origHeight {
|
||||||
|
// width = size
|
||||||
|
// height = origHeight * size / origWidth
|
||||||
|
//} else {
|
||||||
|
// width = origWidth * size / origHeight
|
||||||
|
// height = size
|
||||||
|
//}
|
||||||
|
|
||||||
|
tempFile, err := os.CreateTemp(gmx.TempDir, "thumbnail-*")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temporary file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tempFile.Close()
|
||||||
|
_ = os.Remove(tempFile.Name())
|
||||||
|
}()
|
||||||
|
thumbnailImage := imaging.Thumbnail(img, size, size, imaging.Lanczos)
|
||||||
|
fileHasher := sha256.New()
|
||||||
|
wrappedWriter := io.MultiWriter(fileHasher, tempFile)
|
||||||
|
err = cwebp.Encode(wrappedWriter, thumbnailImage, &cwebp.Options{Quality: 80})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encode thumbnail: %w", err)
|
||||||
|
}
|
||||||
|
fileInfo, err := tempFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to stat temporary file: %w", err)
|
||||||
|
}
|
||||||
|
entry.ThumbnailHash = (*[32]byte)(fileHasher.Sum(nil))
|
||||||
|
entry.ThumbnailError = ""
|
||||||
|
entry.ThumbnailSize = fileInfo.Size()
|
||||||
|
cachePath := gmx.cacheEntryToPath(entry.ThumbnailHash[:])
|
||||||
|
err = os.MkdirAll(filepath.Dir(cachePath), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||||
|
}
|
||||||
|
err = os.Rename(tempFile.Name(), cachePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to rename temporary file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type noErrorWriter struct {
|
type noErrorWriter struct {
|
||||||
|
@ -191,6 +288,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted, _ := strconv.ParseBool(query.Get("encrypted"))
|
encrypted, _ := strconv.ParseBool(query.Get("encrypted"))
|
||||||
|
useThumbnail := query.Get("thumbnail") == "avatar"
|
||||||
|
|
||||||
logVal := zerolog.Ctx(r.Context()).With().
|
logVal := zerolog.Ctx(r.Context()).With().
|
||||||
Stringer("mxc_uri", mxc).
|
Stringer("mxc_uri", mxc).
|
||||||
|
@ -211,7 +309,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, false) {
|
if gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, false, useThumbnail) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,8 +399,8 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
cacheEntry.Size = resp.ContentLength
|
cacheEntry.Size = resp.ContentLength
|
||||||
fileHasher := sha256.New()
|
fileHasher := sha256.New()
|
||||||
wrappedReader := io.TeeReader(reader, fileHasher)
|
wrappedReader := io.TeeReader(reader, fileHasher)
|
||||||
if cacheEntry.Size > 0 && cacheEntry.EncFile == nil {
|
if cacheEntry.Size > 0 && cacheEntry.EncFile == nil && !useThumbnail {
|
||||||
cacheEntryToHeaders(w, cacheEntry)
|
cacheEntryToHeaders(w, cacheEntry, useThumbnail)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
wrappedReader = io.TeeReader(wrappedReader, &noErrorWriter{w})
|
wrappedReader = io.TeeReader(wrappedReader, &noErrorWriter{w})
|
||||||
w = nil
|
w = nil
|
||||||
|
@ -342,7 +440,7 @@ func (gmx *Gomuks) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w != nil {
|
if w != nil {
|
||||||
gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, true)
|
gmx.downloadMediaFromCache(ctx, w, r, cacheEntry, true, useThumbnail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,24 +23,27 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
insertMediaQuery = `
|
insertMediaQuery = `
|
||||||
INSERT INTO media (mxc, enc_file, file_name, mime_type, size, hash, error)
|
INSERT INTO media (mxc, enc_file, file_name, mime_type, size, hash, error, thumbnail_size, thumbnail_hash, thumbnail_error)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
ON CONFLICT (mxc) DO NOTHING
|
ON CONFLICT (mxc) DO NOTHING
|
||||||
`
|
`
|
||||||
upsertMediaQuery = `
|
upsertMediaQuery = `
|
||||||
INSERT INTO media (mxc, enc_file, file_name, mime_type, size, hash, error)
|
INSERT INTO media (mxc, enc_file, file_name, mime_type, size, hash, error, thumbnail_size, thumbnail_hash, thumbnail_error)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
ON CONFLICT (mxc) DO UPDATE
|
ON CONFLICT (mxc) DO UPDATE
|
||||||
SET enc_file = COALESCE(excluded.enc_file, media.enc_file),
|
SET enc_file = COALESCE(excluded.enc_file, media.enc_file),
|
||||||
file_name = COALESCE(excluded.file_name, media.file_name),
|
file_name = COALESCE(excluded.file_name, media.file_name),
|
||||||
mime_type = COALESCE(excluded.mime_type, media.mime_type),
|
mime_type = COALESCE(excluded.mime_type, media.mime_type),
|
||||||
size = COALESCE(excluded.size, media.size),
|
size = COALESCE(excluded.size, media.size),
|
||||||
hash = COALESCE(excluded.hash, media.hash),
|
hash = COALESCE(excluded.hash, media.hash),
|
||||||
error = excluded.error
|
error = excluded.error,
|
||||||
|
thumbnail_size = COALESCE(excluded.thumbnail_size, media.thumbnail_size),
|
||||||
|
thumbnail_hash = COALESCE(excluded.thumbnail_hash, media.thumbnail_hash),
|
||||||
|
thumbnail_error = excluded.thumbnail_error
|
||||||
WHERE excluded.error IS NULL OR media.hash IS NULL
|
WHERE excluded.error IS NULL OR media.hash IS NULL
|
||||||
`
|
`
|
||||||
getMediaQuery = `
|
getMediaQuery = `
|
||||||
SELECT mxc, enc_file, file_name, mime_type, size, hash, error
|
SELECT mxc, enc_file, file_name, mime_type, size, hash, error, thumbnail_size, thumbnail_hash, thumbnail_error
|
||||||
FROM media
|
FROM media
|
||||||
WHERE mxc = $1
|
WHERE mxc = $1
|
||||||
`
|
`
|
||||||
|
@ -137,9 +140,22 @@ type Media struct {
|
||||||
Size int64
|
Size int64
|
||||||
Hash *[32]byte
|
Hash *[32]byte
|
||||||
Error *MediaError
|
Error *MediaError
|
||||||
|
|
||||||
|
ThumbnailError string
|
||||||
|
ThumbnailSize int64
|
||||||
|
ThumbnailHash *[32]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Media) ETag() string {
|
func (m *Media) ETag(thumbnail bool) string {
|
||||||
|
if m == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if thumbnail {
|
||||||
|
if m.ThumbnailHash == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`"%x"`, m.ThumbnailHash)
|
||||||
|
}
|
||||||
if m.Hash == nil {
|
if m.Hash == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -151,14 +167,18 @@ func (m *Media) UseCache() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Media) sqlVariables() []any {
|
func (m *Media) sqlVariables() []any {
|
||||||
var hash []byte
|
var hash, thumbnailHash []byte
|
||||||
if m.Hash != nil {
|
if m.Hash != nil {
|
||||||
hash = m.Hash[:]
|
hash = m.Hash[:]
|
||||||
}
|
}
|
||||||
|
if m.ThumbnailHash != nil {
|
||||||
|
thumbnailHash = m.ThumbnailHash[:]
|
||||||
|
}
|
||||||
return []any{
|
return []any{
|
||||||
&m.MXC, dbutil.JSONPtr(m.EncFile),
|
&m.MXC, dbutil.JSONPtr(m.EncFile),
|
||||||
dbutil.StrPtr(m.FileName), dbutil.StrPtr(m.MimeType), dbutil.NumPtr(m.Size),
|
dbutil.StrPtr(m.FileName), dbutil.StrPtr(m.MimeType), dbutil.NumPtr(m.Size),
|
||||||
hash, dbutil.JSONPtr(m.Error),
|
hash, dbutil.JSONPtr(m.Error),
|
||||||
|
dbutil.NumPtr(m.ThumbnailSize), thumbnailHash, dbutil.StrPtr(m.ThumbnailError),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,19 +192,27 @@ var safeMimes = []string{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Media) Scan(row dbutil.Scannable) (*Media, error) {
|
func (m *Media) Scan(row dbutil.Scannable) (*Media, error) {
|
||||||
var mimeType, fileName sql.NullString
|
var mimeType, fileName, thumbnailError sql.NullString
|
||||||
var size sql.NullInt64
|
var size, thumbnailSize sql.NullInt64
|
||||||
var hash []byte
|
var hash, thumbnailHash []byte
|
||||||
err := row.Scan(&m.MXC, dbutil.JSON{Data: &m.EncFile}, &fileName, &mimeType, &size, &hash, dbutil.JSON{Data: &m.Error})
|
err := row.Scan(
|
||||||
|
&m.MXC, dbutil.JSON{Data: &m.EncFile}, &fileName, &mimeType, &size,
|
||||||
|
&hash, dbutil.JSON{Data: &m.Error}, &thumbnailSize, &thumbnailHash, &thumbnailError,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m.MimeType = mimeType.String
|
m.MimeType = mimeType.String
|
||||||
m.FileName = fileName.String
|
m.FileName = fileName.String
|
||||||
m.Size = size.Int64
|
m.Size = size.Int64
|
||||||
|
m.ThumbnailSize = thumbnailSize.Int64
|
||||||
|
m.ThumbnailError = thumbnailError.String
|
||||||
if len(hash) == 32 {
|
if len(hash) == 32 {
|
||||||
m.Hash = (*[32]byte)(hash)
|
m.Hash = (*[32]byte)(hash)
|
||||||
}
|
}
|
||||||
|
if len(thumbnailHash) == 32 {
|
||||||
|
m.ThumbnailHash = (*[32]byte)(thumbnailHash)
|
||||||
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- v0 -> v12 (compatible with v10+): Latest revision
|
-- v0 -> v13 (compatible with v10+): Latest revision
|
||||||
CREATE TABLE account (
|
CREATE TABLE account (
|
||||||
user_id TEXT NOT NULL PRIMARY KEY,
|
user_id TEXT NOT NULL PRIMARY KEY,
|
||||||
device_id TEXT NOT NULL,
|
device_id TEXT NOT NULL,
|
||||||
|
@ -218,7 +218,11 @@ CREATE TABLE media (
|
||||||
mime_type TEXT,
|
mime_type TEXT,
|
||||||
size INTEGER,
|
size INTEGER,
|
||||||
hash BLOB,
|
hash BLOB,
|
||||||
error TEXT
|
error TEXT,
|
||||||
|
|
||||||
|
thumbnail_size INTEGER,
|
||||||
|
thumbnail_hash BLOB,
|
||||||
|
thumbnail_error TEXT
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE media_reference (
|
CREATE TABLE media_reference (
|
||||||
|
|
4
pkg/hicli/database/upgrades/13-media-thumbnails.sql
Normal file
4
pkg/hicli/database/upgrades/13-media-thumbnails.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-- v13 (compatible with v10+): Add columns for media thumbnails
|
||||||
|
ALTER TABLE media ADD COLUMN thumbnail_size INTEGER;
|
||||||
|
ALTER TABLE media ADD COLUMN thumbnail_hash BLOB;
|
||||||
|
ALTER TABLE media ADD COLUMN thumbnail_error TEXT;
|
Loading…
Add table
Reference in a new issue