summaryrefslogtreecommitdiff
path: root/internal/cleaner/cleaner.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-06-22 20:46:36 +0100
committerLibravatar GitHub <noreply@github.com>2023-06-22 20:46:36 +0100
commit9a22102fa8b1ce47571d5bba71e8f36895d21bf0 (patch)
tree3c2af6db0a3905d31243cd840d1dd50bea59dbb0 /internal/cleaner/cleaner.go
parent[docs] Clarify email requirement for OIDC (#1918) (diff)
downloadgotosocial-9a22102fa8b1ce47571d5bba71e8f36895d21bf0.tar.xz
[bugfix/chore] oauth entropy fix + media cleanup tasks rewrite (#1853)
Diffstat (limited to 'internal/cleaner/cleaner.go')
-rw-r--r--internal/cleaner/cleaner.go135
1 files changed, 135 insertions, 0 deletions
diff --git a/internal/cleaner/cleaner.go b/internal/cleaner/cleaner.go
new file mode 100644
index 000000000..ee1e4785f
--- /dev/null
+++ b/internal/cleaner/cleaner.go
@@ -0,0 +1,135 @@
+// 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"
+ "errors"
+ "time"
+
+ "codeberg.org/gruf/go-runners"
+ "codeberg.org/gruf/go-sched"
+ "codeberg.org/gruf/go-store/v2/storage"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
+)
+
+const (
+ selectLimit = 50
+)
+
+type Cleaner struct {
+ state *state.State
+ emoji Emoji
+ media Media
+}
+
+func New(state *state.State) *Cleaner {
+ c := new(Cleaner)
+ c.state = state
+ c.emoji.Cleaner = c
+ c.media.Cleaner = c
+ scheduleJobs(c)
+ return c
+}
+
+// Emoji returns the emoji set of cleaner utilities.
+func (c *Cleaner) Emoji() *Emoji {
+ return &c.emoji
+}
+
+// Media returns the media set of cleaner utilities.
+func (c *Cleaner) Media() *Media {
+ return &c.media
+}
+
+// checkFiles checks for each of the provided files, and calls onMissing() if any of them are missing. Returns true if missing.
+func (c *Cleaner) checkFiles(ctx context.Context, onMissing func() error, files ...string) (bool, error) {
+ for _, file := range files {
+ // Check whether each file exists in storage.
+ have, err := c.state.Storage.Has(ctx, file)
+ if err != nil {
+ return false, gtserror.Newf("error checking storage for %s: %w", file, err)
+ } else if !have {
+ // Missing files, perform hook.
+ return true, onMissing()
+ }
+ }
+ return false, nil
+}
+
+// removeFiles removes the provided files, returning the number of them returned.
+func (c *Cleaner) removeFiles(ctx context.Context, files ...string) (int, error) {
+ if gtscontext.DryRun(ctx) {
+ // Dry run, do nothing.
+ return len(files), nil
+ }
+
+ var errs gtserror.MultiError
+
+ for _, path := range files {
+ // Remove each provided storage path.
+ log.Debugf(ctx, "removing file: %s", path)
+ err := c.state.Storage.Delete(ctx, path)
+ if err != nil && !errors.Is(err, storage.ErrNotFound) {
+ errs.Appendf("error removing %s: %v", path, err)
+ }
+ }
+
+ // Calculate no. files removed.
+ diff := len(files) - len(errs)
+
+ // Wrap the combined error slice.
+ if err := errs.Combine(); err != nil {
+ return diff, gtserror.Newf("error(s) removing files: %w", err)
+ }
+
+ return diff, nil
+}
+
+func scheduleJobs(c *Cleaner) {
+ const day = time.Hour * 24
+
+ // Calculate closest midnight.
+ now := time.Now()
+ midnight := now.Round(day)
+
+ if midnight.Before(now) {
+ // since <= 11:59am rounds down.
+ midnight = midnight.Add(day)
+ }
+
+ // Get ctx associated with scheduler run state.
+ done := c.state.Workers.Scheduler.Done()
+ doneCtx := runners.CancelCtx(done)
+
+ // TODO: we'll need to do some thinking to make these
+ // jobs restartable if we want to implement reloads in
+ // the future that make call to Workers.Stop() -> Workers.Start().
+
+ // Schedule the cleaning tasks to execute every day at midnight.
+ c.state.Workers.Scheduler.Schedule(sched.NewJob(func(start time.Time) {
+ log.Info(nil, "starting media clean")
+ c.Media().All(doneCtx, config.GetMediaRemoteCacheDays())
+ c.Emoji().All(doneCtx)
+ log.Infof(nil, "finished media clean after %s", time.Since(start))
+ }).EveryAt(midnight, day))
+}