[feature] update config types to use bytesize.Size (#828)

* update config size types to use bytesize.Size

* submit unchecked-out file ... 🤦

* fix bytesize config var decoding

* bump bytesize version

* update kim's libraries in readme

* update envparse.sh to output more useful errors

* improve envparse.sh

* remove reliance on jq

* instead, use uint64 for bytesize flag types

* remove redundant type

* fix viper unmarshaling

* Update envparsing.sh

* fix envparsing test

Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tobi <31960611+tsmethurst@users.noreply.github.com>
This commit is contained in:
kim 2022-09-29 21:50:43 +01:00 committed by GitHub
parent f0bf69d4d0
commit 1d999712e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 223 additions and 169 deletions

View file

@ -219,6 +219,8 @@ The following libraries and frameworks are used by GoToSocial, with gratitude
- [go-playground/validator](https://github.com/go-playground/validator); struct validation. [MIT License](https://spdx.org/licenses/MIT.html).
- [gorilla/websocket](https://github.com/gorilla/websocket); Websocket connectivity. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html).
- [gruf/go-debug](https://codeberg.org/gruf/go-debug); profiling support in debug builds. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-bytesize](https://codeberg.org/gruf/go-bytesize); byte size parsing / formatting. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-cache](https://codeberg.org/gruf/go-cache); object caching. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-kv](https://codeberg.org/gruf/go-kv); key-value field formatting. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-mutexes](https://codeberg.org/gruf/go-mutexes); mutex map. [MIT License](https://spdx.org/licenses/MIT.html).
- [gruf/go-runners](https://codeberg.org/gruf/go-runners); worker pool library. [MIT License](https://spdx.org/licenses/MIT.html).

2
go.mod
View file

@ -4,7 +4,7 @@ go 1.19
require (
codeberg.org/gruf/go-atomics v1.1.0
codeberg.org/gruf/go-bytesize v0.2.1
codeberg.org/gruf/go-bytesize v1.0.0
codeberg.org/gruf/go-byteutil v1.0.2
codeberg.org/gruf/go-cache/v2 v2.1.4
codeberg.org/gruf/go-debug v1.2.0

4
go.sum
View file

@ -64,8 +64,8 @@ codeberg.org/gruf/go-bitutil v1.0.1/go.mod h1:3ezHnADoiRJs9jgn65AEZ3HY7dsabAYLmm
codeberg.org/gruf/go-bytes v1.0.0/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
codeberg.org/gruf/go-bytes v1.0.2 h1:malqE42Ni+h1nnYWBUAJaDDtEzF4aeN4uPN8DfMNNvo=
codeberg.org/gruf/go-bytes v1.0.2/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
codeberg.org/gruf/go-bytesize v0.2.1 h1:nbAta3FCYe3Q18osqg8Ylk/naOopdqEKiKMpo6KTpAA=
codeberg.org/gruf/go-bytesize v0.2.1/go.mod h1:n/GU8HzL9f3UNp/mUKyr1qVmTlj7+xacpp0OHfkvLPs=
codeberg.org/gruf/go-bytesize v1.0.0 h1:/Mcv4prniJLkPEqZ+LZ5/D/e27rNrZZEMmty9jpIvlc=
codeberg.org/gruf/go-bytesize v1.0.0/go.mod h1:n/GU8HzL9f3UNp/mUKyr1qVmTlj7+xacpp0OHfkvLPs=
codeberg.org/gruf/go-byteutil v1.0.0/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU=
codeberg.org/gruf/go-byteutil v1.0.2 h1:OesVyK5VKWeWdeDR00zRJ+Oy8hjXx1pBhn7WVvcZWVE=
codeberg.org/gruf/go-byteutil v1.0.2/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU=

View file

@ -21,6 +21,7 @@ package config
import (
"reflect"
"codeberg.org/gruf/go-bytesize"
"github.com/mitchellh/mapstructure"
)
@ -76,13 +77,13 @@ type Configuration struct {
AccountsReasonRequired bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"`
AccountsAllowCustomCSS bool `name:"accounts-allow-custom-css" usage:"Allow accounts to enable custom CSS for their profile pages and statuses."`
MediaImageMaxSize int `name:"media-image-max-size" usage:"Max size of accepted images in bytes"`
MediaVideoMaxSize int `name:"media-video-max-size" usage:"Max size of accepted videos in bytes"`
MediaDescriptionMinChars int `name:"media-description-min-chars" usage:"Min required chars for an image description"`
MediaDescriptionMaxChars int `name:"media-description-max-chars" usage:"Max permitted chars for an image description"`
MediaRemoteCacheDays int `name:"media-remote-cache-days" usage:"Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely."`
MediaEmojiLocalMaxSize int `name:"media-emoji-local-max-size" usage:"Max size in bytes of emojis uploaded to this instance via the admin API."`
MediaEmojiRemoteMaxSize int `name:"media-emoji-remote-max-size" usage:"Max size in bytes of emojis to download from other instances."`
MediaImageMaxSize bytesize.Size `name:"media-image-max-size" usage:"Max size of accepted images in bytes"`
MediaVideoMaxSize bytesize.Size `name:"media-video-max-size" usage:"Max size of accepted videos in bytes"`
MediaDescriptionMinChars int `name:"media-description-min-chars" usage:"Min required chars for an image description"`
MediaDescriptionMaxChars int `name:"media-description-max-chars" usage:"Max permitted chars for an image description"`
MediaRemoteCacheDays int `name:"media-remote-cache-days" usage:"Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely."`
MediaEmojiLocalMaxSize bytesize.Size `name:"media-emoji-local-max-size" usage:"Max size in bytes of emojis uploaded to this instance via the admin API."`
MediaEmojiRemoteMaxSize bytesize.Size `name:"media-emoji-remote-max-size" usage:"Max size in bytes of emojis to download from other instances."`
StorageBackend string `name:"storage-backend" usage:"Storage backend to use for media attachments"`
StorageLocalBasePath string `name:"storage-local-base-path" usage:"Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir."`

View file

@ -72,13 +72,13 @@ func AddServerFlags(cmd *cobra.Command) {
cmd.Flags().Bool(AccountsAllowCustomCSSFlag(), cfg.AccountsAllowCustomCSS, fieldtag("AccountsAllowCustomCSS", "usage"))
// Media
cmd.Flags().Int(MediaImageMaxSizeFlag(), cfg.MediaImageMaxSize, fieldtag("MediaImageMaxSize", "usage"))
cmd.Flags().Int(MediaVideoMaxSizeFlag(), cfg.MediaVideoMaxSize, fieldtag("MediaVideoMaxSize", "usage"))
cmd.Flags().Uint64(MediaImageMaxSizeFlag(), uint64(cfg.MediaImageMaxSize), fieldtag("MediaImageMaxSize", "usage"))
cmd.Flags().Uint64(MediaVideoMaxSizeFlag(), uint64(cfg.MediaVideoMaxSize), fieldtag("MediaVideoMaxSize", "usage"))
cmd.Flags().Int(MediaDescriptionMinCharsFlag(), cfg.MediaDescriptionMinChars, fieldtag("MediaDescriptionMinChars", "usage"))
cmd.Flags().Int(MediaDescriptionMaxCharsFlag(), cfg.MediaDescriptionMaxChars, fieldtag("MediaDescriptionMaxChars", "usage"))
cmd.Flags().Int(MediaRemoteCacheDaysFlag(), cfg.MediaRemoteCacheDays, fieldtag("MediaRemoteCacheDays", "usage"))
cmd.Flags().Int(MediaEmojiLocalMaxSizeFlag(), cfg.MediaEmojiLocalMaxSize, fieldtag("MediaEmojiLocalMaxSize", "usage"))
cmd.Flags().Int(MediaEmojiRemoteMaxSizeFlag(), cfg.MediaEmojiRemoteMaxSize, fieldtag("MediaEmojiRemoteMaxSize", "usage"))
cmd.Flags().Uint64(MediaEmojiLocalMaxSizeFlag(), uint64(cfg.MediaEmojiLocalMaxSize), fieldtag("MediaEmojiLocalMaxSize", "usage"))
cmd.Flags().Uint64(MediaEmojiRemoteMaxSizeFlag(), uint64(cfg.MediaEmojiRemoteMaxSize), fieldtag("MediaEmojiRemoteMaxSize", "usage"))
// Storage
cmd.Flags().String(StorageBackendFlag(), cfg.StorageBackend, fieldtag("StorageBackend", "usage"))

View file

@ -100,7 +100,7 @@ func main() {
fmt.Fprintf(output, "func Set%[1]s(v %[2]s) { global.Set%[1]s(v) }\n\n", field.Name, field.Type.String())
}
_ = output.Close()
_ = exec.Command("gofmt", "-w", out).Run()
_ = exec.Command("gofumports", "-w", out).Run()
// The plain here is that eventually we might be able
// to generate an example configuration from struct tags

View file

@ -18,7 +18,9 @@
package config
import "github.com/spf13/cobra"
import (
"github.com/spf13/cobra"
)
var global *ConfigState

View file

@ -18,6 +18,8 @@
*/
package config
import "codeberg.org/gruf/go-bytesize"
// GetLogLevel safely fetches the Configuration value for state's 'LogLevel' field
func (st *ConfigState) GetLogLevel() (v string) {
st.mutex.Lock()
@ -719,7 +721,7 @@ func GetAccountsAllowCustomCSS() bool { return global.GetAccountsAllowCustomCSS(
func SetAccountsAllowCustomCSS(v bool) { global.SetAccountsAllowCustomCSS(v) }
// GetMediaImageMaxSize safely fetches the Configuration value for state's 'MediaImageMaxSize' field
func (st *ConfigState) GetMediaImageMaxSize() (v int) {
func (st *ConfigState) GetMediaImageMaxSize() (v bytesize.Size) {
st.mutex.Lock()
v = st.config.MediaImageMaxSize
st.mutex.Unlock()
@ -727,7 +729,7 @@ func (st *ConfigState) GetMediaImageMaxSize() (v int) {
}
// SetMediaImageMaxSize safely sets the Configuration value for state's 'MediaImageMaxSize' field
func (st *ConfigState) SetMediaImageMaxSize(v int) {
func (st *ConfigState) SetMediaImageMaxSize(v bytesize.Size) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.MediaImageMaxSize = v
@ -738,13 +740,13 @@ func (st *ConfigState) SetMediaImageMaxSize(v int) {
func MediaImageMaxSizeFlag() string { return "media-image-max-size" }
// GetMediaImageMaxSize safely fetches the value for global configuration 'MediaImageMaxSize' field
func GetMediaImageMaxSize() int { return global.GetMediaImageMaxSize() }
func GetMediaImageMaxSize() bytesize.Size { return global.GetMediaImageMaxSize() }
// SetMediaImageMaxSize safely sets the value for global configuration 'MediaImageMaxSize' field
func SetMediaImageMaxSize(v int) { global.SetMediaImageMaxSize(v) }
func SetMediaImageMaxSize(v bytesize.Size) { global.SetMediaImageMaxSize(v) }
// GetMediaVideoMaxSize safely fetches the Configuration value for state's 'MediaVideoMaxSize' field
func (st *ConfigState) GetMediaVideoMaxSize() (v int) {
func (st *ConfigState) GetMediaVideoMaxSize() (v bytesize.Size) {
st.mutex.Lock()
v = st.config.MediaVideoMaxSize
st.mutex.Unlock()
@ -752,7 +754,7 @@ func (st *ConfigState) GetMediaVideoMaxSize() (v int) {
}
// SetMediaVideoMaxSize safely sets the Configuration value for state's 'MediaVideoMaxSize' field
func (st *ConfigState) SetMediaVideoMaxSize(v int) {
func (st *ConfigState) SetMediaVideoMaxSize(v bytesize.Size) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.MediaVideoMaxSize = v
@ -763,10 +765,10 @@ func (st *ConfigState) SetMediaVideoMaxSize(v int) {
func MediaVideoMaxSizeFlag() string { return "media-video-max-size" }
// GetMediaVideoMaxSize safely fetches the value for global configuration 'MediaVideoMaxSize' field
func GetMediaVideoMaxSize() int { return global.GetMediaVideoMaxSize() }
func GetMediaVideoMaxSize() bytesize.Size { return global.GetMediaVideoMaxSize() }
// SetMediaVideoMaxSize safely sets the value for global configuration 'MediaVideoMaxSize' field
func SetMediaVideoMaxSize(v int) { global.SetMediaVideoMaxSize(v) }
func SetMediaVideoMaxSize(v bytesize.Size) { global.SetMediaVideoMaxSize(v) }
// GetMediaDescriptionMinChars safely fetches the Configuration value for state's 'MediaDescriptionMinChars' field
func (st *ConfigState) GetMediaDescriptionMinChars() (v int) {
@ -844,7 +846,7 @@ func GetMediaRemoteCacheDays() int { return global.GetMediaRemoteCacheDays() }
func SetMediaRemoteCacheDays(v int) { global.SetMediaRemoteCacheDays(v) }
// GetMediaEmojiLocalMaxSize safely fetches the Configuration value for state's 'MediaEmojiLocalMaxSize' field
func (st *ConfigState) GetMediaEmojiLocalMaxSize() (v int) {
func (st *ConfigState) GetMediaEmojiLocalMaxSize() (v bytesize.Size) {
st.mutex.Lock()
v = st.config.MediaEmojiLocalMaxSize
st.mutex.Unlock()
@ -852,7 +854,7 @@ func (st *ConfigState) GetMediaEmojiLocalMaxSize() (v int) {
}
// SetMediaEmojiLocalMaxSize safely sets the Configuration value for state's 'MediaEmojiLocalMaxSize' field
func (st *ConfigState) SetMediaEmojiLocalMaxSize(v int) {
func (st *ConfigState) SetMediaEmojiLocalMaxSize(v bytesize.Size) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.MediaEmojiLocalMaxSize = v
@ -863,13 +865,13 @@ func (st *ConfigState) SetMediaEmojiLocalMaxSize(v int) {
func MediaEmojiLocalMaxSizeFlag() string { return "media-emoji-local-max-size" }
// GetMediaEmojiLocalMaxSize safely fetches the value for global configuration 'MediaEmojiLocalMaxSize' field
func GetMediaEmojiLocalMaxSize() int { return global.GetMediaEmojiLocalMaxSize() }
func GetMediaEmojiLocalMaxSize() bytesize.Size { return global.GetMediaEmojiLocalMaxSize() }
// SetMediaEmojiLocalMaxSize safely sets the value for global configuration 'MediaEmojiLocalMaxSize' field
func SetMediaEmojiLocalMaxSize(v int) { global.SetMediaEmojiLocalMaxSize(v) }
func SetMediaEmojiLocalMaxSize(v bytesize.Size) { global.SetMediaEmojiLocalMaxSize(v) }
// GetMediaEmojiRemoteMaxSize safely fetches the Configuration value for state's 'MediaEmojiRemoteMaxSize' field
func (st *ConfigState) GetMediaEmojiRemoteMaxSize() (v int) {
func (st *ConfigState) GetMediaEmojiRemoteMaxSize() (v bytesize.Size) {
st.mutex.Lock()
v = st.config.MediaEmojiRemoteMaxSize
st.mutex.Unlock()
@ -877,7 +879,7 @@ func (st *ConfigState) GetMediaEmojiRemoteMaxSize() (v int) {
}
// SetMediaEmojiRemoteMaxSize safely sets the Configuration value for state's 'MediaEmojiRemoteMaxSize' field
func (st *ConfigState) SetMediaEmojiRemoteMaxSize(v int) {
func (st *ConfigState) SetMediaEmojiRemoteMaxSize(v bytesize.Size) {
st.mutex.Lock()
defer st.mutex.Unlock()
st.config.MediaEmojiRemoteMaxSize = v
@ -888,10 +890,10 @@ func (st *ConfigState) SetMediaEmojiRemoteMaxSize(v int) {
func MediaEmojiRemoteMaxSizeFlag() string { return "media-emoji-remote-max-size" }
// GetMediaEmojiRemoteMaxSize safely fetches the value for global configuration 'MediaEmojiRemoteMaxSize' field
func GetMediaEmojiRemoteMaxSize() int { return global.GetMediaEmojiRemoteMaxSize() }
func GetMediaEmojiRemoteMaxSize() bytesize.Size { return global.GetMediaEmojiRemoteMaxSize() }
// SetMediaEmojiRemoteMaxSize safely sets the value for global configuration 'MediaEmojiRemoteMaxSize' field
func SetMediaEmojiRemoteMaxSize(v int) { global.SetMediaEmojiRemoteMaxSize(v) }
func SetMediaEmojiRemoteMaxSize(v bytesize.Size) { global.SetMediaEmojiRemoteMaxSize(v) }
// GetStorageBackend safely fetches the Configuration value for state's 'StorageBackend' field
func (st *ConfigState) GetStorageBackend() (v string) {

View file

@ -133,6 +133,12 @@ func (st *ConfigState) reloadFromViper() {
if err := st.viper.Unmarshal(&st.config, func(c *mapstructure.DecoderConfig) {
c.TagName = "name"
c.ZeroFields = true // empty the config struct before we marshal values into it
oldhook := c.DecodeHook
c.DecodeHook = mapstructure.ComposeDecodeHookFunc(
mapstructure.TextUnmarshallerHookFunc(),
oldhook,
)
}); err != nil {
panic(err)
}

View file

@ -67,14 +67,6 @@ func Validate() error {
errs = append(errs, fmt.Errorf("%s must be set", WebAssetBaseDirFlag()))
}
if m := GetMediaEmojiLocalMaxSize(); m < 0 {
errs = append(errs, fmt.Errorf("%s must not be less than 0", MediaEmojiLocalMaxSizeFlag()))
}
if m := GetMediaEmojiRemoteMaxSize(); m < 0 {
errs = append(errs, fmt.Errorf("%s must not be less than 0", MediaEmojiRemoteMaxSizeFlag()))
}
if len(errs) > 0 {
errStrings := []string{}
for _, err := range errs {

View file

@ -141,16 +141,6 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigBadProtocolNoHost() {
suite.EqualError(err, "host must be set; protocol must be set to either http or https, provided value was foo")
}
func (suite *ConfigValidateTestSuite) TestValidateConfigBadEmojiSizes() {
testrig.InitTestConfig()
config.SetMediaEmojiLocalMaxSize(-10)
config.SetMediaEmojiRemoteMaxSize(-50)
err := config.Validate()
suite.EqualError(err, "media-emoji-local-max-size must not be less than 0; media-emoji-remote-max-size must not be less than 0")
}
func TestConfigValidateTestSuite(t *testing.T) {
suite.Run(t, &ConfigValidateTestSuite{})
}

View file

@ -496,7 +496,7 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
}
}
data := func(innerCtx context.Context) (io.Reader, int, error) {
data := func(innerCtx context.Context) (io.Reader, int64, error) {
return t.DereferenceMedia(innerCtx, avatarIRI)
}
@ -562,7 +562,7 @@ func (d *deref) fetchRemoteAccountMedia(ctx context.Context, targetAccount *gtsm
}
}
data := func(innerCtx context.Context) (io.Reader, int, error) {
data := func(innerCtx context.Context) (io.Reader, int64, error) {
return t.DereferenceMedia(innerCtx, headerIRI)
}

View file

@ -42,7 +42,7 @@ func (d *deref) GetRemoteEmoji(ctx context.Context, requestingUsername string, r
return nil, fmt.Errorf("GetRemoteEmoji: error parsing url: %s", err)
}
dataFunc := func(innerCtx context.Context) (io.Reader, int, error) {
dataFunc := func(innerCtx context.Context) (io.Reader, int64, error) {
return t.DereferenceMedia(innerCtx, derefURI)
}

View file

@ -42,7 +42,7 @@ func (d *deref) GetRemoteMedia(ctx context.Context, requestingUsername string, a
return nil, fmt.Errorf("GetRemoteMedia: error parsing url: %s", err)
}
dataFunc := func(innerCtx context.Context) (io.Reader, int, error) {
dataFunc := func(innerCtx context.Context) (io.Reader, int64, error) {
return t.DereferenceMedia(innerCtx, derefURI)
}

View file

@ -43,13 +43,13 @@ type ManagerTestSuite struct {
func (suite *ManagerTestSuite) TestEmojiProcessBlocking() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/rainbow-original.png")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
emojiID := "01GDQ9G782X42BAMFASKP64343"
@ -104,13 +104,13 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlocking() {
func (suite *ManagerTestSuite) TestEmojiProcessBlockingTooLarge() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/big-panda.gif")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
emojiID := "01GDQ9G782X42BAMFASKP64343"
@ -128,13 +128,13 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingTooLarge() {
func (suite *ManagerTestSuite) TestEmojiProcessBlockingTooLargeNoSizeGiven() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/big-panda.gif")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
emojiID := "01GDQ9G782X42BAMFASKP64343"
@ -152,7 +152,7 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingTooLargeNoSizeGiven() {
func (suite *ManagerTestSuite) TestEmojiProcessBlockingNoFileSizeGiven() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/rainbow-original.png")
if err != nil {
@ -214,13 +214,13 @@ func (suite *ManagerTestSuite) TestEmojiProcessBlockingNoFileSizeGiven() {
func (suite *ManagerTestSuite) TestSimpleJpegProcessBlocking() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-jpeg.jpg")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
@ -286,7 +286,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlocking() {
func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingNoContentLengthGiven() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-jpeg.jpg")
if err != nil {
@ -359,7 +359,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingNoContentLengthGiven
func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingReadCloser() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// open test image as a file
f, err := os.Open("./test/test-jpeg.jpg")
if err != nil {
@ -432,13 +432,13 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingReadCloser() {
func (suite *ManagerTestSuite) TestPngNoAlphaChannelProcessBlocking() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-png-noalphachannel.png")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
@ -504,13 +504,13 @@ func (suite *ManagerTestSuite) TestPngNoAlphaChannelProcessBlocking() {
func (suite *ManagerTestSuite) TestPngAlphaChannelProcessBlocking() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-png-alphachannel.png")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
@ -576,13 +576,13 @@ func (suite *ManagerTestSuite) TestPngAlphaChannelProcessBlocking() {
func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithCallback() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-jpeg.jpg")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
// test the callback function by setting a simple boolean
@ -659,13 +659,13 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithCallback() {
func (suite *ManagerTestSuite) TestSimpleJpegProcessAsync() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-jpeg.jpg")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
@ -744,9 +744,9 @@ func (suite *ManagerTestSuite) TestSimpleJpegQueueSpamming() {
panic(err)
}
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
return bytes.NewReader(b), len(b), nil
return bytes.NewReader(b), int64(len(b)), nil
}
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"
@ -820,13 +820,13 @@ func (suite *ManagerTestSuite) TestSimpleJpegQueueSpamming() {
func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithDiskStorage() {
ctx := context.Background()
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("./test/test-jpeg.jpg")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
accountID := "01FS1X72SK9ZPW0J1QQ68BD264"

View file

@ -210,11 +210,11 @@ func (p *ProcessingEmoji) store(ctx context.Context) error {
// concatenate the first bytes with the existing bytes still in the reader (thanks Mara)
readerToStore := io.MultiReader(bytes.NewBuffer(firstBytes), reader)
var maxEmojiSize int
var maxEmojiSize int64
if p.emoji.Domain == "" {
maxEmojiSize = config.GetMediaEmojiLocalMaxSize()
maxEmojiSize = int64(config.GetMediaEmojiLocalMaxSize())
} else {
maxEmojiSize = config.GetMediaEmojiRemoteMaxSize()
maxEmojiSize = int64(config.GetMediaEmojiRemoteMaxSize())
}
// if we know the fileSize already, make sure it's not bigger than our limit
@ -241,7 +241,7 @@ func (p *ProcessingEmoji) store(ctx context.Context) error {
return fmt.Errorf("store: discovered emoji fileSize (%db) is larger than allowed emojiRemoteMaxSize (%db)", fileSize, maxEmojiSize)
}
p.emoji.ImageFileSize = fileSize
p.emoji.ImageFileSize = int(fileSize)
p.read = true
if p.postData != nil {

View file

@ -315,7 +315,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
p.attachment.Type = gtsmodel.FileTypeImage
if fileSize > 0 {
var err error
readerToStore, err = terminator.Terminate(readerToStore, fileSize, extension)
readerToStore, err = terminator.Terminate(readerToStore, int(fileSize), extension)
if err != nil {
return fmt.Errorf("store: exif error: %s", err)
}
@ -344,7 +344,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
cached := true
p.attachment.Cached = &cached
p.attachment.File.FileSize = fileSize
p.attachment.File.FileSize = int(fileSize)
p.read = true
if p.postData != nil {

View file

@ -74,13 +74,13 @@ func (suite *PruneRemoteTestSuite) TestPruneAndRecache() {
suite.ErrorIs(err, storage.ErrNotFound)
// now recache the image....
data := func(_ context.Context) (io.Reader, int, error) {
data := func(_ context.Context) (io.Reader, int64, error) {
// load bytes from a test image
b, err := os.ReadFile("../../testrig/media/thoughtsofdog-original.jpeg")
if err != nil {
panic(err)
}
return bytes.NewBuffer(b), len(b), nil
return bytes.NewBuffer(b), int64(len(b)), nil
}
processingRecache, err := suite.manager.RecacheMedia(ctx, data, nil, testAttachment.ID)
suite.NoError(err)

View file

@ -118,7 +118,7 @@ type AdditionalEmojiInfo struct {
}
// DataFunc represents a function used to retrieve the raw bytes of a piece of media.
type DataFunc func(ctx context.Context) (reader io.Reader, fileSize int, err error)
type DataFunc func(ctx context.Context) (reader io.Reader, fileSize int64, err error)
// PostDataCallbackFunc represents a function executed after the DataFunc has been executed,
// and the returned reader has been read. It can be used to clean up any remaining resources.

View file

@ -151,19 +151,19 @@ func parseOlderThan(olderThanDays int) (time.Time, error) {
// lengthReader wraps a reader and reads the length of total bytes written as it goes.
type lengthReader struct {
source io.Reader
length int
length int64
}
func (r *lengthReader) Read(b []byte) (int, error) {
n, err := r.source.Read(b)
r.length += n
r.length += int64(n)
return n, err
}
// putStream either puts a file with a known fileSize into storage directly, and returns the
// fileSize unchanged, or it wraps the reader with a lengthReader and returns the discovered
// fileSize.
func putStream(ctx context.Context, storage storage.Driver, key string, r io.Reader, fileSize int) (int, error) {
func putStream(ctx context.Context, storage storage.Driver, key string, r io.Reader, fileSize int64) (int64, error) {
if fileSize > 0 {
return fileSize, storage.PutStream(ctx, key, r)
}

View file

@ -184,13 +184,13 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
// the account's new avatar image.
func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
maxImageSize := config.GetMediaImageMaxSize()
if int(avatar.Size) > maxImageSize {
if avatar.Size > int64(maxImageSize) {
return nil, fmt.Errorf("UpdateAvatar: avatar with size %d exceeded max image size of %d bytes", avatar.Size, maxImageSize)
}
dataFunc := func(innerCtx context.Context) (io.Reader, int, error) {
dataFunc := func(innerCtx context.Context) (io.Reader, int64, error) {
f, err := avatar.Open()
return f, int(avatar.Size), err
return f, avatar.Size, err
}
isAvatar := true
@ -211,13 +211,13 @@ func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHead
// the account's new header image.
func (p *processor) UpdateHeader(ctx context.Context, header *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
maxImageSize := config.GetMediaImageMaxSize()
if int(header.Size) > maxImageSize {
if header.Size > int64(maxImageSize) {
return nil, fmt.Errorf("UpdateHeader: header with size %d exceeded max image size of %d bytes", header.Size, maxImageSize)
}
dataFunc := func(innerCtx context.Context) (io.Reader, int, error) {
dataFunc := func(innerCtx context.Context) (io.Reader, int64, error) {
f, err := header.Open()
return f, int(header.Size), err
return f, header.Size, err
}
isHeader := true

View file

@ -52,9 +52,9 @@ func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account,
emojiURI := uris.GenerateURIForEmoji(emojiID)
data := func(innerCtx context.Context) (io.Reader, int, error) {
data := func(innerCtx context.Context) (io.Reader, int64, error) {
f, err := form.Image.Open()
return f, int(form.Image.Size), err
return f, form.Image.Size, err
}
processingEmoji, err := p.mediaManager.ProcessEmoji(ctx, data, nil, form.Shortcode, emojiID, emojiURI, nil)

View file

@ -30,9 +30,9 @@ import (
)
func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, gtserror.WithCode) {
data := func(innerCtx context.Context) (io.Reader, int, error) {
data := func(innerCtx context.Context) (io.Reader, int64, error) {
f, err := form.File.Open()
return f, int(form.File.Size), err
return f, form.File.Size, err
}
focusX, focusY, err := parseFocus(form.Focus)

View file

@ -138,7 +138,7 @@ func (p *processor) getAttachmentContent(ctx context.Context, requestingAccount
// if it's the thumbnail that's requested then the user will have to wait a bit while we process the
// large version and derive a thumbnail from it, so use the normal recaching procedure: fetch the media,
// process it, then return the thumbnail data
data = func(innerCtx context.Context) (io.Reader, int, error) {
data = func(innerCtx context.Context) (io.Reader, int64, error) {
transport, err := p.transportController.NewTransportForUsername(innerCtx, requestingUsername)
if err != nil {
return nil, 0, err
@ -169,7 +169,7 @@ func (p *processor) getAttachmentContent(ctx context.Context, requestingAccount
// the caller will read from the buffered reader, so it doesn't matter if they drop out without reading everything
attachmentContent.Content = bufferedReader
data = func(innerCtx context.Context) (io.Reader, int, error) {
data = func(innerCtx context.Context) (io.Reader, int64, error) {
transport, err := p.transportController.NewTransportForUsername(innerCtx, requestingUsername)
if err != nil {
return nil, 0, err

View file

@ -26,7 +26,7 @@ import (
"net/url"
)
func (t *transport) DereferenceMedia(ctx context.Context, iri *url.URL) (io.ReadCloser, int, error) {
func (t *transport) DereferenceMedia(ctx context.Context, iri *url.URL) (io.ReadCloser, int64, error) {
// Build IRI just once
iriStr := iri.String()
@ -50,5 +50,5 @@ func (t *transport) DereferenceMedia(ctx context.Context, iri *url.URL) (io.Read
return nil, 0, fmt.Errorf("GET request to %s failed (%d): %s", iriStr, rsp.StatusCode, rsp.Status)
}
return rsp.Body, int(rsp.ContentLength), nil
return rsp.Body, rsp.ContentLength, nil
}

View file

@ -47,7 +47,7 @@ import (
type Transport interface {
pub.Transport
// DereferenceMedia fetches the given media attachment IRI, returning the reader and filesize.
DereferenceMedia(ctx context.Context, iri *url.URL) (io.ReadCloser, int, error)
DereferenceMedia(ctx context.Context, iri *url.URL) (io.ReadCloser, int64, error)
// DereferenceInstance dereferences remote instance information, first by checking /api/v1/instance, and then by checking /.well-known/nodeinfo.
DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error)
// Finger performs a webfinger request with the given username and domain, and returns the bytes from the response body.

View file

@ -688,9 +688,9 @@ func (c *converter) InstanceToAPIInstance(ctx context.Context, i *gtsmodel.Insta
},
MediaAttachments: &model.InstanceConfigurationMediaAttachments{
SupportedMimeTypes: media.AllSupportedMIMETypes(),
ImageSizeLimit: config.GetMediaImageMaxSize(),
ImageSizeLimit: int(config.GetMediaImageMaxSize()),
ImageMatrixLimit: instanceMediaAttachmentsImageMatrixLimit, // height*width
VideoSizeLimit: config.GetMediaVideoMaxSize(),
VideoSizeLimit: int(config.GetMediaVideoMaxSize()),
VideoFrameRateLimit: instanceMediaAttachmentsVideoFrameRateLimit,
VideoMatrixLimit: instanceMediaAttachmentsVideoMatrixLimit, // height*width
},

View file

@ -2,7 +2,7 @@
set -eu
EXPECTED='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","application-name":"gts","bind-address":"127.0.0.1","config-path":"./test/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-suspended":true,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","application-name":"gts","bind-address":"127.0.0.1","config-path":"./test/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-suspended":true,"letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
# Set all the environment variables to
# ensure that these are parsed without panic
@ -66,16 +66,24 @@ GTS_SMTP_HOST='example.com' \
GTS_SMTP_PORT=4269 \
GTS_SMTP_USERNAME='sex-haver' \
GTS_SMTP_PASSWORD='hunter2' \
GTS_SMTP_FROM='queen@terfisland.org' \
GTS_SMTP_FROM='queen.rip.in.piss@terfisland.org' \
GTS_SYSLOG_ENABLED=true \
GTS_SYSLOG_PROTOCOL='udp' \
GTS_SYSLOG_ADDRESS='127.0.0.1:6969' \
GTS_ADVANCED_COOKIES_SAMESITE='strict' \
go run ./cmd/gotosocial/... --config-path $(dirname ${0})/test.yaml debug config)
if [ "${OUTPUT}" != "${EXPECTED}" ]; then
OUTPUT_OUT=$(mktemp)
echo "$OUTPUT" > "$OUTPUT_OUT"
EXPECT_OUT=$(mktemp)
echo "$EXPECT" > "$EXPECT_OUT"
if ! DIFF=$(diff "$OUTPUT_OUT" "$EXPECT_OUT"); then
echo "OUTPUT not equal EXPECTED"
echo "$DIFF"
exit 1
else
echo "OK"
exit 0
fi

View file

@ -6,6 +6,24 @@ import (
"unsafe"
)
const (
// SI units
KB Size = 1e3
MB Size = 1e6
GB Size = 1e9
TB Size = 1e12
PB Size = 1e15
EB Size = 1e18
// IEC units
KiB Size = 1024
MiB Size = KiB * 1024
GiB Size = MiB * 1024
TiB Size = GiB * 1024
PiB Size = TiB * 1024
EiB Size = PiB * 1024
)
var (
// ErrInvalidUnit is returned when an invalid IEC/SI is provided.
ErrInvalidUnit = errors.New("bytesize: invalid unit")
@ -13,42 +31,39 @@ var (
// ErrInvalidFormat is returned when an invalid size value is provided.
ErrInvalidFormat = errors.New("bytesize: invalid format")
// bunits are the binary unit chars.
units = `kMGTPE`
// iecpows is a precomputed table of 1024^n.
iecpows = [...]float64{
float64(1024), // KiB
float64(1024 * 1024), // MiB
float64(1024 * 1024 * 1024), // GiB
float64(1024 * 1024 * 1024 * 1024), // TiB
float64(1024 * 1024 * 1024 * 1024 * 1024), // PiB
float64(1024 * 1024 * 1024 * 1024 * 1024 * 1024), // EiB
float64(KiB),
float64(MiB),
float64(GiB),
float64(TiB),
float64(PiB),
float64(EiB),
}
// sipows is a precomputed table of 1000^n.
sipows = [...]float64{
float64(1e3), // KB
float64(1e6), // MB
float64(1e9), // GB
float64(1e12), // TB
float64(1e15), // PB
float64(1e18), // EB
float64(KB),
float64(MB),
float64(GB),
float64(TB),
float64(PB),
float64(EB),
}
// bvals is a precomputed table of IEC unit values.
iecvals = [...]uint64{
'k': 1024,
'K': 1024,
'M': 1024 * 1024,
'G': 1024 * 1024 * 1024,
'T': 1024 * 1024 * 1024 * 1024,
'P': 1024 * 1024 * 1024 * 1024 * 1024,
'E': 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
iecvals = [...]Size{
'k': KiB,
'K': KiB,
'M': MiB,
'G': GiB,
'T': TiB,
'P': PiB,
'E': EiB,
}
// sivals is a precomputed table of SI unit values.
sivals = [...]uint64{
sivals = [...]Size{
// ASCII numbers _aren't_ valid SI unit values,
// BUT if the space containing a possible unit
// char is checked with this table -- it is valid
@ -64,12 +79,13 @@ var (
'8': 1,
'9': 1,
'k': 1e3,
'M': 1e6,
'G': 1e9,
'T': 1e12,
'P': 1e15,
'E': 1e18,
'k': KB,
'K': KB,
'M': MB,
'G': GB,
'T': TB,
'P': PB,
'E': EB,
}
)
@ -91,7 +107,28 @@ func ParseSize(s string) (Size, error) {
return 0, ErrInvalidFormat
}
return Size(uint64(f) * unit), nil
return Size(f) * unit, nil
}
// Set implements flag.Value{}.
func (sz *Size) Set(in string) error {
s, err := ParseSize(in)
if err != nil {
return err
}
*sz = s
return nil
}
// MarshalText implements encoding.TextMarshaler{}.
func (sz *Size) MarshalText() ([]byte, error) {
const maxLen = 7 // max IEC string length
return sz.AppendFormatIEC(make([]byte, 0, maxLen)), nil
}
// UnmarshalText implements encoding.TextUnmarshaler{}.
func (sz *Size) UnmarshalText(text []byte) error {
return sz.Set(*(*string)(unsafe.Pointer(&text)))
}
// AppendFormat defaults to using Size.AppendFormatIEC().
@ -121,7 +158,13 @@ func (sz Size) AppendFormatIEC(dst []byte) []byte {
// appendFormat will append formatted Size to 'dst', depending on base, powers table and single unit suffix.
func (sz Size) appendFormat(dst []byte, base uint64, pows *[6]float64, sunit string) []byte {
const min = 0.75
const (
// min "small" unit threshold
min = 0.75
// binary unit chars.
units = `kMGTPE`
)
// Larger number: get value of
// i / unit size. We have a 'min'
@ -143,13 +186,15 @@ func (sz Size) appendFormat(dst []byte, base uint64, pows *[6]float64, sunit str
// StringSI returns an SI unit string format of Size.
func (sz Size) StringSI() string {
b := sz.AppendFormatSI(make([]byte, 0, 6))
const maxLen = 6 // max SI string length
b := sz.AppendFormatSI(make([]byte, 0, maxLen))
return *(*string)(unsafe.Pointer(&b))
}
// StringIEC returns an IEC unit string format of Size.
func (sz Size) StringIEC() string {
b := sz.AppendFormatIEC(make([]byte, 0, 7))
const maxLen = 7 // max IEC string length
b := sz.AppendFormatIEC(make([]byte, 0, maxLen))
return *(*string)(unsafe.Pointer(&b))
}
@ -159,9 +204,7 @@ func (sz Size) String() string {
}
// parseUnit will parse the byte size unit from string 's'.
func parseUnit(s string) (uint64, int, error) {
var isIEC bool
func parseUnit(s string) (Size, int, error) {
// Check for string
if len(s) < 1 {
return 0, 0, ErrInvalidFormat
@ -171,8 +214,8 @@ func parseUnit(s string) (uint64, int, error) {
if l := len(s) - 1; s[l] == 'B' {
s = s[:l]
// Check str remains
if len(s) < 1 {
// No remaining str before unit suffix
return 0, 0, ErrInvalidFormat
}
}
@ -180,38 +223,41 @@ func parseUnit(s string) (uint64, int, error) {
// Strip IEC binary unit suffix
if l := len(s) - 1; s[l] == 'i' {
s = s[:l]
isIEC = true
// Check str remains
if len(s) < 1 {
// No remaining str before unit suffix
return 0, 0, ErrInvalidFormat
}
// Location of unit char.
l := len(s) - 1
c := int(s[l])
// Check valid unit char was provided
if len(iecvals) < c || iecvals[c] == 0 {
return 0, 0, ErrInvalidUnit
}
// Return parsed IEC unit size
return iecvals[c], l, nil
}
// Location of unit char.
l := len(s) - 1
c := int(s[l])
var unit uint64
switch c := int(s[l]); {
// Determine IEC unit in use
case isIEC && c < len(iecvals):
unit = iecvals[c]
if unit == 0 {
return 0, 0, ErrInvalidUnit
}
switch {
// Check valid unit char provided
case len(sivals) < c || sivals[c] == 0:
return 0, 0, ErrInvalidUnit
// Determine SI unit in use
case c < len(sivals):
unit = sivals[c]
switch unit {
case 0:
return 0, 0, ErrInvalidUnit
case 1:
l++
}
// No unit char (only ascii number)
case sivals[c] == 1:
l++
}
return unit, l, nil
// Return parsed SI unit size
return sivals[c], l, nil
}
// ftoa appends string formatted 'f' to 'dst', assumed < ~800.
@ -286,7 +332,7 @@ func ftoa(dst []byte, f float64) []byte {
// itoa appends string formatted 'i' to 'dst'.
func itoa(dst []byte, i uint64) []byte {
// Assemble int in reverse order.
// Assemble uint in reverse order.
var b [4]byte
bp := len(b) - 1
@ -302,5 +348,10 @@ func itoa(dst []byte, i uint64) []byte {
return append(dst, b[bp:]...)
}
// We use the following internal strconv function usually
// used internally to parse float values, as we know that
// are value passed will always be of 64bit type, and knowing
// the returned float string length is very helpful!
//
//go:linkname atof64 strconv.atof64
func atof64(string) (float64, int, error)

2
vendor/modules.txt vendored
View file

@ -7,7 +7,7 @@ codeberg.org/gruf/go-bitutil
# codeberg.org/gruf/go-bytes v1.0.2
## explicit; go 1.14
codeberg.org/gruf/go-bytes
# codeberg.org/gruf/go-bytesize v0.2.1
# codeberg.org/gruf/go-bytesize v1.0.0
## explicit; go 1.17
codeberg.org/gruf/go-bytesize
# codeberg.org/gruf/go-byteutil v1.0.2