maildirs (8819B)
1 #!/usr/bin/env zsh 2 # 3 # Jaro Mail, your humble and faithful electronic postman 4 # 5 # a tool to easily and privately handle your e-mail communication 6 # 7 # Copyleft (C) 2010-2015 Denis Roio <jaromil@dyne.org> 8 # 9 # This source code is free software; you can redistribute it and/or 10 # modify it under the terms of the GNU Public License as published by 11 # the Free Software Foundation; either version 3 of the License, or 12 # (at your option) any later version. 13 # 14 # This source code is distributed in the hope that it will be useful, 15 # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 # Please refer to the GNU Public License for more details. 18 # 19 # You should have received a copy of the GNU Public License along with 20 # this source code; if not, write to: 21 # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22 23 # static global variable formail cache (used by rmdupes) 24 typeset -h formail_cache 25 26 27 # checks if its a maildir 28 # returns 0 (success) if yes 29 # no in all other cases 30 maildircheck() { 31 fn maildircheck $* 32 33 { test -r "$1" } || { 34 func "Maildir not existing: $1" 35 return 1 } 36 { test -w "$1" } || { 37 error "Directory not writable: $1" 38 return 1 } 39 { test -r "$1/cur" } \ 40 && { return 0 } # Yes is a maildir 41 # shortened test to speedup 42 # && { test -r $1/new } \ 43 # && { test -r $1/tmp } \ 44 func "Not a maildir: $1" 45 return 1 46 } 47 48 maildirmake() { 49 fn maildirmake $* 50 51 { test -z "$1" } && { 52 error "internal error: missing argument for maildirmake" 53 return 255 } 54 55 { test -f "$1" } && { 56 func "not a maildir, but a file: $1" 57 return 254 } 58 59 { test -d "$1/new" } && { 60 func "maildir already exists: $1" 61 return 0 } 62 63 { test -r "$1" } && { 64 func "directory exists, but is not a maildir: $1" 65 return 254 } 66 67 func "creating maildir: $1" 68 69 ${=mkdir} ${1}/cur 70 ${=mkdir} ${1}/new 71 ${=mkdir} ${1}/tmp 72 73 return 0 74 } 75 76 77 # fills up all maildirs array 78 list_maildirs() { 79 fn list_maildirs $* 80 81 maildirs=`${=find} "$MAILDIRS" -maxdepth 1 -type d` 82 for m in ${(f)maildirs}; do 83 func "$m" 84 { maildircheck "$m" } && { 85 # is a maildir 86 { test "`${=find} "$m" -type f`" != "" } && { 87 # and is not empty 88 maildirs+=(`basename "$m"`) 89 } 90 } 91 done 92 return ${#maildirs} 93 } 94 95 96 rmdupes() { 97 fn rmdupes $* 98 99 ## special argument lastlog 100 { test "$1" = "lastlog" } && { 101 lastdirs=(`maildirs_lastlog`) 102 act "Pruning duplicates across ${#lastdirs} destination maildirs:" 103 act "${lastdirs}" 104 # recursion here 105 rmdupes ${=lastdirs} 106 notice "Done pruning" 107 # all the prioritization above is so that duplicates are spotted 108 # across different maildirs and deleted from the filtered source 109 return 0 110 } 111 ############### 112 113 tot=0 114 typeset -al msgs 115 116 ztmp 117 formail_cache=$ztmpfile 118 119 for folder in ${=@}; do 120 { test -r "$folder" } || { folder="$MAILDIRS/$folder" } 121 { test -r "$folder" } || { 122 error "Directory not found: $folder" 123 continue } 124 125 { maildircheck "${folder}" } || { 126 error "Not a maildir folder: $folder" 127 continue } 128 129 c=0 130 notice "Checking for duplicates in $folder" 131 msgs=`${=find} "${folder}" -maxdepth 2 -type f` 132 act "Please wait, this can take a while..." 133 134 135 136 for m in ${(f)msgs}; do 137 func "formail < $m" 138 # 128MB should be enough ehre? 139 formail -D 128000000 "$formail_cache" <"$m" \ 140 && rm "$m" && c=$(( $c + 1 )) 141 done 142 act "$c duplicates found and deleted" 143 tot=$(( $tot + $c )) 144 done 145 146 if [ "$tot" = "0" ]; then 147 act "No duplicates found at all" 148 else 149 act "$tot total duplicates found and deleted" 150 fi 151 } 152 153 merge() { 154 _src=${1} 155 _dst=${2} 156 157 [[ "$_src" = "$_dst" ]] && { 158 error "Cannot merge same directory in itself: $_src = $_dst" 159 return 1 } 160 161 maildircheck "$_src" 162 [[ $? = 0 ]] || { 163 error "Source is not a maildir: $_src" 164 return 1 165 } 166 167 maildircheck "$_dst" 168 [[ $? = 0 ]] || { 169 error "Destination is not a maildir: $_dst" 170 return 1 171 } 172 173 # merge does not uses deliver() because 174 # the new-flag and read-flags must be kept intact. 175 # it will abort on any single error on moving each file. 176 notice "Merging maildir ${_src} into ${_dst}" 177 fr=`${=find} ${_src} -type f` 178 179 c=0 180 for i in ${(f)fr}; do 181 182 refile "$i" "$_dst" 183 [[ $? = 0 ]] || { 184 # bail out on every single error 185 error "Error refiling emails to destination maildir" 186 error "Operation aborted, $c files moved." 187 return 1 188 } 189 c=$(($c + 1)) 190 done 191 notice "$c mails succesfully moved" 192 193 [[ $DRYRUN = 0 ]] && { 194 act "Removing source directory ${_src}" 195 ${=rm} -r "${_src}" 196 } 197 198 act "Done. All mails merged into ${_dst}" 199 } 200 201 maildir_shift() { 202 fn maildir_shift $* 203 dst=${1} 204 cmd=${2:-cp} 205 req=(dst) 206 freq=($dst) 207 ckreq || return 1 208 209 maildircheck "$dst" 210 [[ $? = 0 ]] || { 211 error "Destination is not a maildir: $dst" 212 return 1 213 } 214 215 [[ "$cmd" = "" ]] || { 216 case $cmd in 217 mv|cp) ;; 218 ln) 219 cmd="ln -s" 220 ;; 221 *) 222 error "refile command not supported: $cmd" 223 return 1 224 ;; 225 esac 226 } 227 228 notice "Moving paths on stdin to maildir: $dst" 229 230 for f in "${(f)$(cat)}"; do 231 [[ -r $f ]] || { 232 error "file not found: $f" 233 error "move operation aborted." 234 return 1 235 } 236 237 srcarr=( ${=f//\// } ) 238 srcnum=${#srcarr} 239 pos=${srcarr[$(( $srcnum - 1 ))]} 240 241 func "$cmd $f ${dst}/${pos}/" 242 [[ $DRYRUN = 0 ]] && { 243 ${=cmd} $f ${dst}/${pos}/ 244 print -n . 245 } 246 done 247 return 0 248 } 249 250 251 # move an email file from a maildir to another 252 # keeping cur/new/tmp positioning 253 refile() { 254 fn refile $* 255 src="$1" 256 dst="$2" 257 cmd=${3:-mv} 258 req=(src dst) 259 ckreq || { 260 error "refile() needs 2 args: source file and destination maildir" 261 return 1 262 } 263 264 [[ -r "$src" ]] || { 265 error "refile origin file not existing: $src" 266 return 1 267 } 268 269 # weak destination check, called should use maildircheck anyway 270 [[ -d "$dst"/new ]] || { 271 error "refile destination not a maildir: $dst" 272 return 1 273 } 274 275 [[ "$cmd" = "" ]] || { 276 case $cmd in 277 mv|cp) ;; 278 ln) 279 cmd="ln -s" 280 ;; 281 *) 282 error "refile command not supported: $cmd" 283 return 1 284 ;; 285 esac 286 } 287 288 func "refile command: $cmd" 289 290 srcarr=( ${=src//\// } ) 291 srcnum=${#srcarr} 292 pos=${srcarr[$(( $srcnum - 1 ))]} 293 func "$cmd $src ${dst}/${pos}/" 294 [[ $DRYRUN = 0 ]] && ${=cmd} $src ${dst}/${pos}/ 295 return $? 296 } 297 298 299 # LDA delivery to a maildir 300 # important to return 1 on all errors 301 # so that fetchmail does not deletes mail from server 302 deliver() { 303 fn deliver $* 304 # default destination is incoming 305 dest=${1:-incoming} 306 307 # create destination maildir if not existing 308 [[ -r "$MAILDIRS/$dest" ]] || { 309 act "creating destination maildir: $dest" 310 maildirmake "$MAILDIRS/$dest" 311 } 312 313 maildircheck "$MAILDIRS/$dest" 314 [[ $? = 0 ]] || { 315 error "Invalid maildir destination for delivery, operation aborted." 316 func "Returning error to caller." 317 return 1 318 } 319 320 func "deliver to $dest" 321 322 [[ "$2" = "" ]] && { 323 # no tag specified, plain delivery without indexing 324 ## destinations excluded from notmuch indexing 325 # for indexing rules see filter_maildir() 326 # [[ "$dest" = "outbox" ]] \ 327 # || [[ "$dest" =~ "^zz." ]] \ 328 # || [[ "$dest" = "incoming" ]] && { 329 base="${HOST}_jaro_`date +%Y-%m-%d_%H-%M-%S`_$RANDOM" 330 last_deliver="$MAILDIRS/$dest/new/$base" 331 cat > "$last_deliver" 332 [[ $? = 0 ]] || { 333 error "Could not deliver email file into maildir $dest" 334 func "Returning error to caller." 335 return 1 336 } 337 return 0 338 } 339 340 ######### 341 # notmuch indexing from here 342 tags="$2" 343 func "indexing tags: $tags" 344 345 nm insert --folder="$dest" ${=tags} 346 res=$? 347 [[ $res = 0 ]] || \ 348 error "Could not deliver email file into maildir $dest with tags $tags" 349 350 return $res 351 352 }