Files
tobi b833c9666e [bugfix] set statuses-cleanup-remote-older-than to "0s", skip scheduling if 0 (#4869)
# Description

> If this is a code change, please include a summary of what you've coded, and link to the issue(s) it closes/implements.
>
> If this is a documentation change, please briefly describe what you've changed and why.

Fixes https://codeberg.org/superseriousbusiness/gotosocial/issues/4868 by changing default to 0s instead of 0.

Also skips scheduling statuses cleanup if duration is not > 0 as there's no point.

## Checklist

Please put an x inside each checkbox to indicate that you've read and followed it: `[ ]` -> `[x]`

If this is a documentation change, only the first two checkboxes must be filled (you can delete the others if you want).

- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [x] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4869
2026-06-17 11:32:06 +02:00

167 lines
4.8 KiB
Go

// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cleaner
import (
"context"
"time"
"unsafe"
"code.superseriousbusiness.org/gopkg/log"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/state"
"code.superseriousbusiness.org/gotosocial/internal/storage"
"codeberg.org/gruf/go-longdur"
)
const selectLimit = 50
const stamp = "Jan _2 2006 15:04:05"
type Cleaner struct{ state *state.State }
func New(state *state.State) *Cleaner {
c := new(Cleaner)
c.state = state
return c
}
// Emoji returns the emoji set of cleaner utilities.
func (c *Cleaner) Emoji() *Emoji {
if unsafe.Sizeof(Emoji{}) != unsafe.Sizeof(Cleaner{}) ||
unsafe.Offsetof(Emoji{}.Cleaner) != 0 {
panic(gtserror.New("compile time unsafe pointer assertion"))
}
return (*Emoji)(unsafe.Pointer(c))
}
// Media returns the media set of cleaner utilities.
func (c *Cleaner) Media() *Media {
if unsafe.Sizeof(Media{}) != unsafe.Sizeof(Cleaner{}) ||
unsafe.Offsetof(Media{}.Cleaner) != 0 {
panic(gtserror.New("compile time unsafe pointer assertion"))
}
return (*Media)(unsafe.Pointer(c))
}
// Status returns the status set of cleaner utilities.
func (c *Cleaner) Status() *Status {
if unsafe.Sizeof(Status{}) != unsafe.Sizeof(Cleaner{}) ||
unsafe.Offsetof(Status{}.Cleaner) != 0 {
panic(gtserror.New("compile time unsafe pointer assertion"))
}
return (*Status)(unsafe.Pointer(c))
}
// haveFiles returns whether all of the provided files exist within current storage.
func (c *Cleaner) haveFiles(ctx context.Context, files ...string) (bool, error) {
for _, path := range files {
if path == "" {
// File not stored.
return false, nil
}
// Check whether each file exists in storage.
have, err := c.state.Storage.Has(ctx, path)
if err != nil {
return false, gtserror.Newf("error checking storage for %s: %w", path, err)
}
if !have {
// Missing file(s).
return false, nil
}
}
return true, nil
}
// removeFiles removes the provided files, returning the number of them returned.
func (c *Cleaner) removeFiles(ctx context.Context, files ...string) {
if gtscontext.DryRun(ctx) {
// Dry run,
// do nothing.
return
}
for _, path := range files {
if path == "" {
// not stored.
continue
}
// Remove each provided storage path.
log.Debugf(ctx, "removing file: %s", path)
err := c.state.Storage.Delete(ctx, path)
if err != nil && !storage.IsNotFound(err) {
log.Errorf(ctx, "error removing %s: %v", path, err)
continue
}
}
}
// ScheduleJobs schedules cleaning
// jobs using configured parameters.
func (c *Cleaner) ScheduleJobs() error {
var expr config.CronExpression
expr = config.GetMediaCleanupCron()
log.Infof(nil, "scheduling media cleanup: %s", expr.Expr)
// Schedule media cleaning by expr.
if !c.state.Workers.Scheduler.Add(
"@mediacleanup",
c.cleanMedia,
expr,
) {
panic("failed to schedule @mediacleanup")
}
if _, dur := config.GetStatusesCleanupRemoteOlderThan().Duration(); dur > 0 {
expr = config.GetStatusesCleanupCron()
log.Infof(nil, "scheduling statuses cleanup: %s", expr.Expr)
// Schedule status cleaning by expr.
if !c.state.Workers.Scheduler.Add(
"@statuscleanup",
c.cleanStatuses,
expr,
) {
panic("failed to schedule @statuscleanup")
}
} else {
log.Infof(nil, "skipping statuses cleanup scheduling, as statuses-cleanup-remote-older-than <= 0")
}
return nil
}
func (c *Cleaner) cleanMedia(ctx context.Context, start time.Time) {
log.Info(ctx, "starting")
c.Media().All(ctx, start, config.GetMediaRemoteCacheDuration())
c.Emoji().All(ctx, start, config.GetMediaRemoteCacheDuration())
log.Infof(ctx, "finished after %s", time.Since(start))
}
func (c *Cleaner) cleanStatuses(ctx context.Context, start time.Time) {
log.Info(ctx, "starting")
maxRemoteAge := config.GetStatusesCleanupRemoteOlderThan()
c.Status().All(ctx, start, 7*longdur.Day, maxRemoteAge)
log.Infof(ctx, "finished after %s", time.Since(start))
}