summaryrefslogtreecommitdiff
path: root/fix-duplicate-mail-uids.sh
blob: 5543fe94dc7d09c5082d5efcb573ce2effc518d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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}"