191 lines
6.2 KiB
Go
191 lines
6.2 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/file"
|
|
)
|
|
|
|
// =============================================================================
|
|
// FILE CLEANUP OPERATIONS
|
|
// =============================================================================
|
|
|
|
// DeleteFile soft-deletes a file from both the cloud and local storage
|
|
func (a *Application) DeleteFile(fileID string) error {
|
|
a.logger.Info("DeleteFile called", zap.String("file_id", fileID))
|
|
|
|
// Use the SDK client which has automatic token refresh on 401
|
|
apiClient := a.authService.GetAPIClient()
|
|
|
|
// Use SDK's DeleteFile method which has automatic 401 retry
|
|
if err := apiClient.DeleteFile(a.ctx, fileID); err != nil {
|
|
a.logger.Error("Failed to delete file from cloud",
|
|
zap.String("file_id", fileID),
|
|
zap.Error(err))
|
|
return fmt.Errorf("failed to delete file: %w", err)
|
|
}
|
|
|
|
// Cloud delete succeeded - now clean up local data
|
|
a.cleanupLocalFile(fileID)
|
|
|
|
a.logger.Info("File deleted successfully", zap.String("file_id", fileID))
|
|
return nil
|
|
}
|
|
|
|
// cleanupLocalFile removes physical binary files immediately and marks the metadata as deleted.
|
|
// The metadata record is kept for background cleanup later.
|
|
func (a *Application) cleanupLocalFile(fileID string) {
|
|
// Get the local file record
|
|
localFile, err := a.mustGetFileRepo().Get(fileID)
|
|
if err != nil || localFile == nil {
|
|
a.logger.Debug("No local file record to clean up", zap.String("file_id", fileID))
|
|
return
|
|
}
|
|
|
|
// IMMEDIATELY delete physical binary files
|
|
|
|
// Delete the physical decrypted file if it exists
|
|
if localFile.FilePath != "" {
|
|
if err := os.Remove(localFile.FilePath); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
a.logger.Warn("Failed to delete local decrypted file",
|
|
zap.String("file_id", fileID),
|
|
zap.String("path", localFile.FilePath),
|
|
zap.Error(err))
|
|
}
|
|
} else {
|
|
a.logger.Info("Deleted local decrypted file",
|
|
zap.String("file_id", fileID),
|
|
zap.String("path", localFile.FilePath))
|
|
}
|
|
}
|
|
|
|
// Delete the physical encrypted file if it exists
|
|
if localFile.EncryptedFilePath != "" {
|
|
if err := os.Remove(localFile.EncryptedFilePath); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
a.logger.Warn("Failed to delete local encrypted file",
|
|
zap.String("file_id", fileID),
|
|
zap.String("path", localFile.EncryptedFilePath),
|
|
zap.Error(err))
|
|
}
|
|
} else {
|
|
a.logger.Info("Deleted local encrypted file",
|
|
zap.String("file_id", fileID),
|
|
zap.String("path", localFile.EncryptedFilePath))
|
|
}
|
|
}
|
|
|
|
// Delete the thumbnail if it exists
|
|
if localFile.ThumbnailPath != "" {
|
|
if err := os.Remove(localFile.ThumbnailPath); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
a.logger.Warn("Failed to delete local thumbnail",
|
|
zap.String("file_id", fileID),
|
|
zap.String("path", localFile.ThumbnailPath),
|
|
zap.Error(err))
|
|
}
|
|
} else {
|
|
a.logger.Info("Deleted local thumbnail",
|
|
zap.String("file_id", fileID),
|
|
zap.String("path", localFile.ThumbnailPath))
|
|
}
|
|
}
|
|
|
|
// Mark the metadata record as deleted (will be cleaned up later by background process)
|
|
// Clear the file paths since the physical files are now deleted
|
|
localFile.State = file.StateDeleted
|
|
localFile.FilePath = ""
|
|
localFile.EncryptedFilePath = ""
|
|
localFile.ThumbnailPath = ""
|
|
localFile.ModifiedAt = time.Now()
|
|
|
|
if err := a.mustGetFileRepo().Update(localFile); err != nil {
|
|
a.logger.Warn("Failed to mark local file metadata as deleted",
|
|
zap.String("file_id", fileID),
|
|
zap.Error(err))
|
|
} else {
|
|
a.logger.Info("Marked local file metadata as deleted (will be cleaned up later)",
|
|
zap.String("file_id", fileID))
|
|
|
|
// Remove from search index
|
|
if err := a.searchService.DeleteFile(fileID); err != nil {
|
|
a.logger.Warn("Failed to remove file from search index",
|
|
zap.String("file_id", fileID),
|
|
zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
// purgeDeletedFileMetadata permanently removes a deleted file's metadata record.
|
|
// This is called by the background cleanup process after a retention period.
|
|
func (a *Application) purgeDeletedFileMetadata(fileID string) {
|
|
if err := a.mustGetFileRepo().Delete(fileID); err != nil {
|
|
a.logger.Warn("Failed to purge deleted file metadata",
|
|
zap.String("file_id", fileID),
|
|
zap.Error(err))
|
|
} else {
|
|
a.logger.Info("Purged deleted file metadata",
|
|
zap.String("file_id", fileID))
|
|
}
|
|
}
|
|
|
|
// deletedFileRetentionPeriod is how long to keep deleted file metadata before purging.
|
|
// This allows for potential recovery or sync conflict resolution.
|
|
const deletedFileRetentionPeriod = 7 * 24 * time.Hour // 7 days
|
|
|
|
// cleanupDeletedFiles runs in the background to clean up deleted files.
|
|
// It handles two cases:
|
|
// 1. Files marked as deleted that still have physical files (cleans up binaries immediately)
|
|
// 2. Files marked as deleted past the retention period (purges metadata)
|
|
func (a *Application) cleanupDeletedFiles() {
|
|
a.logger.Info("Starting background cleanup of deleted files")
|
|
|
|
// Get all local files
|
|
localFiles, err := a.mustGetFileRepo().List()
|
|
if err != nil {
|
|
a.logger.Error("Failed to list local files for cleanup", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
binaryCleanedCount := 0
|
|
metadataPurgedCount := 0
|
|
now := time.Now()
|
|
|
|
for _, localFile := range localFiles {
|
|
// Only process deleted files
|
|
if localFile.State != file.StateDeleted {
|
|
continue
|
|
}
|
|
|
|
// Check if there are still physical files to clean up
|
|
if localFile.FilePath != "" || localFile.EncryptedFilePath != "" || localFile.ThumbnailPath != "" {
|
|
a.logger.Info("Cleaning up orphaned binary files for deleted record",
|
|
zap.String("file_id", localFile.ID))
|
|
a.cleanupLocalFile(localFile.ID)
|
|
binaryCleanedCount++
|
|
continue
|
|
}
|
|
|
|
// Check if metadata is past retention period and can be purged
|
|
if now.Sub(localFile.ModifiedAt) > deletedFileRetentionPeriod {
|
|
a.logger.Info("Purging deleted file metadata (past retention period)",
|
|
zap.String("file_id", localFile.ID),
|
|
zap.Time("deleted_at", localFile.ModifiedAt))
|
|
a.purgeDeletedFileMetadata(localFile.ID)
|
|
metadataPurgedCount++
|
|
}
|
|
}
|
|
|
|
if binaryCleanedCount > 0 || metadataPurgedCount > 0 {
|
|
a.logger.Info("Background cleanup completed",
|
|
zap.Int("binaries_cleaned", binaryCleanedCount),
|
|
zap.Int("metadata_purged", metadataPurgedCount))
|
|
} else {
|
|
a.logger.Debug("Background cleanup completed, no cleanup needed")
|
|
}
|
|
}
|