| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | // 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" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"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" | 
					
						
							| 
									
										
										
										
											2024-05-22 09:46:24 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/storage" | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  | 	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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | // haveFiles returns whether all of the provided files exist within current storage. | 
					
						
							|  |  |  | func (c *Cleaner) haveFiles(ctx context.Context, files ...string) (bool, error) { | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 	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 { | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 			// Missing file(s). | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 	return true, nil | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-02 17:21:46 +02:00
										 |  |  | 	var ( | 
					
						
							|  |  |  | 		errs     gtserror.MultiError | 
					
						
							|  |  |  | 		errCount int | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for _, path := range files { | 
					
						
							|  |  |  | 		// Remove each provided storage path. | 
					
						
							|  |  |  | 		log.Debugf(ctx, "removing file: %s", path) | 
					
						
							|  |  |  | 		err := c.state.Storage.Delete(ctx, path) | 
					
						
							| 
									
										
										
										
											2024-05-22 09:46:24 +00:00
										 |  |  | 		if err != nil && !storage.IsNotFound(err) { | 
					
						
							| 
									
										
										
										
											2023-08-02 17:21:46 +02:00
										 |  |  | 			errs.Appendf("error removing %s: %w", path, err) | 
					
						
							|  |  |  | 			errCount++ | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate no. files removed. | 
					
						
							| 
									
										
										
										
											2023-08-02 17:21:46 +02:00
										 |  |  | 	diff := len(files) - errCount | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Wrap the combined error slice. | 
					
						
							|  |  |  | 	if err := errs.Combine(); err != nil { | 
					
						
							|  |  |  | 		return diff, gtserror.Newf("error(s) removing files: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return diff, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 18:35:11 +01:00
										 |  |  | // ScheduleJobs schedules cleaning | 
					
						
							|  |  |  | // jobs using configured parameters. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Returns an error if `MediaCleanupFrom` | 
					
						
							|  |  |  | // is not a valid format (hh:mm:ss). | 
					
						
							|  |  |  | func (c *Cleaner) ScheduleJobs() error { | 
					
						
							|  |  |  | 	const hourMinute = "15:04" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		now            = time.Now() | 
					
						
							|  |  |  | 		cleanupEvery   = config.GetMediaCleanupEvery() | 
					
						
							|  |  |  | 		cleanupFromStr = config.GetMediaCleanupFrom() | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Parse cleanupFromStr as hh:mm. | 
					
						
							|  |  |  | 	// Resulting time will be on 1 Jan year zero. | 
					
						
							|  |  |  | 	cleanupFrom, err := time.Parse(hourMinute, cleanupFromStr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf( | 
					
						
							|  |  |  | 			"error parsing '%s' in time format 'hh:mm': %w", | 
					
						
							|  |  |  | 			cleanupFromStr, err, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 18:35:11 +01:00
										 |  |  | 	// Time travel from | 
					
						
							|  |  |  | 	// year zero, groovy. | 
					
						
							|  |  |  | 	firstCleanupAt := time.Date( | 
					
						
							|  |  |  | 		now.Year(), | 
					
						
							|  |  |  | 		now.Month(), | 
					
						
							|  |  |  | 		now.Day(), | 
					
						
							|  |  |  | 		cleanupFrom.Hour(), | 
					
						
							|  |  |  | 		cleanupFrom.Minute(), | 
					
						
							|  |  |  | 		0, | 
					
						
							|  |  |  | 		0, | 
					
						
							|  |  |  | 		now.Location(), | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 18:35:11 +01:00
										 |  |  | 	// Ensure first cleanup is in the future. | 
					
						
							|  |  |  | 	for firstCleanupAt.Before(now) { | 
					
						
							|  |  |  | 		firstCleanupAt = firstCleanupAt.Add(cleanupEvery) | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-04 20:21:20 +00:00
										 |  |  | 	fn := func(ctx context.Context, start time.Time) { | 
					
						
							|  |  |  | 		log.Info(ctx, "starting media clean") | 
					
						
							|  |  |  | 		c.Media().All(ctx, config.GetMediaRemoteCacheDays()) | 
					
						
							|  |  |  | 		c.Emoji().All(ctx, config.GetMediaRemoteCacheDays()) | 
					
						
							|  |  |  | 		log.Infof(ctx, "finished media clean after %s", time.Since(start)) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 18:35:11 +01:00
										 |  |  | 	log.Infof(nil, | 
					
						
							|  |  |  | 		"scheduling media clean to run every %s, starting from %s; next clean will run at %s", | 
					
						
							|  |  |  | 		cleanupEvery, cleanupFromStr, firstCleanupAt, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-04 20:21:20 +00:00
										 |  |  | 	// Schedule the cleaning to execute according to schedule. | 
					
						
							|  |  |  | 	if !c.state.Workers.Scheduler.AddRecurring( | 
					
						
							|  |  |  | 		"@mediacleanup", | 
					
						
							|  |  |  | 		firstCleanupAt, | 
					
						
							|  |  |  | 		cleanupEvery, | 
					
						
							|  |  |  | 		fn, | 
					
						
							|  |  |  | 	) { | 
					
						
							|  |  |  | 		panic("failed to schedule @mediacleanup") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-30 18:35:11 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2023-06-22 20:46:36 +01:00
										 |  |  | } |