diff options
author | Ben Sima <ben@bensima.com> | 2025-03-27 21:28:58 -0400 |
---|---|---|
committer | Ben Sima <ben@bensima.com> | 2025-03-27 21:28:58 -0400 |
commit | f80bbb901d240bca887ec82a96e4ec861c516506 (patch) | |
tree | 83f245db476212ac6fa578779f3976475044039a | |
parent | 2db5cb4720021ffe56c6164f6c113fd3a3d7ea97 (diff) |
add fix for duplicate mail uids
-rwxr-xr-x | fix-duplicate-mail-uids.sh | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/fix-duplicate-mail-uids.sh b/fix-duplicate-mail-uids.sh new file mode 100755 index 0000000..5543fe9 --- /dev/null +++ b/fix-duplicate-mail-uids.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# +# fix-duplicate-uids.sh - Automatically fix duplicate UIDs in maildir +# +# this is needed because sometimes iOS mail and mbsync download or move the same +# file, and i end up with UID collisions. if i just delete the UID from the +# filename, then mu will generate a new one that doesn't collide. + +# Configuration - edit these paths as needed +MAIL_DIR="$HOME/Mail" + +# Color output helpers +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Create secure temporary files +TEMP_FILE=$(mktemp) +trap 'rm -f "$TEMP_FILE"' EXIT + +# Process each mailbox separately +find "$MAIL_DIR" -type d -name cur | while read -r mailbox_cur; do + # Get the actual mailbox path (parent of cur) + mailbox=$(dirname "$mailbox_cur") + mailbox_name=$(basename "$mailbox") + + echo -e "${YELLOW}Checking mailbox: ${mailbox}${NC}" + + # Find duplicate UIDs in this specific mailbox + find "$mailbox_cur" -type f -name "*,U=*:*" \ + | sed -E 's/.*,U=([0-9]+).*/\1/' \ + | sort \ + | uniq -c \ + | awk '$1 > 1 {print $2}' \ + > "$TEMP_FILE" + + if [ ! -s "$TEMP_FILE" ]; then + echo -e " ${GREEN}No duplicate UIDs found.${NC}" + continue + fi + + echo -e " ${YELLOW}Found $(wc -l < "$TEMP_FILE") duplicate UID(s).${NC}" + + # Process each duplicate UID + while read -r uid; do + echo -e " ${YELLOW}Fixing duplicate UID: ${uid}${NC}" + + # Find all files with this UID in the current mailbox + files=$(find "$mailbox_cur" -type f -name "*,U=${uid}:*" | sort) + + # Find the oldest file with this UID (by creation time) + oldest_file="" + oldest_time=9999999999 + + while read -r file; do + # Extract the timestamp from the filename (typical maildir format) + timestamp=$(basename "$file" | cut -d. -f1) + if [[ $timestamp =~ ^[0-9]+$ ]]; then + if [ "$timestamp" -lt "$oldest_time" ]; then + oldest_time=$timestamp + oldest_file=$file + fi + else + # If we can't extract timestamp from filename, use the file's mtime + file_time=$(stat -c %Y "$file") + if [ "$file_time" -lt "$oldest_time" ]; then + oldest_time=$file_time + oldest_file=$file + fi + fi + done <<< "$files" + + echo " Keeping original: $(basename "$oldest_file")" + + # Rename all other files with this UID (removing the UID part) + while read -r file; do + if [ "$file" != "$oldest_file" ]; then + new_file=$(echo "$file" | sed -E 's/(.*),U=[0-9]+:(.*)/\1\2/') + echo " Renaming: $(basename "$file") → $(basename "$new_file")" + mv "$file" "$new_file" + fi + done <<< "$files" + done < "$TEMP_FILE" +done + +echo -e "${GREEN}Fix complete! Run 'mbsync -a' to resync with new UIDs.${NC}" |