diff --git a/pkg/internal/services/attachments.go b/pkg/internal/services/attachments.go index 66e725a..79c3d7f 100644 --- a/pkg/internal/services/attachments.go +++ b/pkg/internal/services/attachments.go @@ -239,6 +239,85 @@ func DeleteAttachment(item models.Attachment, txs ...*gorm.DB) error { return nil } +func DeleteAttachmentInBatch(items []models.Attachment, txs ...*gorm.DB) error { + if len(items) == 0 { + return nil + } + + var tx *gorm.DB + if len(txs) == 0 { + tx = database.C.Begin() + } else { + tx = txs[0] + } + + refIDs := []uint{} + for _, item := range items { + if item.RefID != nil { + refIDs = append(refIDs, *item.RefID) + } + } + + if len(refIDs) > 0 { + var refTargets []models.Attachment + if err := tx.Where("id IN ?", refIDs).Find(&refTargets).Error; err == nil { + for i := range refTargets { + refTargets[i].RefCount-- + } + if err := tx.Save(&refTargets).Error; err != nil { + tx.Rollback() + return fmt.Errorf("unable to update ref count: %v", err) + } + } + } + + var subAttachments []models.Attachment + for _, item := range items { + if item.Thumbnail != nil { + subAttachments = append(subAttachments, *item.Thumbnail) + } + if item.Compressed != nil { + subAttachments = append(subAttachments, *item.Compressed) + } + } + + if len(subAttachments) > 0 { + if err := DeleteAttachmentInBatch(subAttachments, tx); err != nil { + tx.Rollback() + return err + } + } + + rids := make([]string, len(items)) + for i, item := range items { + rids[i] = item.Rid + } + + if err := tx.Where("rid IN ?", rids).Delete(&models.Attachment{}).Error; err != nil { + tx.Rollback() + return err + } + + cacheManager := cache.New[any](localCache.S) + marshal := marshaler.New(cacheManager) + contx := context.Background() + for _, rid := range rids { + _ = marshal.Delete(contx, GetAttachmentCacheKey(rid)) + } + + tx.Commit() + + go func() { + for _, item := range items { + if item.RefCount == 0 { + fs.DeleteFile(item) + } + } + }() + + return nil +} + func CountAttachmentUsage(id []uint, delta int) (int64, error) { if tx := database.C.Model(&models.Attachment{}). Where("id IN ?", id). diff --git a/pkg/internal/services/cleaner.go b/pkg/internal/services/cleaner.go index 839b2cf..85b53fc 100644 --- a/pkg/internal/services/cleaner.go +++ b/pkg/internal/services/cleaner.go @@ -1,24 +1,28 @@ package services import ( - database2 "git.solsynth.dev/hypernet/paperclip/pkg/internal/database" "time" + "git.solsynth.dev/hypernet/paperclip/pkg/internal/database" + "git.solsynth.dev/hypernet/paperclip/pkg/internal/models" + "github.com/rs/zerolog/log" ) -func DoAutoDatabaseCleanup() { - deadline := time.Now().Add(60 * time.Minute) - log.Debug().Time("deadline", deadline).Msg("Now cleaning up entire database...") +func DoUnusedAttachmentCleanup() { + deadline := time.Now().Add(-60 * time.Minute) - var count int64 - for _, model := range database2.AutoMaintainRange { - tx := database2.C.Unscoped().Delete(model, "deleted_at >= ?", deadline) - if tx.Error != nil { - log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...") - } - count += tx.RowsAffected + var result []models.Attachment + if err := database.C.Where("created_at < ? AND used_count = 0", deadline). + Find(&result).Error; err != nil { + log.Error().Err(err).Msg("An error occurred when getting unused attachments...") + return } - log.Debug().Int64("affected", count).Msg("Clean up entire database accomplished.") + if err := DeleteAttachmentInBatch(result); err != nil { + log.Error().Err(err).Msg("An error occurred when deleting unused attachments...") + return + } + + log.Info().Int("count", len(result)).Msg("Deleted unused attachments...") } diff --git a/pkg/main.go b/pkg/main.go index 44aaefd..2256e15 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -80,7 +80,7 @@ func main() { // Configure timed tasks quartz := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(&log.Logger))) - quartz.AddFunc("@every 60m", services.DoAutoDatabaseCleanup) + quartz.AddFunc("@every 60m", services.DoUnusedAttachmentCleanup) quartz.AddFunc("@every 60m", fs.RunMarkLifecycleDeletionTask) quartz.AddFunc("@every 60m", fs.RunMarkMultipartDeletionTask) quartz.AddFunc("@midnight", fs.RunScheduleDeletionTask)