jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

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 }