jaromail

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

commit e233f47ff29acfc176597e6480e89d67deb789ea
parent a5632f95183662cea1b1b99d346c4ddcbf2961ac
Author: Jaromil <jaromil@dyne.org>
Date:   Fri, 26 Dec 2014 16:04:19 +0100

new addressbook now using abook native format

Diffstat:
Msrc/jaro | 21++++++++++++---------
Msrc/zlibs/addressbook | 427+++++++++++++++++++++----------------------------------------------------------
Msrc/zlibs/search | 3+--
3 files changed, 125 insertions(+), 326 deletions(-)

diff --git a/src/jaro b/src/jaro @@ -153,6 +153,8 @@ fi # env override { test "$JAROWORKDIR" = "" } || { WORKDIR="${JAROWORKDIR}" } +# default addressbook +ADDRESSBOOK="$MAILDIRS/whitelist.abook" # which command to use when creating dirs mkdir="`command -v mkdir` -m 700 -p" @@ -194,7 +196,6 @@ fi ACCOUNTS="$MAILDIRS/Accounts" KEYRING="$MAILDIRS/Keyring" -addressbook="$MAILDIRS/Addressbook" # temporary directory TMPDIR="$MAILDIRS/tmp/jaromil.$USER" @@ -216,10 +217,6 @@ case $OS in act "Updating keyring location: $KEYRING" cp $WORKDIR/keyring "$KEYRING" } - { test -r $WORKDIR/addressbook } && { test ! -r $addressbook } && { - act "Updating addressbook location: $addressbook" - cp $WORKDIR/addressbook $addressbook } - ;; MAC) mount | grep 'JaroTmp' > /dev/null @@ -250,7 +247,7 @@ hostname=$(hostname) # gather the current hostname { test -r "$KEYRING" } || { create_keyring "$KEYRING" } # make sure we have an addressbook -{ test -r "$addressbook" } || { create_addressbook "$addressbook" } +[[ -r "$ADDRESSBOOK" ]] || { create_addressbook "$ADDRESSBOOK" } ${=mkdir} "$MAILDIRS/logs" # ${=mkdir} "$MAILDIRS/certs" @@ -647,8 +644,14 @@ main() fi { option_is_set -a } && { account=`option_value -a` } { option_is_set -l } && { - if [[ "`option_value -l`" =~ "black" ]]; then list=blacklist; fi - if [[ "`option_value -l`" =~ "white" ]]; then list=whitelist; fi + if [[ "`option_value -l`" =~ "black" ]]; then + list=blacklist + elif [[ "`option_value -l`" =~ "white" ]]; then + list=whitelist + else + list=`option_value -l` + fi + ADDRESSBOOK="$MAILDIRS"/$list.abook } { option_is_set -h } && { CLEANEXIT=0 usage; return 0 } @@ -693,7 +696,7 @@ main() isknown) CLEANEXIT=0; sender_isknown ${PARAM} ;; learn) CLEANEXIT=0; learn ${PARAM} ;; forget) CLEANEXIT=0; forget ${PARAM} ;; - list) CLEANEXIT=0; list_addresses ${PARAM} ;; + list) CLEANEXIT=0; edit_abook ${PARAM} ;; import) import_addressbook ${PARAM} ;; "export") diff --git a/src/zlibs/addressbook b/src/zlibs/addressbook @@ -4,7 +4,7 @@ # # a tool to easily and privately handle your e-mail communication # -# Copyleft (C) 2010-2014 Denis Roio <jaromil@dyne.org> +# Copyleft (C) 2010-2015 Denis Roio <jaromil@dyne.org> # # This source code is free software; you can redistribute it and/or # modify it under the terms of the GNU Public License as published by @@ -21,49 +21,41 @@ # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -ADDRESSBOOK="$MAILDIRS/Addressbook" ################### # Jaro Brother DB create_addressbook() { - func "create addressbook" - { test -r "$1" } && { - error "Addressbook already exists: $1" - return 1 + ab="$ADDRESSBOOK" + func "create addressbook: $ab" + [[ -r "$ab" ]] && { + error "Addressbook already exists: $ab" + return 1 } - cat <<EOF | ${SQL} -batch "$1" -CREATE TABLE whitelist -( - email text collate nocase unique, - name text collate nocase -); -CREATE TABLE blacklist -( - email text collate nocase unique, - name text collate nocase -); -EOF - { test $? != 0 } && { - error "Error creating addressbook database." - return 1 } + + touch "$ab" + # make sure is private - chmod 600 "$1" - chown $_uid:$_gid "$1" + chmod 600 "$ab" + chown $_uid:$_gid "$ab" return 0 } insert_address() { - _email="${(Q)1}"; _name="${(Q)2}"; + _email="${1}"; _name="${2}"; func "insert address: $_name <$_email>" - cat <<EOF | ${SQL} -batch $ADDRESSBOOK 2> /dev/null -INSERT INTO $list (email, name) -VALUES ("${_email}", "${_name}"); -EOF - [[ $? = 0 ]] || { + + lookup_email "$_email" + + [[ $? = 0 ]] && { func "address already present in $list" return 1 } + + print "From: $_name <$_email>" | \ + abook --datafile "$ADDRESSBOOK" \ + --add-email-quiet + return 0 } @@ -77,39 +69,21 @@ EOF # } remove_address() { - func "remove address <$1> from $list" - cat <<EOF | ${SQL} -batch $ADDRESSBOOK -DELETE FROM $list -WHERE email IS "${1}"; -EOF - { test $? != 0 } && { - func "address not found or error occurred" } + warning "remove_address() TODO in abook branch" + return 0 } -search_name() { - func "search_name from $list like $1" - cat <<EOF | ${SQL} -column -batch $ADDRESSBOOK -.width 64 128 -SELECT * FROM $list -WHERE name LIKE "%${1}%"; -EOF +search_addressbook() { + func "search \"$1\" in $list" + abook --datafile "$ADDRESSBOOK" --mutt-query "$1" } -search_email() { - func "search addressbook $list for $1" - cat <<EOF | ${SQL} -column -batch $ADDRESSBOOK -.width 64 128 -SELECT * FROM $list -WHERE email LIKE "%${1}%"; -EOF -} lookup_email() { - func "lookup email id from $list where $1" - cat <<EOF | ${SQL} -column -batch $ADDRESSBOOK -SELECT rowid FROM $list -WHERE email IS "${1}"; -EOF + func "lookup email $1 in $list" + abook --datafile "$ADDRESSBOOK" \ + --mutt-query "$1" > /dev/null + return $? } complete() { @@ -117,40 +91,29 @@ complete() { # completion on configured groups { test -r "$MAILDIRS/Groups" } && { - if [[ "$1" =~ "group/" ]]; then - func "completion will look into groups" - needle="${1[(ws:/:)2]}" - if [ "$needle" = "" ]; then - act "Listing all mailout groups" - matches=`${=find} "$MAILDIRS/Groups" -type f` - else - act "Searching for \"$needle\" in mailout groups" - matches=`${=find} "$MAILDIRS/Groups" -type f -name \"*$needle*\"` + if [[ "$1" =~ "group/" ]]; then + func "completion will look into groups" + needle="${1[(ws:/:)2]}" + if [ "$needle" = "" ]; then + act "Listing all mailout groups" + matches=`${=find} "$MAILDIRS/Groups" -type f` + else + act "Searching for \"$needle\" in mailout groups" + matches=`${=find} "$MAILDIRS/Groups" -type f -name \"*$needle*\"` + fi + print "Groups: `print $matches | wc -l` matches" + print + for i in ${(f)matches}; do + gr=`basename $i` + print "$gr@jaromail.group\t`wc -l < $i` recipients" + done + return 0 fi - print "Groups: `print $matches | wc -l` matches" - print - for i in ${(f)matches}; do - gr=`basename $i` - print "$gr@jaromail.group\t`wc -l < $i` recipients" - done - return 0 - fi } act "Searching for \"$1\" in addressbook $list" - matches="${matches}\n`search_name $1`" - - # mutt query requires something like this - print "jaro: $((`print $matches | wc -l` -1)) matches" - print "$matches" | awk ' -{ printf "%s\t", $1 - for(i=2;i<=NF;i++) { - sub("<","",$i) - sub(">","",$i) - if($i!=$1) printf "%s ", $i - } - printf "\n" }' - return 0 + abook --datafile "$ADDRESSBOOK" --mutt-query "$1" + return $? } sender_isknown() { @@ -160,16 +123,11 @@ sender_isknown() { /^$/ { exit }' | ${WORKDIR}/bin/fetchaddr -x From -a`" email="${head[(ws:,:)1]}" - exitcode=1 [[ "$email" = "" ]] && { return 1 } - lookup="`lookup_email ${email}`" - - [[ "$lookup" = "" ]] || { - func "sender_isknown() found <$email> in $list (id $lookup)" - return 0 } - - return 1 + abook --datafile $MAILDIRS/whitelist.abook \ + --mutt-query "$email" > /dev/null + return $? } learn() { @@ -194,7 +152,7 @@ learn() { print "$head" [[ $DRYRUN == 1 ]] || { insert_address "$email" "$name" - { test $? = 0 } && { act "new: $_name <${_email}>" } + [[ $? = 0 ]] && { act "new: $_name <${_email}>" } } return 0 ;; @@ -210,7 +168,7 @@ learn() { [[ $DRYRUN == 1 ]] || { insert_address "$email" "$name" - { test $? = 0 } && { act "new: $_name <${_email}>" } + [[ $? = 0 ]] && { act "new: $_name <${_email}>" } } done return 0 @@ -226,7 +184,7 @@ learn() { [[ $DRYRUN == 1 ]] || { insert_address "$email" "$name" - { test $? = 0 } && { act "new: $_name <${_email}>" } + [[ $? = 0 ]] && { act "new: $_name <${_email}>" } } done @@ -239,7 +197,7 @@ learn() { [[ $DRYRUN == 1 ]] || { insert_address "$email" "$name" - { test $? = 0 } && { act "new: $_name <${_email}>" } + [[ $? = 0 ]] && { act "new: $_name <${_email}>" } } done return 0 @@ -253,32 +211,26 @@ learn() { } forget() { - func "forget sender from mail in stdin" - act "Expecting mail from stdin pipe" - head="`${WORKDIR}/bin/fetchaddr -x From -a`" - # forget the email part of the parsed head - remove_address "${head[(ws:,:)1]}" -} -list_addresses() { - [[ "$1" = "" ]] || list="$1" + warning "forget() TODO in abook branch" + return 0 - act "Listing all contents for $list" - cat <<EOF | ${SQL} -column -header -batch $ADDRESSBOOK -.width 32 72 -SELECT * FROM $list; -EOF + # func "forget sender from mail in stdin" + # act "Expecting mail from stdin pipe" + # head="`${WORKDIR}/bin/fetchaddr -x From -a`" + # # forget the email part of the parsed head + # remove_address "${head[(ws:,:)1]}" } # import an addressbook, autodetect its type import_addressbook() { notice "Importing addressbook" if [ "${PARAM[1]}" != "" ]; then - func "file specified: ${PARAM[1]}" - # a file was given as argument - import_vcard ${PARAM[2]} + func "file specified: ${PARAM[1]}" + # a file was given as argument + import_vcard ${PARAM[2]} else - # no file as parameter - { test "$OS" = "MAC" } && { import_macosx } + # no file as parameter + { test "$OS" = "MAC" } && { import_macosx } fi } @@ -319,30 +271,30 @@ import_macosx() { return 0 } -# import addresbook email from VCard +# import emails from VCard into abook +# checks if the emails are already known import_vcard() { - act "import VCard from file: ${PARAM[1]}" + act "import VCard from file: $1" - { test -r "${PARAM[1]}" } || { - error "File not found: ${PARAM[1]}" - return 1 + [[ -r "$1" ]] || { + error "File not found: $1" + return 1 } - vcard=${PARAM[1]} + vcard="$1" head -n1 $vcard | grep '^BEGIN:VCARD' > /dev/null - { test $? = 0 } || { - error "File to import is not a VCard: $vcard" - return 1 + [[ $? = 0 ]] || { + error "File to import is not a VCard: $vcard" + return 1 } - notice "Import in addressbook VCard ${vcard}" - tmp=$TMPDIR/import.$datestamp.$RANDOM + notice "Import VCard in addressbook: ${vcard}" # parse the vcard and print a simple name and email list # each value on a single line, entry tuples followed by a # # we skip entries that don't have an email - cat ${vcard} | awk ' + addresses `cat ${vcard} | awk ' BEGIN { newcard=0; c=0; name=""; email=""; } /^BEGIN:VCARD/ { newcard=1 } /^FN:/ { if(newcard = 1) name=$0 } @@ -360,40 +312,41 @@ BEGIN { newcard=0; c=0; name=""; email=""; } next } } -' | cut -d: -f2 > $tmp +' | cut -d: -f2` # now parse the temporary list of name and emails # made of name, email and a hash for each, newline separated - addresses=`cat $tmp` -# ${=rm} $tmp + + # ${=rm} $tmp lock $ADDRESSBOOK newa=1; _name=""; _email="" for a in ${(f)addresses}; do - { test "${a[1]}" = "#" } && { - newa=1; # its the end of the entry - - # handle lines with multiple emails in vcard - # TODO: generate Groups/${_name} from this - for ee in ${=_email}; do - # check if we have this email already - _e=`print ${ee} | extract_emails` - func "lookup_email: ${_e}" - foundemail=`lookup_email "${_e}"` -# func "lookup_email: ${_email}" - - { test "$foundemail" = "" } && { - insert_address "${_e}" "${_name}" - act "${a} ${_name} <${_e}>" - } - done - - continue } - { test $newa -eq 1 } && { - # (V) makes special chars visible, we need to remove them.. - _name=`echo ${(V)a} | cut -d^ -f1`; newa=0; continue } - { test $newa -eq 0 } && { _email=`echo ${(V)a} | cut -d^ -f1` } + { test "${a[1]}" = "#" } && { + newa=1; # its the end of the entry + + # handle lines with multiple emails in vcard + # TODO: generate Groups/${_name} from this + for ee in ${=_email}; do + # check if we have this email already + _e=`print ${ee} | extract_emails` + func "lookup_email: ${_e}" + lookup_email "${_e}" + + [[ $? = 0 ]] || { + insert_address "${_e}" "${_name}" + act "${a} ${_name} <${_e}>" + } + done + + continue } + if [[ $newa -eq 1 ]]; then + # (V) makes special chars visible, we need to remove them.. + _name="${(V)a[(ws:^:)1}"; newa=0; continue + elif [[ $newa -eq 0 ]]; then + _email="${(V)a[(ws:^:)1}" + fi done unlock $ADDRESSBOOK @@ -401,177 +354,21 @@ BEGIN { newcard=0; c=0; name=""; email=""; } notice "Done importing addresses" } -export_abook() { - - lock $ADDRESSBOOK - - out=$MAILDIRS/$list.abook - act "Exporting $list to abook format: $out" - rm -f $out - - func "launching SELECT email,name sqlite3 query" - addresses=`cat <<EOF | ${SQL} -column -header -batch $ADDRESSBOOK \ - | grep -v '^email' -.width 40 100 -.mode list -.separator '|' -SELECT email, name FROM $list; -EOF` - - unlock $ADDRESSBOOK - func "converting database into abook format" - cat <<EOF > $out -# abook addressbook file - -[format] -program=JaroMail -version=$VERSION - -EOF - c=0 - for a in ${(f)addresses}; do - _email="${(Q)a[(ws:|:)1]}" - # remove from name all what is an email between brackets - # crop (trim) all beginning and ending whitespaces from name - _name=`print ${(Q)a[(ws:|:)2]} | trim` - { test "${_email}" != "" } && { - cat <<EOF >> $out -[${c}] -name=${_name} -email=${_email} - -EOF - c=$(( $c + 1 )) - } - done - -} - # export addressbook to vcard export_vcard() { - - act "Export addressbook into vCard $ADDRESSBOOK.vcf" - tmp=$TMPDIR/export.$datestamp.$RANDOM - - lock $ADDRESSBOOK - - cat <<EOF | ${SQL} -column -header -batch $ADDRESSBOOK \ - | grep -v '^email' > $tmp -.width 40 100 -.mode list -.separator '|' -SELECT email, name FROM $list; -EOF - - unlock $ADDRESSBOOK - - addresses=`cat $tmp` - ${=rm} $tmp - - rm -f $ADDRESSBOOK.vcf - touch $ADDRESSBOOK.vcf - for a in ${(f)addresses}; do - _email="${a[(ws:|:)1]}" - # remove from name all what is an email between brackets - # crop (trim) all beginning and ending whitespaces from name - _name=`print ${a[(ws:|:)2]} | sed 's/<.*>//;s/^[ \t]*//;s/[ \t]*$//'` - { test "${_email}" != "" } && { - cat <<EOF >> $ADDRESSBOOK.vcf -BEGIN:VCARD -VERSION:3.0 -FN:${_name} -N:;${_name};;; -EMAIL;TYPE=HOME:${_email// /} -END:VCARD -EOF - } - done - + abook --convert --informat abook \ + --infile "$ADDRESSBOOK" \ + --outformat gcrd --outfile "$MAILDIRS"/$list.vcf + return $? } edit_abook() { - # take argument even without option -l - [[ "$1" = "" ]] || list="$1" - # check if abook binary is found - { command -v abook > /dev/null } || { - error "ABook not found, operation aborted." - return 1 - } - - notice "Preparing to edit addressbook $list" - - export_abook "$list" - - func "abook format ready, generating configuration" - - # generate abook configuration -# new abook supports also: -# set emailpos=35 -# set extra_column=-1 - - func "ready to launch abook." - - abook --config <(cat <<EOF > $abookrc + abook --config <(cat <<EOF set autosave=true set mutt_command=jaro set sort_field=name EOF ) --datafile $MAILDIRS/$list.abook - - act "Saving changes to native addressbook..." - func "exporting abook to spruce format" - - addresses=`abook --convert --infile $MAILDIRS/$list.abook \ - --outformat spruce | awk ' -BEGIN { c=0; name=""; email=""; } -/^#/ { if(email != "") { - c+=1 - print name - print email - print "# " c - } - email="" - next - } -/^Name:/ { name=$0 } -/^Email:/ { email=$0 } -'` - - func "done, ready to reimport the database" - - tmp=$TMPDIR/abook_edit.$datestamp.$RANDOM - - # move addressbook to old - act "Updating the addressbook database" - cat <<EOF > $tmp -DROP TABLE $list; -CREATE TABLE $list -( - email text collate nocase unique, - name text collate nocase -); -PRAGMA synchronous = OFF; -EOF - - newa=1; _name=""; _email="" - for a in ${(f)addresses}; do - - [[ "${a[1]}" = "#" ]] && { - newa=1; #its the end of the entry - print "INSERT INTO $list (email, name) VALUES (\"${_email}\", \"${_name}\");" >> $tmp - continue } - - [[ $newa -eq 1 ]] && { - _name="${a[(ws/:/)2]}"; newa=0; continue } - [[ $newa -eq 0 ]] && { a=${a// /}; _email="${a[(ws/:/)2]}" } - - done - func "Inserting the updated addressbook" - lock $ADDRESSBOOK - cat $tmp | ${SQL} -batch $addressbook - rm $tmp - unlock $ADDRESSBOOK - notice "Addressbook updated" } ################### diff --git a/src/zlibs/search b/src/zlibs/search @@ -60,8 +60,7 @@ search() { res="" for t in ${term}; do - res+=`search_name ${t}` - res+=`search_email ${t}` + res+=`search_addressbook ${t}` done for rr in ${(f)res}; do