feat: delete attachment files when removing an account#257
feat: delete attachment files when removing an account#257YourEconProf wants to merge 5 commits intowesm:mainfrom
Conversation
Attachments unique to the removed source are deleted from disk during `remove-account`. Attachments whose content_hash is shared with another source are preserved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
- Filter NULL/empty storage_path in AttachmentPathsUniqueToSource query to prevent scan panic and accidental deletion of the attachments root dir - Re-check each path is unreferenced immediately before os.Remove to guard against a concurrent sync adding a reference after the candidate list was collected (TOCTOU race) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
StoreAttachmentFile writes the file before UpsertAttachment inserts the DB row, leaving a window where IsAttachmentPathReferenced returns false for a file a concurrent sync has already written. To close this race, skip all file deletion when HasAnyActiveSync reports a running sync and print a message directing the user to re-run when idle. The per-path IsAttachmentPathReferenced re-check is kept as a last-ditch guard for the narrow window between the HasAnyActiveSync gate and the individual os.Remove calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
sync_runs.source_id is ON DELETE CASCADE, so calling HasAnyActiveSync() after RemoveSource() can miss a running sync on the account being removed — the sync_run row is already gone by the time we check. Move the HasAnyActiveSync check (and the skip decision) to before RemoveSource so sync_runs are still intact. The skip flag is then acted on after the cascade, keeping the correct invariant: per-path IsAttachmentPathReferenced catches any new reference a concurrent sync for a *different* source may have inserted in the window between the pre-check and the os.Remove calls. Also fix the warning message: since the account no longer exists after removal, directing the user to re-run 'remove-account' is not actionable. Instead point them at the attachments directory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…etion - Validate that each resolved attachment path stays within attachmentsDir before calling os.Remove. filepath.Join resolves '..' components, so a corrupt or tampered DB storage_path value could otherwise direct deletion at a file outside the attachments directory. - Add idx_attachments_storage_path index so IsAttachmentPathReferenced does not full-scan the attachments table on every per-file re-check. The existing content_hash index does not cover the storage_path = ? query. - Document in AttachmentPathsUniqueToSource that thumbnail_path is not included; no sync/import code currently writes thumbnail files, so there is nothing to clean up, but the gap is noted for future implementors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Attachments unique to the removed source are deleted from disk during
remove-account. Attachments whose content_hash is shared with another source are preserved.Removes need for 'msgvault gc (when available)' message