Merge branch 'main' into unified-panels
This commit is contained in:
commit
bb42d2609a
|
@ -62,7 +62,8 @@ Example:
|
||||||
gotosocial admin account create \
|
gotosocial admin account create \
|
||||||
--username some_username \
|
--username some_username \
|
||||||
--email someuser@example.org \
|
--email someuser@example.org \
|
||||||
--password 'somelongandcomplicatedpassword'
|
--password 'somelongandcomplicatedpassword' \
|
||||||
|
--config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin account confirm
|
### gotosocial admin account confirm
|
||||||
|
@ -85,7 +86,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin account confirm --username some_username
|
gotosocial admin account confirm --username some_username --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin account promote
|
### gotosocial admin account promote
|
||||||
|
@ -108,7 +109,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin account promote --username some_username
|
gotosocial admin account promote --username some_username --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin account demote
|
### gotosocial admin account demote
|
||||||
|
@ -131,7 +132,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin account demote --username some_username
|
gotosocial admin account demote --username some_username --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin account disable
|
### gotosocial admin account disable
|
||||||
|
@ -154,7 +155,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin account disable --username some_username
|
gotosocial admin account disable --username some_username --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin account suspend
|
### gotosocial admin account suspend
|
||||||
|
@ -179,7 +180,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin account suspend --username some_username
|
gotosocial admin account suspend --username some_username --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin account password
|
### gotosocial admin account password
|
||||||
|
@ -203,7 +204,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin account password --username some_username --pasword some_really_good_password
|
gotosocial admin account password --username some_username --pasword some_really_good_password --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### gotosocial admin export
|
### gotosocial admin export
|
||||||
|
@ -228,7 +229,7 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin export --config-file ./config.yaml --path ./example.json
|
gotosocial admin export --path example.json --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
`example.json`:
|
`example.json`:
|
||||||
|
@ -276,5 +277,5 @@ Flags:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gotosocial admin import --config-file ./config.yaml --path ./example.json
|
gotosocial admin import --path example.json --config-path config.yaml
|
||||||
```
|
```
|
||||||
|
|
|
@ -85,20 +85,22 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.URL != nil {
|
|
||||||
c.Redirect(http.StatusFound, content.URL.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// if the content is a ReadCloser, close it when we're done
|
// if the content is a ReadCloser (ie., it's streamed from storage), close it when we're done
|
||||||
|
if content.Content != nil {
|
||||||
if closer, ok := content.Content.(io.ReadCloser); ok {
|
if closer, ok := content.Content.(io.ReadCloser); ok {
|
||||||
if err := closer.Close(); err != nil {
|
if err := closer.Close(); err != nil {
|
||||||
log.Errorf("ServeFile: error closing readcloser: %s", err)
|
log.Errorf("ServeFile: error closing readcloser: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if content.URL != nil {
|
||||||
|
c.Redirect(http.StatusFound, content.URL.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: if the requester only accepts text/html we should try to serve them *something*.
|
// TODO: if the requester only accepts text/html we should try to serve them *something*.
|
||||||
// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will
|
// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will
|
||||||
// attempt to look up the content to provide a preview of the link, and they ask for text/html.
|
// attempt to look up the content to provide a preview of the link, and they ask for text/html.
|
||||||
|
|
|
@ -121,6 +121,12 @@ func (p *ProcessingEmoji) loadStatic(ctx context.Context) error {
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := stored.Close(); err != nil {
|
||||||
|
log.Errorf("loadStatic: error closing stored full size: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// we haven't processed a static version of this emoji yet so do it now
|
// we haven't processed a static version of this emoji yet so do it now
|
||||||
static, err := deriveStaticEmoji(stored, p.emoji.ImageContentType)
|
static, err := deriveStaticEmoji(stored, p.emoji.ImageContentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -129,14 +135,8 @@ func (p *ProcessingEmoji) loadStatic(ctx context.Context) error {
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stored.Close(); err != nil {
|
|
||||||
p.err = fmt.Errorf("loadStatic: error closing stored full size: %s", err)
|
|
||||||
atomic.StoreInt32(&p.staticState, int32(errored))
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// put the static in storage
|
// put the static in storage
|
||||||
if err := p.storage.Put(ctx, p.emoji.ImageStaticPath, static.small); err != nil {
|
if err := p.storage.Put(ctx, p.emoji.ImageStaticPath, static.small); err != nil && err != storage.ErrAlreadyExists {
|
||||||
p.err = fmt.Errorf("loadStatic: error storing static: %s", err)
|
p.err = fmt.Errorf("loadStatic: error storing static: %s", err)
|
||||||
atomic.StoreInt32(&p.staticState, int32(errored))
|
atomic.StoreInt32(&p.staticState, int32(errored))
|
||||||
return p.err
|
return p.err
|
||||||
|
@ -217,7 +217,7 @@ func (p *ProcessingEmoji) store(ctx context.Context) error {
|
||||||
multiReader := io.MultiReader(bytes.NewBuffer(firstBytes), reader)
|
multiReader := io.MultiReader(bytes.NewBuffer(firstBytes), reader)
|
||||||
|
|
||||||
// store this for now -- other processes can pull it out of storage as they please
|
// store this for now -- other processes can pull it out of storage as they please
|
||||||
if err := p.storage.PutStream(ctx, p.emoji.ImagePath, multiReader); err != nil {
|
if err := p.storage.PutStream(ctx, p.emoji.ImagePath, multiReader); err != nil && err != storage.ErrAlreadyExists {
|
||||||
return fmt.Errorf("store: error storing stream: %s", err)
|
return fmt.Errorf("store: error storing stream: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,9 +145,7 @@ func (p *ProcessingMedia) loadThumb(ctx context.Context) error {
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// whatever happens, close the stream when we're done
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Tracef("loadThumb: closing stored stream %s", p.attachment.URL)
|
|
||||||
if err := stored.Close(); err != nil {
|
if err := stored.Close(); err != nil {
|
||||||
log.Errorf("loadThumb: error closing stored full size: %s", err)
|
log.Errorf("loadThumb: error closing stored full size: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -164,7 +162,7 @@ func (p *ProcessingMedia) loadThumb(ctx context.Context) error {
|
||||||
|
|
||||||
// put the thumbnail in storage
|
// put the thumbnail in storage
|
||||||
log.Tracef("loadThumb: storing new thumbnail %s", p.attachment.URL)
|
log.Tracef("loadThumb: storing new thumbnail %s", p.attachment.URL)
|
||||||
if err := p.storage.Put(ctx, p.attachment.Thumbnail.Path, thumb.small); err != nil {
|
if err := p.storage.Put(ctx, p.attachment.Thumbnail.Path, thumb.small); err != nil && err != storage.ErrAlreadyExists {
|
||||||
p.err = fmt.Errorf("loadThumb: error storing thumbnail: %s", err)
|
p.err = fmt.Errorf("loadThumb: error storing thumbnail: %s", err)
|
||||||
atomic.StoreInt32(&p.thumbState, int32(errored))
|
atomic.StoreInt32(&p.thumbState, int32(errored))
|
||||||
return p.err
|
return p.err
|
||||||
|
@ -210,6 +208,12 @@ func (p *ProcessingMedia) loadFullSize(ctx context.Context) error {
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := stored.Close(); err != nil {
|
||||||
|
log.Errorf("loadFullSize: error closing stored full size: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// decode the image
|
// decode the image
|
||||||
ct := p.attachment.File.ContentType
|
ct := p.attachment.File.ContentType
|
||||||
switch ct {
|
switch ct {
|
||||||
|
@ -227,12 +231,6 @@ func (p *ProcessingMedia) loadFullSize(ctx context.Context) error {
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stored.Close(); err != nil {
|
|
||||||
p.err = fmt.Errorf("loadFullSize: error closing stored full size: %s", err)
|
|
||||||
atomic.StoreInt32(&p.fullSizeState, int32(errored))
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set appropriate fields on the attachment based on the image we derived
|
// set appropriate fields on the attachment based on the image we derived
|
||||||
p.attachment.FileMeta.Original = gtsmodel.Original{
|
p.attachment.FileMeta.Original = gtsmodel.Original{
|
||||||
Width: decoded.width,
|
Width: decoded.width,
|
||||||
|
@ -343,7 +341,7 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
|
||||||
p.attachment.File.FileSize = fileSize
|
p.attachment.File.FileSize = fileSize
|
||||||
|
|
||||||
// store this for now -- other processes can pull it out of storage as they please
|
// store this for now -- other processes can pull it out of storage as they please
|
||||||
if err := p.storage.PutStream(ctx, p.attachment.File.Path, clean); err != nil {
|
if err := p.storage.PutStream(ctx, p.attachment.File.Path, clean); err != nil && err != storage.ErrAlreadyExists {
|
||||||
return fmt.Errorf("store: error storing stream: %s", err)
|
return fmt.Errorf("store: error storing stream: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ package router
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ const (
|
||||||
writeTimeout = 30 * time.Second
|
writeTimeout = 30 * time.Second
|
||||||
idleTimeout = 30 * time.Second
|
idleTimeout = 30 * time.Second
|
||||||
readHeaderTimeout = 30 * time.Second
|
readHeaderTimeout = 30 * time.Second
|
||||||
|
shutdownTimeout = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// Router provides the REST interface for gotosocial, using gin.
|
// Router provides the REST interface for gotosocial, using gin.
|
||||||
|
@ -128,7 +130,16 @@ func (r *router) Start() {
|
||||||
|
|
||||||
// Stop shuts down the router nicely
|
// Stop shuts down the router nicely
|
||||||
func (r *router) Stop(ctx context.Context) error {
|
func (r *router) Stop(ctx context.Context) error {
|
||||||
return r.srv.Shutdown(ctx)
|
log.Infof("shutting down http router with %s grace period", shutdownTimeout)
|
||||||
|
timeout, cancel := context.WithTimeout(ctx, shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := r.srv.Shutdown(timeout); err != nil {
|
||||||
|
return fmt.Errorf("error shutting down http router: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("http router closed connections and shut down gracefully")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Router with the specified configuration.
|
// New returns a new Router with the specified configuration.
|
||||||
|
@ -174,17 +185,24 @@ func New(ctx context.Context, db db.DB) (Router, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the http server here, passing the gin engine as handler
|
// use the passed-in command context as the base context for the server,
|
||||||
|
// since we'll never want the server to live past the command anyway
|
||||||
|
baseCtx := func(_ net.Listener) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
bindAddress := config.GetBindAddress()
|
bindAddress := config.GetBindAddress()
|
||||||
port := config.GetPort()
|
port := config.GetPort()
|
||||||
listen := fmt.Sprintf("%s:%d", bindAddress, port)
|
addr := fmt.Sprintf("%s:%d", bindAddress, port)
|
||||||
|
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: listen,
|
Addr: addr,
|
||||||
Handler: engine,
|
Handler: engine, // use gin engine as handler
|
||||||
ReadTimeout: readTimeout,
|
ReadTimeout: readTimeout,
|
||||||
|
ReadHeaderTimeout: readHeaderTimeout,
|
||||||
WriteTimeout: writeTimeout,
|
WriteTimeout: writeTimeout,
|
||||||
IdleTimeout: idleTimeout,
|
IdleTimeout: idleTimeout,
|
||||||
ReadHeaderTimeout: readHeaderTimeout,
|
BaseContext: baseCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to spawn the underlying server slightly differently depending on whether lets encrypt is enabled or not.
|
// We need to spawn the underlying server slightly differently depending on whether lets encrypt is enabled or not.
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"codeberg.org/gruf/go-store/kv"
|
"codeberg.org/gruf/go-store/kv"
|
||||||
|
"codeberg.org/gruf/go-store/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Local struct {
|
type Local struct {
|
||||||
|
@ -39,11 +40,19 @@ func (l *Local) GetStream(ctx context.Context, key string) (io.ReadCloser, error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Local) PutStream(ctx context.Context, key string, r io.Reader) error {
|
func (l *Local) PutStream(ctx context.Context, key string, r io.Reader) error {
|
||||||
return l.KVStore.PutStream(key, r)
|
err := l.KVStore.PutStream(key, r)
|
||||||
|
if err == storage.ErrAlreadyExists {
|
||||||
|
return ErrAlreadyExists
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Local) Put(ctx context.Context, key string, value []byte) error {
|
func (l *Local) Put(ctx context.Context, key string, value []byte) error {
|
||||||
return l.KVStore.Put(key, value)
|
err := l.KVStore.Put(key, value)
|
||||||
|
if err == storage.ErrAlreadyExists {
|
||||||
|
return ErrAlreadyExists
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Local) Delete(ctx context.Context, key string) error {
|
func (l *Local) Delete(ctx context.Context, key string) error {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNotSupported = errors.New("driver does not suppport functionality")
|
var ErrNotSupported = errors.New("driver does not suppport functionality")
|
||||||
|
var ErrAlreadyExists = errors.New("storage key already exists")
|
||||||
|
|
||||||
// Driver implements the functionality to store and retrieve blobs
|
// Driver implements the functionality to store and retrieve blobs
|
||||||
// (images,video,audio)
|
// (images,video,audio)
|
||||||
|
|
Loading…
Reference in New Issue