├── README.rst ├── build ├── 01-date-local.patch ├── 02-hack-notmuch-with-embedded-manpages.patch ├── ardet.pl ├── centos6 │ ├── README │ ├── podman-notmuch-build-in-centos6.sh │ ├── podman-notmuch-buildenv-centos6.sh │ └── podman-notmuch-buildwip-centos6.sh ├── make-one-notmuch-el.pl └── podman-notmuch-buildenv.sh ├── mboxviewfs ├── mboxviewfs-notmuchmail.sh └── mboxviewfs.c ├── md5mda.sh ├── nottoomuch-addresses.rst ├── nottoomuch-addresses.sh ├── nottoomuch-emacs-mailto.pl ├── nottoomuch-emacs-mailto.rst ├── nottoomuch-remote-emacs.sh ├── nottoomuch-remote.bash ├── nottoomuch-remote.rst ├── nottoomuch-xdg-email.sh ├── selection-menu.el ├── selection-menu.png ├── selection-menu.rst ├── startfetchmail.sh └── wip ├── frm-md5mdalog.pl ├── mbox-to-mda.sh ├── mm ├── nottoomuch-emacs-mua.bash ├── nottoomuch-gmail-emacs.sh └── nottoomuch-wrapper.c /README.rst: -------------------------------------------------------------------------------- 1 | nottoomuch 2 | ========== 3 | 4 | misc material i use with notmuch mail indexer 5 | 6 | 7 | introduction 8 | ------------ 9 | 10 | my personal “extensions” around notmuch mail indexer that are too specific 11 | to be added to http://notmuchmail.org/ wiki, with additional scripts 12 | that aren't feasible to be stored to the wiki. 13 | 14 | address completion: 15 | 16 | nottoomuch-addresses_ | 17 | selection-menu_ 18 | 19 | mail sending: 20 | 21 | nottoomuch-emacs-mailto_ | 22 | `nottoomuch-xdg-email <#mail-sending>`__ 23 | 24 | mail delivery: 25 | 26 | `startfetchmail <#mail-delivery>`__ | 27 | `md5mda <#mail-delivery>`__ 28 | 29 | remote access: 30 | 31 | nottoomuch-remote_ 32 | 33 | building: 34 | 35 | `make-one-notmuch-el <#building>`__ 36 | 37 | 38 | address completion 39 | ------------------ 40 | 41 | nottoomuch-addresses_ 42 | the address completion provider i use to get list of email addresses 43 | from where email addresses is selected when sending emails. 44 | 45 | .. _nottoomuch-addresses: nottoomuch-addresses.rst 46 | 47 | selection-menu_ 48 | the address completion tool i use to complete email addresses 49 | when sending emails. 50 | 51 | .. _selection-menu: selection-menu.rst 52 | 53 | 54 | mail sending 55 | ------------ 56 | 57 | nottoomuch-emacs-mailto_ 58 | send mail from e.g. following mailto: link in web browsers, 59 | using notmuch emacs client. 60 | 61 | .. _nottoomuch-emacs-mailto: nottoomuch-emacs-mailto.rst 62 | 63 | nottoomuch-xdg-email_ 64 | wrap ``xdg-email`` with this (by putting this as ``xdg-email`` in 65 | ``$PATH`` before the system one) so that nottoomuch-emacs-mailto_ 66 | is used as the mailer. 67 | 68 | .. _nottoomuch-xdg-email: nottoomuch-xdg-email.sh 69 | 70 | 71 | mail delivery 72 | ------------- 73 | 74 | startfetchmail_ 75 | the fetchmail startup script i use to get it configured as required 76 | and to see that startup succeeded (failures due to incorrect password etc). 77 | 78 | .. _startfetchmail: startfetchmail.sh 79 | 80 | md5mda_ 81 | the mail delivery agent i uset to get mails delivered from fetchmail 82 | to target directories. mails are finally delivered to subdirs whose first 83 | 2 characters are 2 first hexdigits of the md5sum of the file contents 84 | and the file name is rest 30 hexdigits of the file md5sum. 85 | 86 | startfetchmail_ provides an example how md5mda_ is used. 87 | 88 | .. _md5mda: md5mda.sh 89 | 90 | 91 | remote access 92 | ------------- 93 | 94 | nottoomuch-remote_ 95 | access notmuch on remote machine using ssh without passwordless login 96 | requirement. 97 | 98 | .. _nottoomuch-remote: nottoomuch-remote.rst 99 | 100 | 101 | building 102 | -------- 103 | 104 | make-one-notmuch-el_ 105 | i like to have the notmuch emacs byte-compiled file available as a one 106 | file which is easy to carry along. this script combines all notmuch .el 107 | files together (with minor adjustments) in suitable order for 108 | byte-compilation as one file to succeed. the final ``one-notmuch.elc`` 109 | is somewhat smaller than all notmuch ``.elc`` files separately and 110 | may even load a bit faster. i've been using this for quite a long time 111 | and have not had problems -- but ymmv with your different setup in case 112 | trying this option. 113 | 114 | .. _make-one-notmuch-el: build/make-one-notmuch-el.pl 115 | 116 | 117 | work in progress 118 | ---------------- 119 | 120 | some code that has not reached suitable maturity state (I am using to 121 | distribute changes to many dev machines) or is lacking reasonable 122 | ux/documentation, is located in `wip/ `__ subdictory. 123 | 124 | 125 | contributing 126 | ------------ 127 | 128 | anything can be sent digitally to the email address below, or the ways 129 | any particular code repository interfaces provide (in case i receive 130 | email notication). thanks for all contributions i've received so far. 131 | 132 | how contributions appear to the repository is another issue. in any 133 | case proper attribution is given in all cases (preferably as commit 134 | author but sometimes in other ways...). 135 | 136 | 137 | *too ät iki dot fi* 138 | -------------------------------------------------------------------------------- /build/01-date-local.patch: -------------------------------------------------------------------------------- 1 | From 6696121faeefb28f76c69ef97c3b4f20f5d75d68 Mon Sep 17 00:00:00 2001 2 | From: Tomi Ollila 3 | Date: Wed, 12 Aug 2020 23:40:13 +0300 4 | Subject: [DRAFT PATCH V2] emacs: show local date next to Date: in case value differs 5 | 6 | When adding Date: header of a message to notmuch-show buffer, compare the 7 | date string with local representation of it and if these differ, output 8 | Date: {original-date-string} ({local-date-representation}) 9 | 10 | This is useful when mail system provides Date: strings with 11 | different timezone information than the sender is located at. 12 | 13 | --- 14 | 15 | V2 of id:1427132722-20346-1-git-send-email-tomi.ollila@iki.fi 16 | sent Mon, 23 Mar 2015 19:45:22 +0200. 17 | 18 | Resent due to conflict during rebase Aug 2020. 19 | 20 | And still an "early" draft =D, see discussion at 21 | 22 | https://nmbug.notmuchmail.org/nmweb/search/id%3A1427132722-20346-1-git-send-email-tomi.ollila%40iki.fi 23 | 24 | emacs/notmuch-show.el | 19 ++++++++++++++++--- 25 | 1 file changed, 16 insertions(+), 3 deletions(-) 26 | 27 | diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el 28 | index b0f2d28b..6498c041 100644 29 | --- a/emacs/notmuch-show.el 30 | +++ b/emacs/notmuch-show.el 31 | @@ -482,14 +482,27 @@ (defun notmuch-show-insert-header (header header-value) 32 | "Insert a single header." 33 | (insert header ": " (notmuch-sanitize header-value) "\n")) 34 | 35 | -(defun notmuch-show-insert-headers (headers) 36 | +(defun notmuch--make-date (timestamp) 37 | + (if (> timestamp 2147483647) 38 | + (message-make-date (seconds-to-time timestamp)) 39 | + (message-make-date (encode-time timestamp 0 0 1 1 1970 t)))) 40 | + 41 | +(defun notmuch-show-insert-headers (headers &optional timestamp) 42 | "Insert the headers of the current message." 43 | - (let ((start (point))) 44 | + (let ((start (point)) 45 | + date-local) 46 | (mapc (lambda (header) 47 | (let* ((header-symbol (intern (concat ":" header))) 48 | (header-value (plist-get headers header-symbol))) 49 | (when (and header-value 50 | (not (string-equal "" header-value))) 51 | + (if (and timestamp 52 | + (string-equal header "Date") 53 | + (not (string-equal 54 | + (setq date-local (notmuch--make-date timestamp)) 55 | + header-value))) 56 | + (setq header-value 57 | + (format "%s (%s)" header-value date-local))) 58 | (notmuch-show-insert-header header header-value)))) 59 | notmuch-message-headers) 60 | (save-excursion 61 | @@ -1056,7 +1069,7 @@ (defun notmuch-show-insert-msg (msg depth) 62 | ;; Set `headers-start' to point after the 'Subject:' header to be 63 | ;; compatible with the existing implementation. This just sets it 64 | ;; to after the first header. 65 | - (notmuch-show-insert-headers headers) 66 | + (notmuch-show-insert-headers headers (plist-get msg :timestamp)) 67 | (save-excursion 68 | (goto-char content-start) 69 | ;; If the subject of this message is the same as that of the 70 | -- 71 | 2.25.1 72 | 73 | -------------------------------------------------------------------------------- /build/02-hack-notmuch-with-embedded-manpages.patch: -------------------------------------------------------------------------------- 1 | From 01da1179b6acb1fabe1f676bee7fbc5a5c86da25 Mon Sep 17 00:00:00 2001 2 | From: Tomi Ollila 3 | Date: Sun, 17 Jan 2021 20:47:09 +0200 4 | Subject: [SUPPORTIVE PATCH #2] hack: notmuch help ... using embedded manual pages 5 | MIME-Version: 1.0 6 | Content-Type: text/plain; charset=UTF-8 7 | Content-Transfer-Encoding: 8bit 8 | 9 | ¡¡ NOT TO BE MERGED TO NOTMUCH REPOSITORY !! 10 | 11 | Normal notmuch help system relies on notmuch manual pages installed 12 | separately. 13 | 14 | If you're like me and copy only built notmuch to some other systems 15 | the default help system provides notmuch help. 16 | 17 | This hack embeds built notmuch manual pages (in gzipped roff format) 18 | into the notmuch binary and creates `gzip -dc | nroff -man | less` 19 | pipeline to display requested notmuch manual page. 20 | --- 21 | Makefile.local | 11 ++++ 22 | notmuch.c | 4 ++ 23 | x-embd-manp/embedded-manpages.c | 106 ++++++++++++++++++++++++++++++++ 24 | x-embd-manp/gen-manpage-code.pl | 70 +++++++++++++++++++++ 25 | 4 files changed, 191 insertions(+) 26 | create mode 100644 x-embd-manp/embedded-manpages.c 27 | create mode 100755 x-embd-manp/gen-manpage-code.pl 28 | 29 | diff --git a/Makefile.local b/Makefile.local 30 | index e12b94cd..5138bff3 100644 31 | --- a/Makefile.local 32 | +++ b/Makefile.local 33 | @@ -253,6 +253,17 @@ notmuch_client_srcs = \ 34 | mime-node.c \ 35 | tag-util.c 36 | 37 | +# --- embedded manpages part begin --- 38 | +x-embd-manp: 39 | + mkdir $@ 40 | +x-embd-manp/embedded-manpages.o x-embd-manp/manpages-data.o: | x-embd-manp 41 | +notmuch_client_srcs += x-embd-manp/embedded-manpages.c 42 | +notmuch_client_srcs += $(CURDIR)/x-embd-manp/manpages-data.c 43 | +$(CURDIR)/x-embd-manp/manpages-data.c: $(MAN_GZIP_FILES) 44 | +$(CURDIR)/x-embd-manp/manpages-data.c: $(srcdir)/x-embd-manp/gen-manpage-code.pl 45 | + $(srcdir)/x-embd-manp/gen-manpage-code.pl $@ 46 | +# --- embedded manpages part end ---- 47 | + 48 | notmuch_client_modules = $(notmuch_client_srcs:.c=.o) 49 | 50 | notmuch.o: version.stamp 51 | diff --git a/notmuch.c b/notmuch.c 52 | index d0a94fc2..43b7af16 100644 53 | --- a/notmuch.c 54 | +++ b/notmuch.c 55 | @@ -318,6 +318,10 @@ _help_for (const char *topic_name) 56 | return EXIT_SUCCESS; 57 | } 58 | 59 | + // does not return 60 | + extern void embedded_manpages(const char *); 61 | + embedded_manpages(topic_name); 62 | + 63 | if (strcmp (topic_name, "help") == 0) { 64 | printf ("The notmuch help system.\n\n" 65 | "\tNotmuch uses the man command to display help. In case\n" 66 | diff --git a/x-embd-manp/embedded-manpages.c b/x-embd-manp/embedded-manpages.c 67 | new file mode 100644 68 | index 00000000..7ae74410 69 | --- /dev/null 70 | +++ b/x-embd-manp/embedded-manpages.c 71 | @@ -0,0 +1,106 @@ 72 | +/* 73 | + * This file is external hack to have namual pages embedded in single 74 | + * notmuch binary. 75 | + * As notmuch, this file is licenced under GPLv3+ 76 | + */ 77 | + 78 | +#include 79 | +#include 80 | +#include 81 | +#include 82 | +#include 83 | +#include 84 | + 85 | +static void show_manpage(const char * content, int clen); 86 | + 87 | +extern struct { 88 | + // const unsigned char * in manpages-data.c 89 | + const char * manpage; 90 | + const int pagesize; 91 | +} manpages[]; 92 | + 93 | +void embedded_manpages(const char * topic); 94 | +void embedded_manpages(const char * topic) 95 | +{ 96 | + if (strcmp(topic, "help") == 0) 97 | + topic = "notmuch"; 98 | + 99 | + int tlen1 = strlen(topic) + 1; 100 | + for (int i = 0; manpages[i].manpage; i++) { 101 | + const char * manpage = manpages[i].manpage; 102 | + if (memcmp(topic, manpage, tlen1) == 0) 103 | + show_manpage(manpage + tlen1, manpages[i].pagesize); 104 | + } 105 | + // 106 | + exit(1); 107 | +} 108 | + 109 | +static void die(const char * msg) { 110 | + fprintf(stderr, "%s: %s\n", msg, strerror(errno)); 111 | + exit(1); 112 | +} 113 | + 114 | +static void xpipe(int * fds) { 115 | + if (pipe(fds) < 0) die("pipe failed"); 116 | +} 117 | +static pid_t xfork() { 118 | + pid_t pid = fork(); 119 | + if (pid < 0) die("fork failed"); 120 | + return pid; 121 | +} 122 | +static void movefd(int ofd, int nfd) { 123 | + if (ofd == nfd) return; 124 | + dup2(ofd, nfd); 125 | + close(ofd); 126 | +} 127 | + 128 | +static void show_manpage(const char * content, const int clength) 129 | +{ 130 | + int fds[2]; 131 | + 132 | + xpipe(fds); 133 | + if (xfork()) { 134 | + // parent 135 | + close(fds[1]); 136 | + movefd(fds[0], 0); 137 | + execlp("less", "less", NULL); 138 | + die("execlp failed"); 139 | + } 140 | + int wfd = fds[1]; 141 | + close(fds[0]); 142 | + 143 | + xpipe(fds); 144 | + if (xfork() == 0) { 145 | + // child 146 | + close(fds[1]); 147 | + movefd(fds[0], 0); 148 | + movefd(wfd, 1); 149 | + execlp("nroff", "nroff", "-man", NULL); 150 | + die("execlp failed"); 151 | + } 152 | + close(wfd); 153 | + wfd = fds[1]; 154 | + close(fds[0]); 155 | + 156 | + xpipe(fds); 157 | + if (xfork() == 0) { 158 | + // child 159 | + close(fds[1]); 160 | + movefd(fds[0], 0); 161 | + movefd(wfd, 1); 162 | + execlp("gzip", "gzip", "-dc", NULL); 163 | + die("execlp failed"); 164 | + } 165 | + close(wfd); 166 | + wfd = fds[1]; 167 | + close(fds[0]); 168 | + 169 | + write(wfd, content, clength); 170 | + close(wfd); 171 | + while (1) { 172 | + if (wait(NULL) < 0 && errno != EINTR) 173 | + break; 174 | + } 175 | + exit(0); 176 | + // defunct until less(1) exits and init(8) reaps 177 | +} 178 | diff --git a/x-embd-manp/gen-manpage-code.pl b/x-embd-manp/gen-manpage-code.pl 179 | new file mode 100755 180 | index 00000000..174960b2 181 | --- /dev/null 182 | +++ b/x-embd-manp/gen-manpage-code.pl 183 | @@ -0,0 +1,70 @@ 184 | +#!/usr/bin/perl 185 | +# -*- mode: cperl; cperl-indent-level: 4 -*- 186 | + 187 | +use 5.8.1; 188 | +use strict; 189 | +use warnings; 190 | + 191 | +$ENV{'PATH'} = '/sbin:/usr/sbin:/bin:/usr/bin'; 192 | + 193 | +my $mpdir = 'doc/_build/man'; 194 | + 195 | +die "'$mpdir' missing; build docs first\n" unless -d $mpdir; 196 | + 197 | +die "Usage: $0 output-file\n" unless @ARGV == 1; 198 | + 199 | +my %skip = map { $_.'.gz' => 1 } qw/notmuch-emacs-mua.1 notmuch.3/; 200 | + 201 | +#print %skip, "\n"; 202 | + 203 | +my @manpages; 204 | + 205 | +for my $pd (<$mpdir/man*>) { 206 | + #print $pd, "\n"; 207 | + 208 | + for my $f (<$pd/notmuch[.-]*.gz>) { 209 | + my $b = $f; $b =~ s/.*\///; 210 | + next if defined $skip{$b}; 211 | + $b =~ s/[.]?\d+[.]gz//; $b =~ s/notmuch-//; 212 | + push @manpages, [ $b, $f ]; 213 | + #print "$b: $f\n"; 214 | + } 215 | +} 216 | + 217 | +open O, '>', $ARGV[0] or die "Cannot write file '$ARGV[0]': $!\n"; 218 | +select O; 219 | +print "/* generated */\n"; 220 | + 221 | +my @pagevars; 222 | + 223 | +foreach (@manpages) { 224 | + my $vn = $_->[0]; 225 | + $vn =~ tr/-/_/; 226 | + my $fn = $_->[1]; 227 | + open I, '<', $fn or die "Cannot read $fn: $!\n"; 228 | + binmode I; 229 | + my $buf; 230 | + sysread I, $buf, 32768; 231 | + close I; 232 | + my $buflen = length $buf; 233 | + my $c = 1; 234 | + print "\nstatic const unsigned char ${vn}[] = {\n"; 235 | + push @pagevars, "{ $vn, $buflen }"; 236 | + foreach (split //, $_->[0]) { print ord($_), ", "; } print "0,\n"; 237 | + foreach (split //, $buf) { 238 | + if ($c & 15) { print ord($_), ", "; } else { print ord($_), ",\n"; } 239 | + $c++; 240 | + } 241 | + print "};\n"; 242 | + 243 | +} 244 | + 245 | +my $nr_mp_1 = scalar @manpages + 1; 246 | + 247 | +print "\nstruct { 248 | + const unsigned char * manpage; 249 | + const int len; 250 | +} manpages[$nr_mp_1] = {\n "; 251 | + 252 | +print join ",\n ", @pagevars; 253 | +print ",\n { (void *)0, 0 }\n};\n"; 254 | -- 255 | 2.31.1 256 | 257 | -------------------------------------------------------------------------------- /build/ardet.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -*- mode: cperl; cperl-indent-level: 4 -*- 3 | # $ ardet.pl $ 4 | # 5 | # Author: Tomi Ollila -- too ät iki piste fi 6 | # 7 | # Copyright (c) 2019 Tomi Ollila 8 | # All rights reserved 9 | # 10 | # Created: Sun 17 Mar 2019 13:30:15 EET too 11 | # Last modified: Sun 17 Mar 2019 18:16:00 +0200 too 12 | 13 | # SPDX-License-Identifier: Artistic-2.0 14 | 15 | use 5.8.1; 16 | use strict; 17 | use warnings; 18 | 19 | my ($time, $uid, $gid) = (0,0,0); 20 | ($time, $uid, $gid) = ($1, $2, $3), shift 21 | if @ARGV and $ARGV[0] =~ /^(\d+)\s+(\d+)\s+(\d+)$/; 22 | 23 | die "Usage: [istr] files...\n" unless @ARGV; 24 | 25 | die "time $time > 9999999999 -- does not fit\n" if $time > 9999999999; 26 | die "uid $uid > 99999 -- does not fit\n" if $uid > 99999; 27 | die "gid $gid > 99999 -- does not fit\n" if $gid > 99999; 28 | 29 | my $ri = sprintf "%-11d %-5d %-5d ", $time, $uid, $gid; # 24 octets 30 | my $re = ' ' x 24; 31 | 32 | my (@mfiles, $failed); 33 | 34 | sub fwarn($) { 35 | warn $_[0] =~ /:$/ ? "$_[0] $!\n" : "$_[0]\n"; 36 | $failed = 1; 37 | } 38 | 39 | # https://en.wikipedia.org/wiki/Ar_(Unix) 40 | 41 | # scan files where changes required 42 | foreach (@ARGV) { 43 | fwarn "Cannot open $_:",next unless open my $fh, '<', $_; 44 | my $f = $_; 45 | fwarn "Cannot read from $_:",next unless sysread $fh, $_, 8 || 0; 46 | fwarn "'$f' did not start with '!\\n",next unless $_ eq "!\n"; 47 | while (1) { 48 | fwarn "Cannot read from $_:",next unless defined sysread $fh, $_, 60; 49 | last unless $_; 50 | fwarn "Could not read 60 bytes from $_\n",next unless length($_) == 60; 51 | my $fri = substr $_, 16, 24; 52 | if ($fri ne $ri and $fri ne $re) { 53 | fwarn "'$f' is not writable" unless -w $f; 54 | push @mfiles, $f; 55 | last; 56 | } 57 | my $len = (substr $_, 48, 10) + 0; 58 | $len++ if $len & 1; 59 | sysseek $fh, $len, 1; # 1 == SEEK_SET (i expect de-facto portability...) 60 | } 61 | } 62 | die "Encountered problems -- no changes made\n" if $failed; 63 | 64 | # do changes (duplicate code, but less work w/o refactoring) 65 | foreach (@mfiles) { 66 | fwarn "Cannot open $_:",next unless open my $fh, '+<', $_; 67 | my $f = $_; 68 | fwarn "Cannot read from $_:",next unless sysread $fh, $_, 8 || 0; 69 | fwarn "'$f' did not start with '!\\n",next unless $_ eq "!\n"; 70 | while (1) { 71 | fwarn "Cannot read from $_:",next unless defined sysread $fh, $_, 60; 72 | last unless $_; 73 | fwarn "Could not read 60 bytes from $_\n",next unless length($_) == 60; 74 | my $fri = substr $_, 16, 24; 75 | my $len = (substr $_, 48, 10) + 0; 76 | if ($fri ne $ri and $fri ne $re) { 77 | sysseek $fh, -44, 1; # 1 == SEEK_SET (ditto) 78 | syswrite $fh, $ri; 79 | $len += 20 # 44 - 24 80 | } 81 | $len++ if $len & 1; 82 | sysseek $fh, $len, 1; # 1 == SEEK_SET (i expect de-facto portability...) 83 | } 84 | } 85 | die "Encountered problems -- some changes may have been done\n" if $failed; 86 | -------------------------------------------------------------------------------- /build/centos6/README: -------------------------------------------------------------------------------- 1 | 2 | I used to use notmuch on Scientific Linux 6.2 (like rhel6) system, 3 | from 2021 to end of may 2022 (notmuch 0.6 - notmuch 0.36). 4 | 5 | At some point I could not continue compiling notmuch on that 6 | particular system, so I had to create these build containers 7 | to get the job done. 8 | 9 | But no more, so I moved these neat(?) scripts here, 10 | there may be some reference value here... 11 | -------------------------------------------------------------------------------- /build/centos6/podman-notmuch-build-in-centos6.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Author: Tomi Ollila -- too ät iki piste fi 4 | # 5 | # Copyright (c) 2019 Tomi Ollila 6 | # All rights reserved 7 | # 8 | # Created: Sun 20 Oct 2019 20:41:59 +0300 too 9 | # Last modified: Tue 26 Apr 2022 23:42:45 +0300 too 10 | 11 | # Note: writes material under $HOME. grep HOME {thisfile} to see details... 12 | 13 | # SPDX-License-Identifier: BSD-2-Clause 14 | 15 | case ${BASH_VERSION-} in *.*) set -o posix; shopt -s xpg_echo; esac 16 | case ${ZSH_VERSION-} in *.*) emulate ksh; esac 17 | 18 | set -euf # hint: sh -x thisfile [args] to trace execution 19 | 20 | die () { 21 | { case "$-" in *x*) ;; *) printf '%s\n' "$@" 22 | esac; } 2>/dev/null 23 | exit 1; 24 | } >&2 25 | 26 | x () { printf '+ %s\n' "$*" >&2; "$@"; } 27 | 28 | test $# -ge 3 || { 29 | exec >&2; echo 30 | echo "Usage: $0 yyyymmdd notmuch-srcdir [0] [1]..." 31 | echo 32 | echo ' yyyymmdd: TAG of notmuch-buildenv-centos6 container image' 33 | echo 34 | podman images notmuch-buildenv-centos6 35 | echo 36 | grep '^case .* [0-9] '\' "$0" 37 | echo 38 | echo "Usually, after libs once done, to rebuild '3' and '7' are enough." 39 | echo "Build is done in-tree and after build, final 'notmuch' binary" 40 | echo "must *not* be stripped." 41 | echo 42 | exit 1 43 | } 44 | 45 | tag=$1; shift 46 | 47 | ciname=notmuch-buildenv-centos6:$tag 48 | coname=notmuch-buildenv-centos6-$tag 49 | 50 | if test "${1-}" != '--in-container--' 51 | then 52 | podman inspect "$ciname" --format '{{.RepoTags}}' 53 | 54 | test -f "$1/configure" && test -f "$1/lib/notmuch-private.h" || 55 | die "'$1' does not look like notmuch source dir" 56 | 57 | nmsrc=$1; shift 58 | 59 | case $0 in /*) dn0=${0%/*} 60 | ;; */*/*) dn0=`cd "${0%/*}" && pwd` 61 | ;; ./*) dn0=$PWD 62 | ;; */*) dn0=`cd "${0%/*}" && pwd` 63 | ;; *) dn0=$PWD 64 | esac 65 | 66 | # used initially, but stated "non-essential", so... 67 | #test "${XDG_CACHE_HOME-}" && 68 | # cache_dir=$XDG_CACHE_HOME || cache_dir=$HOME/.cache 69 | #case $cache_dir in $HOME*) ;; *) 70 | # die "'$cache_dir' not in '$HOME'..." 71 | #esac 72 | Z=`exec date +%Z` 73 | x podman run --pull=never --rm -it --privileged -v "$dn0:/media" \ 74 | --tmpfs /tmp:rw,size=65536k,mode=1777 \ 75 | -v "$HOME:$HOME" -v "$nmsrc:/mnt" \ 76 | --name "$coname" "$ciname" \ 77 | /media/"${0##*/}" "$tag" --in-container-- \ 78 | "$HOME" "$Z" '' "$@" '' 79 | echo 'back in "host" environment...' 80 | echo 81 | echo all done 82 | echo 83 | exit 0 84 | fi 85 | 86 | ### rest of the file executed in container ### 87 | 88 | echo -- 89 | echo -- Executing in container -- xtrace now on -- >&2 90 | echo -- 91 | 92 | set -x 93 | 94 | test -f /run/.containerenv || die "No '/run/.containerenv' !?" 95 | 96 | HOME=$2; Z=$3; shift 3 97 | echo $HOME - $Z 98 | # note: HOME not exported.. 99 | 100 | TAR_OPTIONS='--no-same-owner' # hint: podman unshare ... if this does not do it 101 | export TAR_OPTIONS 102 | 103 | dn0=${0%/*} 104 | 105 | ls -l /mnt /media 106 | 107 | # zlib on centos6 is too old (1.2.3) 108 | zlib_lnk=http://prdownloads.sourceforge.net/libpng/zlib-1.2.11.tar.gz 109 | zlib_cks=c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 110 | 111 | xapian_lnk=https://oligarchy.co.uk/xapian/1.4.14/xapian-core-1.4.14.tar.xz 112 | xapian_cks=975a7ac018c9d34a15cc94a3ecc883204403469f748907e5c4c64d0aec2e4949 113 | 114 | pcre_lnk=https://ftp.pcre.org/pub/pcre/pcre-8.43.tar.gz 115 | pcre_cks=0b8e7465dc5e98c757cc3650a20a7843ee4c3edf50aaf60bb33fd879690d2c73 116 | 117 | # 2.57.1 works w/o meson/ninja (and has ./configure) but requires libmount 118 | #glib_lnk=http://ftp.gnome.org/pub/gnome/sources/glib/2.57/glib-2.57.1.tar.xz 119 | #glib_cks=d029e7c4536835f1f103472f7510332c28d58b9b7d6cd0e9f45c2653e670d9b4 120 | 121 | glib_lnk=http://ftp.gnome.org/pub/gnome/sources/glib/2.49/glib-2.49.4.tar.xz 122 | glib_cks=9e914f9d7ebb88f99f234a7633368a7c1133ea21b5cac9db2a33bc25f7a0e0d1 123 | 124 | gmime_lnk=https://download.gnome.org/sources/gmime/3.2/gmime-3.2.5.tar.xz 125 | gmime_cks=fb7556501f85c3bf3e65fdd82697cbc4fa4b55dccd33ad14239ce0197e78ba59 126 | 127 | 128 | dl_dir=$HOME/.local/share/nottoomuch-c6/dl 129 | 130 | : 0 : 131 | case $* in *' 0 '*) ## download missing source archives 132 | test -d "$dl_dir" || mkdir -p "$dl_dir" 133 | may_dl () { 134 | test -f "$dl_dir"/${1##*/} || { 135 | curl -Lo "$dl_dir"/${1##*/}.wip $1${2-} 136 | mv "$dl_dir"/${1##*/}.wip "$dl_dir"/${1##*/} 137 | } 138 | } 139 | may_dl $zlib_lnk '?download' 140 | may_dl $xapian_lnk 141 | may_dl $pcre_lnk 142 | may_dl $glib_lnk 143 | may_dl $gmime_lnk 144 | esac 145 | 146 | ipa=$HOME/.local/share/nottoomuch-c6/a 147 | ips=$HOME/.local/share/nottoomuch-c6/src 148 | test -d "$ipa" || mkdir -p "$ipa" 149 | test -d "$ips" || mkdir -p "$ips" 150 | 151 | 152 | : 1 : 153 | nob=t 154 | case $* in *' 1 '*) ## build needed libs 155 | chk_sha256 () { 156 | set -- "$1" "$2" `exec openssl sha256 < "$1"` 157 | test "$2" = "$4" || die "sha256 of '$1' is not expected" \ 158 | "expected: $2" "actual: $4" 159 | } 160 | nob= 161 | esac 162 | 163 | lnk_to_file () { 164 | eval set -- \$1 \$$1''_lnk 165 | a=${2##*/} 166 | test -f "$dl_dir"/$a 167 | eval $1_file="$dl_dir"/$a 168 | case $a in *.tar.gz) a=${a%.tar.gz} 169 | ;; *.tar.xz) a=${a%.tar.xz} 170 | esac 171 | eval $1_ver=\${a##*-} 172 | } 173 | 174 | lnk_to_file zlib 175 | 176 | test "$nob" || test -d $ipa/zlib-$zlib_ver || ( 177 | # false 178 | chk_sha256 "$zlib_file" $zlib_cks 179 | rm -rf $ips/zlib-$zlib_ver 180 | tar -C $ips -zxvf "$zlib_file" 181 | cd $ips/zlib-$zlib_ver 182 | ./configure --prefix=$ipa/zlib-$zlib_ver 183 | make install 184 | ) 185 | #export CPPFLAGS="-I$ipa/zlib-$zlib_ver/include" 186 | #export LDFLAGS="-L$ipa/zlib-$zlib_ver/lib" 187 | export CPATH=$ipa/zlib-$zlib_ver/include 188 | export LIBRARY_PATH=$ipa/zlib-$zlib_ver/lib 189 | 190 | export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$ipa/zlib-$zlib_ver/lib/pkgconfig 191 | 192 | lnk_to_file xapian 193 | 194 | test "$nob" || test -d $ipa/xapian-core-$xapian_ver || ( 195 | # false 196 | chk_sha256 "$xapian_file" $xapian_cks 197 | rm -rf $ips/xapian-core-$xapian_ver 198 | tar -C $ips -Jxvf "$xapian_file" 199 | cd $ips/xapian-core-$xapian_ver 200 | ./configure --prefix=$ipa/xapian-core-$xapian_ver 201 | make install 202 | ) 203 | # environment settings for xapian not needed yet 204 | 205 | lnk_to_file pcre 206 | 207 | test "$nob" || test -d $ipa/pcre-$pcre_ver || ( 208 | # false 209 | chk_sha256 "$pcre_file" $pcre_cks 210 | rm -rf $ips/pcre-$pcre_ver 211 | tar -C $ips -zxvf "$pcre_file" 212 | cd $ips/pcre-$pcre_ver 213 | ./configure --prefix=$ipa/pcre-$pcre_ver \ 214 | --enable-utf --enable-unicode-properties 215 | make install 216 | ) 217 | #export CPPFLAGS="$CPPFLAGS -I$ipa/pcre-$pcre_ver/include" 218 | #export CPATH=$CPATH:$ipa/pcre-$pcre_ver/include 219 | #export LDFLAGS="$LDFLAGS -L$ipa/pcre-$pcre_ver/lib" 220 | #export LIBRARY_PATH=$LIBRARY_PATH:$ipa/pcre-$pcre_ver/lib 221 | 222 | export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$ipa/pcre-$pcre_ver/lib/pkgconfig 223 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ipa/pcre-$pcre_ver/lib 224 | 225 | lnk_to_file glib 226 | 227 | test "$nob" || test -d $ipa/glib-$glib_ver || ( 228 | # false 229 | chk_sha256 "$glib_file" $glib_cks 230 | rm -rf $ips/glib-$glib_ver 231 | tar -C $ips -Jxvf "$glib_file" 232 | cd $ips/glib-$glib_ver 233 | ./configure --prefix=$ipa/glib-$glib_ver 234 | make install 235 | ) 236 | export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$ipa/glib-$glib_ver/lib/pkgconfig 237 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ipa/glib-$glib_ver/lib 238 | 239 | lnk_to_file gmime 240 | 241 | test "$nob" || test -d $ipa/gmime-$gmime_ver || ( 242 | # false 243 | chk_sha256 "$gmime_file" $gmime_cks 244 | rm -rf $ips/gmime-$gmime_ver 245 | tar -C $ips -Jxvf "$gmime_file" 246 | cd $ips/gmime-$gmime_ver 247 | ./configure --prefix=$ipa/gmime-$gmime_ver 248 | make install 249 | ) 250 | export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$ipa/gmime-$gmime_ver/lib/pkgconfig 251 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ipa/gmime-$gmime_ver/lib 252 | 253 | test "$nob" || ( 254 | cd $ipa 255 | # zero timestamps in ar(5) files. build reproducible if same --prefix 256 | find . -name '*.a' | xargs $dn0/ardet.pl # from ldpreload-ardet repo 257 | ) 258 | 259 | # xapian not needed until now... 260 | #export CPPFLAGS="$CPPFLAGS -I$ipa/xapian-core-$xapian_ver/include" 261 | #export CPATH=$CPATH:$ipa/xapian-core-$xapian_ver/include 262 | #export LDFLAGS="$LDFLAGS -L$ipa/xapian-core-$xapian_ver/lib" 263 | #export LIBRARY_PATH=$LIBRARY_PATH:$ipa/xapian-core-$xapian_ver/lib 264 | 265 | export PATH=$PATH:$ipa/xapian-core-$xapian_ver/bin 266 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ipa/xapian-core-$xapian_ver/lib 267 | 268 | # this ld library path setting was not needed until now 269 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ipa/zlib-$zlib_ver/lib 270 | 271 | 272 | # vvvvv better to run desired git am on command line when needed 273 | # : 2 : 274 | # case $* in *' 2 '*) ## patch notmuch (optional) (modifies notmuch-srcdir) 275 | # cd /mnt 276 | # gl5=`exec git --no-pager log -5 --oneline` # enough, due to rebases... 277 | # case $gl5 in *'hax: notmuch binary with embedded manpages'*) ;; *) 278 | # git am "$dn0"/02-hack-notmuch-with-embedded-manpages.patch 279 | # esac 280 | # #git am "$dn0"/01-date-local.patch # see: make-one-notmuch-el.pl 281 | # esac 282 | 283 | 284 | : 3 : 285 | case $* in *' 3 '*) ## build notmuch (in-tree) (remember '7' -- rpath) 286 | cd /mnt 287 | sed '/command .* gpgme-config/,/^else/ s/errors=/#errors=/' configure \ 288 | > hax-centos6-configure 289 | sh ./hax-centos6-configure 290 | #export CFLAGS="${CFLAGS:+$CFLAGS } -v" 291 | make #V=1 292 | #make test 293 | esac 294 | 295 | 296 | : 5 : 297 | case $* in *' 5 '*) ## try notmuch 298 | cd /mnt 299 | ldd notmuch 300 | ldd notmuch-shared 301 | ldd lib/libnotmuch.so.5 302 | :; : try ./notmuch --help on shell prompt below... ;: 303 | /bin/bash #/bin/zsh 304 | esac 305 | 306 | : 6 : 307 | case $* in *' 6 '*) ## package libs for dist: set rpaths... 308 | cd /mnt 309 | od=`TZ=$Z exec date +nmc6-deplibs-%Y%m%d-%H%M` 310 | mkdir $od 311 | dlibs=`ldd notmuch | sed -n '/nottoomuch-c6\/a/ s/=.*//p'` 312 | set +f 313 | for f in $dlibs; do cp -L $ipa/*/lib/$f $od/.; done 314 | cd $od 315 | # set RPATH/RUNPATH, some may not need buit... 316 | for f in *.so* 317 | do ldd $f | grep -q nottoomuch-c6/a || continue 318 | test "`patchelf --print-rpath $f`" || continue 319 | patchelf --set-rpath '$ORIGIN' $f 320 | done 321 | set -f 322 | cd .. 323 | tar zcvf $od.tar.gz $od 324 | esac 325 | 326 | : 7 : 327 | case $* in *' 7 '*) ## set rpath to notmuch(1) (not notmuch-shared (for now)) 328 | cd /mnt 329 | patchelf --set-rpath '$ORIGIN/../lib' notmuch 330 | esac 331 | 332 | : 8 : 333 | case $* in *' 8wip '*) ## build and package emacs (tested emacs 27.1) 334 | cd /mnt/emacs-[1-9]*[0.9] 335 | ./configure --prefix=$HOME/.local 336 | make 337 | make install DESTDIR=$PWD/tmp-dd 338 | ( cd tmp-dd/$HOME/.local && 339 | exec rm -rf include lib share/applications share/icons share/metainfo 340 | ) 341 | ver=`cd tmp-dd/$HOME/.local/share/emacs; echo [1-9]*` 342 | tar -C tmp-dd/$HOME -zcf emacs-$ver-centos6.tar.gz .local 343 | esac 344 | 345 | : 346 | : all done in container 347 | : 348 | 349 | 350 | # Local variables: 351 | # mode: shell-script 352 | # sh-basic-offset: 8 353 | # tab-width: 8 354 | # End: 355 | # vi: set sw=8 ts=8 356 | -------------------------------------------------------------------------------- /build/centos6/podman-notmuch-buildenv-centos6.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Author: Tomi Ollila -- too ät iki piste fi 4 | # 5 | # Copyright (c) 2019 Tomi Ollila 6 | # All rights reserved 7 | # 8 | # Created: Sun 20 Oct 2019 20:41:59 +0300 too 9 | # Last modified: Thu 20 May 2021 23:35:28 +0300 too 10 | 11 | # SPDX-License-Identifier: BSD-2-Clause 12 | 13 | case ${BASH_VERSION-} in *.*) set -o posix; shopt -s xpg_echo; esac 14 | case ${ZSH_VERSION-} in *.*) emulate ksh; esac 15 | 16 | set -euf # hint: sh -x thisfile [args] to trace execution 17 | 18 | die () { printf '%s\n' "$@"; exit 1; } >&2 19 | 20 | x () { printf '+ %s\n' "$*" >&2; "$@"; } 21 | 22 | tname=notmuch-buildenv-centos6 23 | 24 | if test "${1-}" != '--in-container--' 25 | then 26 | today=`exec date +%Y%m%d` 27 | test $# = 1 || 28 | die '' \ 29 | "Usage $0 '$today' -- i.e. today's date as the only arg" \ 30 | '' 'Note: may pull centos:6.10 from an external registry.' '' 31 | test "$1" = $today || die "'$1' != '$today'" 32 | 33 | if podman images -n --format='{{.Repository}}:{{.Tag}} {{.Created}}' | 34 | grep $tname:$1' ' 35 | then 36 | echo Target image exists. 37 | exit 0 38 | fi 39 | 40 | podman inspect centos:6.10 --format '{{.RepoTags}}' 41 | 42 | case $0 in /*) dn0=${0%/*} 43 | ;; */*/*) dn0=`cd "${0%/*}" && pwd` 44 | ;; ./*) dn0=$PWD 45 | ;; */*) dn0=`cd "${0%/*}" && pwd` 46 | ;; *) dn0=$PWD 47 | esac 48 | 49 | # remember in next container: --net=none \ 50 | x podman run -it --privileged -v "$dn0:/mnt" \ 51 | --tmpfs /tmp:rw,size=65536k,mode=1777 \ 52 | --name $tname-wip centos:6.10 \ 53 | /mnt/"${0##*/}" --in-container-- 54 | echo 'back in "host" environment...' 55 | 56 | x podman unshare sh -eufxc ' 57 | mp=`exec podman mount '"$tname"'-wip` 58 | ( cd "$mp"; rm -rfv run; exec mkdir -m 755 run ) 59 | ' 60 | x podman commit --change 'ENTRYPOINT=["/usr/libexec/entrypoint"]' \ 61 | --change 'CMD=/bin/bash' $tname-wip $tname:$1 62 | podman rm $tname-wip 63 | echo 64 | echo all done 65 | echo 66 | exit 0 67 | fi 68 | 69 | ### rest of this file is executed in container ### 70 | 71 | if test -f /.rerun 72 | then 73 | echo -- 74 | echo -- 'failure in previous execution -- starting "rescue" shell' 75 | echo -- 76 | exec /bin/bash 77 | fi 78 | :>/.rerun 79 | 80 | trap "{ set +x; } 2>/dev/null; echo; echo something failed 81 | echo ': execute ; podman start -ia $tname-wip ;: to investigate' 82 | echo" 0 83 | 84 | echo -- 85 | echo -- Executing in container -- xtrace now on -- >&2 86 | echo -- 87 | 88 | set -x 89 | 90 | test -f /run/.containerenv || die "No '/run/.containerenv' !?" 91 | 92 | yum -y install epel-release centos-release-scl 93 | 94 | more_for_emacs='ncurses-devel libxml2-devel gnutls-devel' 95 | 96 | yum -y install devtoolset-8-gcc-c++ sclo-git212-git rh-python36 zsh xz \ 97 | libtalloc-devel libffi-devel gettext-devel \ 98 | rh-python36-python-sphinx patchelf $more_for_emacs 99 | 100 | yum clean all 101 | rm -rf /var/lib/yum/yumdb 102 | 103 | ( 104 | set +f +u 105 | for f in /opt/rh/*/enable 106 | do test -f $f || continue 107 | source $f 108 | done 109 | set -f -u 110 | exec 3>&1 > entrypoint.c 111 | echo 'int putenv(char *string);' 112 | echo 'int execvp(const char *file, char ** argv);' 113 | echo 'int main(int argc, char ** argv) {' 114 | echo " putenv(\"PATH=$PATH\");" 115 | echo " putenv(\"MANPATH=$MANPATH\");" 116 | echo " putenv(\"INFOPATH=$INFOPATH\");" 117 | echo " putenv(\"LD_LIBRARY_PATH=$LD_LIBRARY_PATH\");" 118 | echo " putenv(\"PERL5LIB=$PERL5LIB\");" 119 | echo " putenv(\"XDG_DATA_DIRS=$XDG_DATA_DIRS\");" 120 | echo " putenv(\"PKG_CONFIG_PATH=$PKG_CONFIG_PATH\");" 121 | echo ' return execvp(argv[1], argv + 1);' 122 | echo '}' 123 | exec 1>&3 3>&- 124 | gcc -Wall -Wextra -O2 -o /usr/libexec/entrypoint entrypoint.c 125 | rm entrypoint.c 126 | ) 127 | 128 | trap - 0 129 | rm /.rerun 130 | : 131 | : all done in container 132 | : 133 | 134 | 135 | # Local variables: 136 | # mode: shell-script 137 | # sh-basic-offset: 8 138 | # tab-width: 8 139 | # End: 140 | # vi: set sw=8 ts=8 141 | -------------------------------------------------------------------------------- /build/centos6/podman-notmuch-buildwip-centos6.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Author: Tomi Ollila -- too ät iki piste fi 4 | # 5 | # Copyright (c) 2019 Tomi Ollila 6 | # All rights reserved 7 | # 8 | # Created: Sat 23 Nov 2019 20:11:23 +0200 too 9 | # Last modified: Thu 20 May 2021 23:35:41 +0300 too 10 | 11 | # SPDX-License-Identifier: Unlicense 12 | 13 | case ${BASH_VERSION-} in *.*) set -o posix; shopt -s xpg_echo; esac 14 | case ${ZSH_VERSION-} in *.*) emulate ksh; esac 15 | 16 | set -euf # hint: sh -x thisfile [args] to trace execution 17 | 18 | false || { # set to 'true' (or ': false') to enable 19 | printf ' %s\n' '' \ 20 | 'Disabled by default. See comment and use temporarily where useful.' '' 21 | exit 1 22 | Enable this script by changing "false" to "true" when testing and 23 | trying new content to be added to the "parent" container image 24 | {instead of fully rebuilding it every time}. Finally, copy newfound 25 | content to the "parent" container and disable this script. 26 | Dockerfile option is too limiting in many cases... 27 | } 28 | 29 | die () { printf '%s\n' "$@"; exit 1; } >&2 30 | 31 | x () { printf '+ %s\n' "$*" >&2; "$@"; } 32 | 33 | test $# -ne 0 || { 34 | exec >&2; echo 35 | echo "Usage: $0 prev-tag" 36 | echo 37 | echo ' prev-tag: TAG of notmuch-buildenv-centos6 container image' 38 | echo 39 | podman images notmuch-buildenv-centos6 40 | echo 41 | grep ' wip[0-9] '\' "$0" 42 | echo 43 | exit 1 44 | } 45 | 46 | tag=$1; shift 47 | 48 | ciname=notmuch-buildenv-centos6:$tag 49 | coname=notmuch-buildwip-centos6-$tag 50 | 51 | if test "${1-}" != '--in-container--' 52 | then 53 | test $# = 0 || die "'$*' extra args" 54 | case $tag 55 | in wip[1-8]) otag=wip$(( ${tag#wip} + 1 )) 56 | ;; wip*) die "'$tag': unsupported wip prefix" 57 | ;; *) otag=wip1 58 | esac 59 | 60 | podman inspect "$ciname" --format '{{.RepoTags}}' 61 | 62 | oname=${ciname%:*}:$otag 63 | if podman inspect "$oname" --format '{{.RepoTags}}' 64 | then die "'$oname' exists" 65 | fi 66 | 67 | case $0 in /*) dn0=${0%/*} 68 | ;; */*/*) dn0=`cd "${0%/*}" && pwd` 69 | ;; ./*) dn0=$PWD 70 | ;; */*) dn0=`cd "${0%/*}" && pwd` 71 | ;; *) dn0=$PWD 72 | esac 73 | 74 | x podman run --pull=never -it --privileged -v "$dn0:/mnt" \ 75 | --tmpfs /tmp:rw,size=65536k,mode=1777 \ 76 | --name "$coname" "$ciname" \ 77 | /mnt/"${0##*/}" "$tag" --in-container-- "$otag" 78 | echo 'back in "host" environment...' 79 | 80 | x podman unshare sh -eufxc ' 81 | mp=`exec podman mount '"$coname"'` 82 | ( cd "$mp"; rm -rfv run; exec mkdir -m 755 run ) 83 | ' 84 | x podman commit --change 'CMD=/bin/bash' "$coname" "$oname" 85 | podman rm "$coname" 86 | 87 | echo 88 | echo all done 89 | echo 90 | exit 0 91 | fi 92 | 93 | # rest of the file executed in container # 94 | 95 | if test -f /.rerun 96 | then 97 | echo -- 98 | echo -- 'failure in previous execution -- starting "rescue" shell' 99 | echo -- 100 | /bin/bash 101 | echo 'exit from "rescue" shell' 102 | exit 1 103 | fi 104 | :>/.rerun 105 | 106 | trap "{ set +x; } 2>/dev/null; echo; echo something failed 107 | echo ': execute ; podman start -ia $coname ;: to investigate' 108 | echo" 0 109 | 110 | echo -- 111 | echo -- Executing in container -- xtrace now on -- >&2 112 | echo -- 113 | 114 | d='nothing to do!' 115 | 116 | set -x 117 | 118 | test -f /run/.containerenv || die "No '/run/.containerenv' !?" 119 | 120 | # note: no yum cleanups, temp/wip work... 121 | 122 | if test "$2" = wip1 123 | then 124 | yum -y install patchelf rh-python36-python-sphinx # 2nd round... 125 | #yum -y install libtalloc-devel glib2-devel 126 | d= 127 | fi 128 | 129 | if test "$2" = wip2 130 | then 131 | false # already "merged", so not needed anymore 132 | yum -y install libffi-devel gettext-devel 133 | d= 134 | fi 135 | 136 | if test "$2" = wip3-not # was not useful, had to compile 137 | then 138 | yum -y install pcre-devel pcre2-devel 139 | d= 140 | fi 141 | 142 | test -z "$d" 143 | 144 | trap - 0 145 | rm /.rerun 146 | : 147 | : all done in container 148 | : 149 | 150 | 151 | # Local variables: 152 | # mode: shell-script 153 | # sh-basic-offset: 8 154 | # tab-width: 8 155 | # End: 156 | # vi: set sw=8 ts=8 157 | -------------------------------------------------------------------------------- /build/make-one-notmuch-el.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -*- mode: cperl; cperl-indent-level: 4 -*- 3 | 4 | use 5.6.1; 5 | use strict; 6 | use warnings; 7 | 8 | die " 9 | Usage: $0 notmuch-dir 10 | 11 | Example (entered in root of notmuch source): $0 . 12 | 13 | This script builds one notmuch .elc file which can be convenient 14 | for some purposes. This is provided in the hope that it will be 15 | useful, but this is not guaranteed to work always. As a developer 16 | tool you may have to fix it yourself. 17 | 18 | " unless @ARGV == 1; 19 | 20 | die $! unless chdir $ARGV[0]; 21 | 22 | sub needf ($$) 23 | { 24 | if ($_[0]) { 25 | die "'$ARGV[0]/$_[1]': no such file.\n" unless -f $_[1]; 26 | } 27 | else { 28 | die "'$ARGV[0]/$_[1]': no such directory.\n" unless -d $_[1]; 29 | } 30 | 31 | } 32 | 33 | needf 0, 'emacs'; 34 | needf 1, 'emacs/notmuch.el'; 35 | needf 1, 'emacs/Makefile.local'; 36 | needf 1, 'Makefile.local'; 37 | 38 | $_ = qx/git describe --abbrev=7 --match '[0-9.]*'/; 39 | chomp; s/_/~/; s/-/+/; s/-/~/; 40 | my $version = $_; 41 | 42 | open I, '<', 'emacs/notmuch-version.el.tmpl' or die $!; 43 | open O, '>', 'emacs/notmuch-version.el.tmp' or die $!; 44 | while () { 45 | s/%VERSION%/"$version"/; 46 | print O $_; 47 | } 48 | close O or die $!; 49 | close I or die $!; 50 | rename 'emacs/notmuch-version.el.tmp', 'emacs/notmuch-version.el'; 51 | symlink 'emacs/notmuch-version.el', 'version.stamp' unless -e 'version.stamp'; 52 | 53 | #system qw'make -d -r -f emacs/Makefile.local emacs/.eldeps 54 | system qw'make -r -f emacs/Makefile.local emacs/.eldeps 55 | WITH_EMACS=1 srcdir=. V=1 quiet=emacs'; 56 | #system qw'make emacs/.eldeps WITH_EMACS=1'; 57 | die unless $? == 0; 58 | 59 | my $eldeps = 'emacs/.eldeps'; 60 | open ELDEPS, '<', $eldeps or die "Opening $eldeps failed: $!\n"; 61 | 62 | my $sources = [ 'emacs/notmuch.el' ]; 63 | # hacks to make this work. perhaps investigate 'requires for the sources... 64 | my %deps = ( 'emacs/notmuch.el' => [ 'emacs/notmuch-version.el', 65 | 'emacs/notmuch-jump.el' ] ); 66 | 67 | # load dependencies. 68 | 69 | while () { 70 | die "Unexpected line $. in $eldeps\n" unless /^(\S+[.]el)c:\s+(\S+[.el])c$/; 71 | my $lref = $deps{$1}; 72 | if (defined $lref) { 73 | push @$lref, $2; 74 | } 75 | else { 76 | push @$sources, $1; 77 | $deps{$1} = [ $2 ]; 78 | } 79 | } 80 | close ELDEPS; 81 | 82 | my %seen; 83 | my @files; 84 | 85 | # resolve dependencies. 86 | 87 | sub dodep($); 88 | sub dodep($) 89 | { 90 | foreach (@{$_[0]}) { 91 | next if $seen{$_}; $seen{$_} = 1; 92 | my $name = $_; 93 | 94 | dodep $_ foreach $deps{$name}; # surprisingly this worked... 95 | 96 | push @files, $name; 97 | } 98 | } 99 | 100 | dodep $sources; 101 | 102 | # concatenate files to one. 103 | 104 | open ONE, '>', 'emacs/one-notmuch.el' 105 | or die "Opening emacs/one-notmuch.el for writing failed: $!\n"; 106 | 107 | print ONE ";; -*- lexical-binding: t -*-\n\n"; 108 | 109 | my $oline = 0; 110 | foreach (@files) { 111 | print "Reading $_ ($oline)...\n"; 112 | open I, '<', $_ or die $!; 113 | binmode I; 114 | while () { 115 | if ( /\((?:declare-function|autoload).*"notmuch/ ) { 116 | my $op = tr/(/(/ - tr/)/)/; 117 | while ($op > 0) { 118 | $_ = ; 119 | last if eof I; 120 | $op += tr/(/(/ - tr/)/)/; 121 | } 122 | next 123 | } 124 | s/(\(provide)\s+(.*)/(eval-and-compile $1 $2)/; 125 | 126 | # (require ...) is not "enough" when fn is defined in the same file... 127 | # ...so pick functions that need eval-and-compile... 128 | if ( / ^ \( defun \s+ (?: 129 | notmuch-show-toggle-process-crypto 130 | | notmuch-show-next-button 131 | | notmuch-show-previous-button 132 | | notmuch-help 133 | | notmuch-mua-new-mail 134 | | notmuch-jump-search 135 | | notmuch-show-forward-message 136 | | notmuch-show-reply-sender 137 | | notmuch-show-reply 138 | | notmuch-show-view-raw-message 139 | ) \s /x ) { 140 | # parenthesis count; let's hope no parens in docstrings messes this 141 | my $prc = tr/(/(/ - tr/)/)/; 142 | print ONE "(eval-and-compile\n$_"; 143 | while () { 144 | $prc += tr/(/(/ - tr/)/)/; 145 | print (ONE ")\n"), last if /^\s*$/ && $prc == 0; 146 | print (ONE $_); 147 | } 148 | } 149 | print ONE $_; 150 | $oline += 1; 151 | } 152 | close I; 153 | } 154 | print ONE " 155 | ;; Local Variables: 156 | ;; byte-compile-warnings: (not cl-functions) 157 | ;; End:\n"; 158 | close ONE; 159 | 160 | # build one-notmuch.elc 161 | 162 | print "Wrote ($oline lines):\n"; 163 | system qw'ls -l emacs/one-notmuch.el'; 164 | 165 | #my @cmdline = qw'emacs -batch -f batch-byte-compile emacs/one-notmuch.el'; 166 | #print "\nExecuting @cmdline\n"; 167 | #system @cmdline; 168 | 169 | system qw'make -r -f emacs/Makefile.local emacs/one-notmuch.elc 170 | WITH_EMACS=1 srcdir=. V=1 quiet=emacs'; 171 | die unless $? == 0; 172 | system qw'ls -l emacs/one-notmuch.elc'; 173 | exit $? 174 | -------------------------------------------------------------------------------- /build/podman-notmuch-buildenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # $ podman-notmuch-buildenv.sh $ 4 | # 5 | # Author: Tomi Ollila -- too ät iki piste fi 6 | # 7 | # Copyright (c) 2022 Tomi Ollila 8 | # All rights reserved 9 | # 10 | # Created: Wed 08 Apr 2020 22:04:22 EEST too 11 | # Last modified: Mon 19 Dec 2022 00:08:07 +0200 too 12 | 13 | # SPDX-License-Identifier: 0BSD 14 | 15 | case ${BASH_VERSION-} in *.*) set -o posix; shopt -s xpg_echo; esac 16 | case ${ZSH_VERSION-} in *.*) emulate ksh; esac 17 | 18 | set -euf # hint: sh -x thisfile [args] to trace execution 19 | 20 | # started this as buildah-.. but converted to podman-only solution so one 21 | # doesn't need to install buildah(1) cli tool (also) just for this purpose 22 | 23 | if test "${1-}" != '--in-container--' # --- this block is executed on host --- 24 | then 25 | from_images="debian:* ubuntu:* fedora:*" # more to add... 26 | 27 | die () { printf '%s\n' '' "$@" ''; exit 1; } >&2 28 | x () { printf '+ %s\n' "$*" >&2; "$@"; } 29 | x_exec () { printf '+ %s\n' "$*" >&2; exec "$@"; } 30 | 31 | test $# = 0 && die "Usage: $0 ( make | run ) ..." 32 | if test "$1" = run 33 | then 34 | case $PWD in $HOME/*) ;; *) 35 | die "\$PWD '$PWD'" "not under \$HOME '$HOME/' ..." 36 | esac 37 | test $# -ge 2 || { 38 | # the following worked w/ podman old or new enough... 39 | #podman images --format '{{.ID}} {{printf "%12.12s %8s" .CreatedSince .Size}} {{$e:=""}}{{range $e = split .Repository "/"}}{{end}}{{$e}}:{{.Tag}}' 40 | il=$(podman images --format '{{.ID}} {{printf "%12.12s %8s" .CreatedSince .Size}} //{{.Repository}}:{{.Tag}}' '*notmuch-buildenv-*') 41 | test "$il" || die \ 42 | "No notmuch buildenv container images created."\ 43 | '' "Run $0 make ... to create one." 44 | echo 45 | # v tested that works w/ gawk & mawk v # 46 | awk -v il="$il" 'BEGIN { gsub("//[^/]*/", "", il); print il }' 47 | die "Usage $0 $1 {container-image} [command [args]]" \ 48 | '' 'Use one of the container images listed above.'\ 49 | '' 'Note: $HOME/ is mounted in the started container...' '' `realpath "$0"` 50 | exit not reached 51 | } 52 | case $2 in *notmuch-buildenv-*) ;; *) 53 | die "'$2': not a 'notmuch-buildenv' image" 54 | esac 55 | shift 56 | x_exec podman run --pull=never --rm -it --privileged \ 57 | -v "$HOME:$HOME" -w "$PWD" "$@" 58 | exit not reached 59 | fi 60 | test "$1" = make || die "'$1': not 'run' nor 'make'" 61 | 62 | test $# = 3 || { 63 | today=`date +%Y%m%d` 64 | die "Usage: $0 $1 yyyymmdd from-image-name:tag" '' \ 65 | "Enter '$today' as 'yyyymmdd'." '' \ 66 | 'Known "from" images '"(replace '*' with container image tag):"\ 67 | '' " $from_images" '' \ 68 | "Creates: 'notmuch-buildenv-{from-image-name}-{tag}:yyyymmdd' container image." '' \ 69 | 'Note: all versions of the "from" images may not ne compatible.' \ 70 | '(as of 2022-06 tested with debian:11.3 and fedora:36 as from-image)' 71 | } 72 | case $3 in ??*:?*) ;; *) die "'$3' not '{name}:{tag}'" ;; esac 73 | n3=${3%:*} 74 | for fimg in $from_images 75 | do fimg=${fimg%:*} 76 | test "$fimg" = "$n3" || continue 77 | n3= 78 | break 79 | done 80 | test -z "$n3" || die "'$3': unknown \"from\" image" 81 | unset n2 fimg 82 | 83 | today=`date +%Y%m%d` 84 | 85 | test "$2" = $today || die "'$2' is not date of today ($today)" 86 | 87 | target_base=notmuch-buildenv-${3%:*}-${3##*:} # in one : we trust 88 | 89 | target_image=$target_base:$2 90 | if podman inspect -t image --format='{{.RepoTags}} {{.Created}}' \ 91 | "$target_image" 2>/dev/null 92 | then 93 | echo; echo "Target image '$target_image' exists."; echo 94 | exit 0 95 | fi 96 | podman inspect -t image --format='{{.Size}}' "$3" || { 97 | printf '\n"From" image missing;' 98 | echo " podman pull the image ($3) before continuing." 99 | echo 100 | exit 1 101 | } 102 | #echo "'$0'" 103 | case $0 in /*/../*) dn0=`cd "${0%/*}" && pwd` 104 | ;; /*) dn0=${0%/*} 105 | ;; */*/*) dn0=`cd "${0%/*}" && pwd` 106 | ;; ./*) dn0=$PWD 107 | ;; */*) dn0=`cd "${0%/*}" && pwd` 108 | ;; *) dn0=$PWD 109 | esac 110 | 111 | x podman run --pull=never -it --privileged -v "$dn0:/mnt:ro" \ 112 | --tmpfs /tmp:rw,size=65536k,mode=1777 --hostname=buildhost \ 113 | --name "$target_base-wip" "$3" \ 114 | /bin/sh /mnt/"${0##*/}" --in-container-- "$3" || 115 | die "Building '$target_image' failed..." '' Execute: \ 116 | " podman start -ia $target_base-wip ;: or" \ 117 | " podman logs $target_base-wip ;: to investigate." 118 | 119 | echo 'Back in "host" environment...' 120 | 121 | #podman logs $target_base-wip > "$target_image".plog 122 | 123 | # remove some noise in container, at least older podmans did that... 124 | x podman unshare sh -eufxc ' 125 | mp=`podman mount "'"$target_base-wip"'"` 126 | ( cd "$mp"; rm -rfv run; exec mkdir -m 755 run ) 127 | ' 128 | x podman commit --change 'CMD=/bin/bash' \ 129 | "$target_base-wip" "$target_image" 130 | podman rm "$target_base-wip" 131 | echo 132 | echo all done 133 | echo 134 | exit 135 | fi 136 | 137 | # --- rest of this file is executed in container --- # 138 | 139 | if test -f /.rerun 140 | then 141 | echo -- 142 | echo -- 'failure in previous execution -- starting "rescue" shell' 143 | echo -- 144 | exec /bin/bash 145 | fi 146 | :>/.rerun 147 | 148 | echo -- 149 | echo -- Executing in container -- xtrace now on -- >&2 150 | echo -- 151 | 152 | # executed using /bin/sh -- which may be e.g. dash(1) # 153 | 154 | die () { exit 1; } 155 | 156 | set -x 157 | 158 | test -f /run/.containerenv || die "No '/run/.containerenv' !?" 159 | 160 | case $2 in ( debian:* | ubuntu:* ) 161 | # libsexp-dev is in bullseye-backports... 162 | grep -q ' bullseye ' /etc/apt/sources.list && 163 | echo deb http://deb.debian.org/debian bullseye-backports main \ 164 | > /etc/apt/sources.list.d/backports.list 165 | export DEBIAN_FRONTEND=noninteractive 166 | apt-get update 167 | # note: no 'upgrade' -- pull new base image for that... 168 | # .travis.yml was helpful (but not complete)... 169 | apt-get install -y -q --no-install-recommends build-essential \ 170 | emacs-nox gdb git man dtach gpgsm strace less \ 171 | libxapian-dev libgmime-3.0-dev libtalloc-dev \ 172 | python3-sphinx python3-cffi python3-pytest \ 173 | python3-setuptools libpython3-all-dev ruby-dev 174 | apt-get install -y -q --no-install-recommends libsexp-dev || true 175 | 176 | apt-get -y autoremove 177 | apt-get -y clean 178 | rm -rf /var/lib/apt/lists/ 179 | rm /.rerun 180 | exit 181 | esac 182 | 183 | case $2 in ( fedora:* | centos:* ) # alma/rocky linux instead of centos ? 184 | 185 | # note: centos in progress -- gmime, dtach, python3-sphinx not found... 186 | #case $2 in fedora:*) fedora=true ;; *) fedora=false ;; esac 187 | 188 | #$fedora || dnf -v -y install epel-release 189 | : Note: dnf commands below may be long-lasting and silent... 190 | # 191 | dnf -v -y install make gcc gcc-c++ emacs-nox gdb git man \ 192 | dtach xapian-core-devel gmime30-devel libtalloc-devel \ 193 | zlib-devel python3-sphinx gnupg2-smime xz openssl \ 194 | redhat-rpm-config ruby-devel diffutils findutils strace \ 195 | sfsexp-devel 196 | # python3-devel python3-cffi python3-pytest 197 | 198 | dnf -v -y autoremove # note: removed findutils in centos 7.0 199 | dnf -v -y clean all 200 | set +f 201 | rm -rf /var/cache/yum /var/cache/dnf /var/lib/yum/* /var/lib/dnf/* 202 | set -f 203 | #rm -rf /var/lib/rpm/__db*; rpm --rebuilddb 204 | test -x /usr/bin/gpg || ln -s gpg2 /usr/bin/gpg # in centos 8 not in 7? 205 | rm /.rerun 206 | exit 207 | esac 208 | 209 | die "'$2': unknown..." 210 | 211 | 212 | # Local variables: 213 | # mode: shell-script 214 | # sh-basic-offset: 8 215 | # tab-width: 8 216 | # End: 217 | # vi: set sw=8 ts=8 218 | -------------------------------------------------------------------------------- /mboxviewfs/mboxviewfs-notmuchmail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -*- mode: shell-script; sh-basic-offset: 8; tab-width: 8 -*- 3 | # $ mboxviewfs-notmuchmail.sh $ 4 | # 5 | # Author: Tomi Ollila -- too ät iki piste fi 6 | # 7 | # Copyright (c) 2016 Tomi Ollila 8 | # All rights reserved 9 | # 10 | # Created: Thu 27 Oct 2016 20:46:23 EEST too 11 | # Last modified: Fri 29 Nov 2019 00:58:39 +0200 too 12 | 13 | case ~ in '~') echo "'~' does not expand. old /bin/sh?" >&2; exit 1; esac 14 | 15 | case ${BASH_VERSION-} in *.*) shopt -s xpg_echo; esac 16 | case ${ZSH_VERSION-} in *.*) emulate ksh; esac 17 | 18 | set -euf # hint: sh -x thisfile [args] to trace execution 19 | 20 | LANG=C LC_ALL=C; export LANG LC_ALL 21 | # LANG=en_IE.UTF-8 LC_ALL=en_IE.UTF-8; export LANG LC_ALL; unset LANGUAGE 22 | # PATH='/sbin:/usr/sbin:/bin:/usr/bin'; export PATH 23 | 24 | die () { printf '%s\n' "$@"; exit 1; } >&2 25 | 26 | x () { printf '+ %s\n' "$*" >&2; "$@"; } 27 | 28 | test "${XDG_RUNTIME_DIR-}" || die '' \ 29 | "'$0' requires 'XDG_RUNTIME_DIR' environment variable" \ 30 | "to be set to some existing directory. Subdirectory 'mboxviewfs-notmuch'" \ 31 | "will be created to that directory."\ 32 | "Enter 'XDG_RUNTIME_DIR=... $0' to continue." '' 33 | 34 | test -d "$XDG_RUNTIME_DIR" || die '' "Directory '${XDG_RUNTIME_DIR-}'"\ 35 | "(value of \$XDG_RUNTIME_DIR) does not exist." '' 36 | 37 | 38 | test $# = 1 || die '' "Usage: $0 f|m" '' ' f - wget new mail, then mount' \ 39 | ' m - just mount' '' 40 | 41 | case $1 in f) nofetch=false 42 | ;; m) nofetch=true 43 | ;; *) die "'$1': not 'f' nor 'm'" 44 | esac 45 | 46 | mountpoint=$XDG_RUNTIME_DIR/mboxviewfs-notmuch 47 | 48 | test -d "$mountpoint" || mkdir "$mountpoint" 49 | 50 | stat_rd=`exec stat -c %D "$XDG_RUNTIME_DIR"` 51 | stat_mp=`exec stat -c %D "$mountpoint"` 52 | 53 | test "$stat_rd" = "$stat_mp" || die '' "Directories '$XDG_RUNTIME_DIR'" \ 54 | "and '$mountpoint' are on" \ 55 | "different devices ($stat_rd and $stat_mp). So not (re!)mounting." 56 | 57 | case $0 in /*) fn0=$0 58 | ;; */*/*) fn0=`exec readlink -f "$0"` 59 | ;; ./*) fn0=$PWD/${0#??} 60 | ;; */*) fn0=`exec readlink -f "$0"` 61 | ;; *) fn0=$PWD/$0 62 | esac 63 | dn0=${fn0%/*} 64 | 65 | x cd "$dn0" 66 | test -f mboxviewfs.c || die "No 'mboxviewfs.c' in '$dn0'" 67 | newer1=`exec ls -t mboxviewfs.c mboxviewfs 2>/dev/null` || : 68 | 69 | case $newer1 in mboxviewfs.c*) # mboxviewfs.c is never (or no mboxviewfs) 70 | x sh mboxviewfs.c 71 | esac 72 | unset newer1 73 | 74 | $nofetch || x wget --continue https://notmuchmail.org/archives/notmuch.mbox 75 | 76 | x ./mboxviewfs notmuch.mbox "$mountpoint" 77 | 78 | maildir=`exec notmuch config get database.path` 79 | # double-check! 80 | test -d "$maildir" || die "surprisingly '$maildir' is not a directory" 81 | lndst=$maildir/mboxviewfs-notmuch 82 | test -d "$lndst" || mkdir "$lndst" 83 | 84 | cd $mountpoint 85 | set +f 86 | for d in 2???-[01][0-9] 87 | do 88 | test -d "$lndst"/$d && 89 | test "$mountpoint"/$d -ef "$lndst"/$d && continue || : 90 | test ! -h "$lndst"/$d || rm "$lndst"/$d 91 | test ! -e "$lndst"/$d || die "'$lndst/$d' exists but is not symlink" 92 | x ln -s "$mountpoint"/$d "$lndst"/$d 93 | done 94 | echo visit $mountpoint 95 | echo fusermount -u $mountpoint ';:' when done 96 | -------------------------------------------------------------------------------- /mboxviewfs/mboxviewfs.c: -------------------------------------------------------------------------------- 1 | #if 0 /* -*- mode: c; c-file-style: "stroustrup"; tab-width: 8; -*- 2 | set -euf; trg=${0##*''/}; trg=${trg%.c}; test ! -e "$trg" || rm "$trg" 3 | WARN="-Wall -Wstrict-prototypes -Winit-self -Wformat=2" # -pedantic 4 | WARN="$WARN -Wcast-align -Wpointer-arith " # -Wfloat-equal #-Werror 5 | WARN="$WARN -Wextra -Wwrite-strings -Wcast-qual -Wshadow" # -Wconversion 6 | WARN="$WARN -Wmissing-include-dirs -Wundef -Wbad-function-cast -Wlogical-op" 7 | WARN="$WARN -Waggregate-return -Wold-style-definition" 8 | WARN="$WARN -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls" 9 | WARN="$WARN -Wnested-externs -Winline -Wvla -Woverlength-strings -Wpadded" 10 | #FLAGS=`{ pkg-config --cflags --libs fuse || kill $$;} | sed 's/-I/-isystem '/g` 11 | FLAGS=`exec pkg-config --cflags --libs fuse` 12 | FLAGS=`exec awk -vf="$FLAGS" 'BEGIN { gsub("-I", "-isystem ", f); print f }'` 13 | case ${1-} in '') set x -O2; shift; esac 14 | #case ${1-} in '') set x -ggdb; shift; esac 15 | #set -x; exec ${CC:-gcc} -std=c99 $WARN $FLAGS "$@" -o "$trg" "$0" 16 | set -x; exec ${CC:-gcc} -std=c99 $WARN "$@" -o "$trg" "$0" $FLAGS 17 | exit $? 18 | # Note: $FLAGS last to avoid "undefined reference to `fuse_main_real'" 19 | */ 20 | #endif 21 | /* 22 | * $ mboxviewfs.c 1.2 2017-06-04 $ 23 | * 24 | * Author: Tomi Ollila -- too ät iki piste fi 25 | * 26 | * Copyright (c) 2014 Tomi Ollila 27 | * All rights reserved 28 | * 29 | * Created: Sun Oct 5 10:45:49 2014 +0300 too 30 | * Last modified: Sun 04 Jun 2017 13:04:13 +0300 too 31 | */ 32 | 33 | /* LICENSE: 2-clause BSD license ("Simplified BSD License"): 34 | 35 | Redistribution and use in source and binary forms, with or without 36 | modification, are permitted provided that the following conditions 37 | are met: 38 | 39 | 1. Redistributions of source code must retain the above copyright 40 | notice, this list of conditions and the following disclaimer. 41 | 42 | 2. Redistributions in binary form must reproduce the above copyright 43 | notice, this list of conditions and the following disclaimer in the 44 | documentation and/or other materials provided with the distribution. 45 | 46 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 47 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 48 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 49 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 50 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 51 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 52 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 53 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 54 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 55 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 56 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 57 | */ 58 | 59 | // dir/file search routines got too hairy in the course of evolution... ;) // 60 | // i.e. that part to be restructured in case there is much more to be done // 61 | 62 | //#define _XOPEN_SOURCE // for strptime 63 | #define _XOPEN_SOURCE 600 // ... for strptime and timerspec when -std=c99 64 | 65 | #include 66 | #include 67 | #include 68 | #include // for strncasecmp 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | 77 | #include 78 | #include 79 | 80 | #include 81 | 82 | #define FUSE_USE_VERSION 26 83 | #include 84 | 85 | #define null ((void*)0) 86 | enum { false = 0, true = 1 }; 87 | 88 | // (variable) block begin/end -- explicit liveness... 89 | #define BB { 90 | #define BE } 91 | 92 | #define DEBUG 0 93 | #if DEBUG 94 | #define d1(format, ...) \ 95 | fprintf(stderr, "%d:%s " format "\n", __LINE__, __func__, __VA_ARGS__) 96 | //#define d0 d1 97 | #define d0(format, ...) do { } while (0) 98 | #define d1x(x) do { x; } while (0) 99 | #define d0x(x) do {} while (0) 100 | #else 101 | #define d1(format, ...) do {} while (0) 102 | #define d0(format, ...) do {} while (0) 103 | #define d1x(x) do {} while (0) 104 | #define d0x(x) do {} while (0) 105 | #endif 106 | #define da(format, ...) \ 107 | fprintf(stderr, "%d:%s " format "\n", __LINE__, __func__, __VA_ARGS__) 108 | 109 | #define xassert_eq(a, b) do { if ((a) != (b)) { fprintf(stderr, #a "(%td) != " #b "(%td)\n", (intptr_t)(a), (intptr_t)(b)); exit(1); }} while (0) 110 | 111 | // set to 0 to and go through all warnings whether all vars are really unused // 112 | #if 1 113 | #define UU(x) (void)x 114 | #else 115 | #define UU(x) (void)x; x 116 | #endif 117 | 118 | typedef struct { 119 | off_t offset; // offset in mbox file 120 | int16_t year; 121 | int8_t mon; 122 | int8_t mday; 123 | int8_t hour; 124 | int8_t min; 125 | int8_t sec; 126 | int8_t xqho; // +- quarter-hours (900secs) from utc -- for file [amc]time:s 127 | } FileEntry; 128 | 129 | typedef struct { 130 | size_t index; // index in yearmona "array" 131 | #if !defined (__SIZEOF_SIZE_T__) || __SIZEOF_SIZE_T__ != 8 132 | uint32_t opportunistic_pad; // off_t expected to be 64bit in size 133 | #endif 134 | int16_t year; 135 | int8_t mon; 136 | int8_t mday; 137 | int8_t hour; 138 | int8_t min; 139 | int8_t sec; 140 | int8_t xqho; // +- quarter-hours (900secs) from utc -- for file [amc]time:s 141 | } DirEntry; 142 | 143 | 144 | const struct { 145 | mode_t dirmode; 146 | mode_t filemode; 147 | time_t time_t_small; // small enough, risks & warnings when shifting 1 more 148 | } C = { 149 | .dirmode = S_IFDIR | 0555, 150 | .filemode = S_IFREG | 0444, 151 | .time_t_small = -((time_t)1 << (8 * sizeof (time_t) - 2)), 152 | }; 153 | 154 | struct { 155 | const char * prgname; 156 | int fd; 157 | int32_t pad1_unused; 158 | off_t mboxsize; 159 | size_t mailc; 160 | FileEntry * maila; 161 | size_t * maili; 162 | unsigned yearmonc; 163 | int32_t pad2_unused; 164 | DirEntry * yearmona; 165 | unsigned lastdirindex; 166 | int32_t pad3_unused; 167 | time_t ht; 168 | uid_t uid; 169 | gid_t gid; 170 | } G; 171 | 172 | static void init_G(const char * prgname) 173 | { 174 | G.prgname = prgname; 175 | 176 | // compiler optimizes these asserts (of constants) away in case succeeds // 177 | // currently, there is one DirEntry -> FileEntry typecast in the code... 178 | xassert_eq(&((FileEntry *)0)->year, &((DirEntry *)0)->year); 179 | xassert_eq(&((FileEntry *)0)->mon, &((DirEntry *)0)->mon); 180 | xassert_eq(&((FileEntry *)0)->mday, &((DirEntry *)0)->mday); 181 | xassert_eq(&((FileEntry *)0)->hour, &((DirEntry *)0)->hour); 182 | xassert_eq(&((FileEntry *)0)->min, &((DirEntry *)0)->min); 183 | xassert_eq(&((FileEntry *)0)->sec, &((DirEntry *)0)->sec); 184 | xassert_eq(&((FileEntry *)0)->xqho, &((DirEntry *)0)->xqho); 185 | 186 | d0("sizeof (time_t): %lu", sizeof (time_t)); 187 | #if 0 // XXX 188 | xassert_eq(sizeof (time_t), 8); 189 | xassert_eq(sizeof (long), 8); 190 | #endif 191 | 192 | G.uid = getuid(); 193 | G.gid = getgid(); 194 | } 195 | 196 | // this is modeled after my_timegm() (minor edits + no tm_isdst use) in 197 | // *** http://www.catb.org/esr/time-programming/ *** 198 | static inline time_t xtimegm(struct tm * tm) 199 | { 200 | static const int cumdays[12] = {0,31,59,90,120,151,181,212,243,273,304,334}; 201 | 202 | long year = 1900 + tm->tm_year + tm->tm_mon / 12; 203 | time_t result = (year - 1970) * 365 + cumdays[tm->tm_mon % 12]; 204 | result += year / 4 - year / 100 + year / 400 - 477; // 1970 / 4 - ... = 477 205 | if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) 206 | && (tm->tm_mon % 12) < 2) 207 | result--; 208 | result += tm->tm_mday - 1; result *= 24; 209 | result += tm->tm_hour; result *= 60; 210 | result += tm->tm_min; result *= 60; 211 | result += tm->tm_sec; 212 | //if (tm->tm_isdst == 1) result -= 3600; 213 | 214 | d0("%d-%02d-%02d %02d:%02d:%02d -- %jd", tm->tm_year + 1900, tm->tm_mon + 1, 215 | tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, result); 216 | 217 | return result; 218 | } 219 | 220 | static time_t fetime(FileEntry * fe) 221 | { 222 | struct tm tm = { 223 | .tm_year = fe->year, .tm_mon = fe->mon, .tm_mday = fe->mday, 224 | .tm_hour = fe->hour, .tm_min = fe->min, .tm_sec = fe->sec, 225 | }; 226 | d0("%d %d", fe->xqho, fe->xqho * 900); 227 | #if defined (MKTIME_ME_HARDER) && MKTIME_ME_HARDER 228 | (void)mktime(&tm); 229 | d0("%d, %d", tm.tm_year + 1900, tm.tm_yday); 230 | return (time_t)(tm.tm_year - 70) * 31536000 231 | // + ((tm.tm_year-101)/4 - (tm.tm_year-101)/100 + (tm.tm_year-101)/400 + 8) 232 | + ((tm.tm_year+1899)/4-(tm.tm_year+1899)/100+(tm.tm_year+1899)/400-477) 233 | * 86400 + tm.tm_yday * 86400 234 | + tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec - tm.tm_isdst * 3600 235 | - fe->xqho * 900; 236 | #else 237 | return xtimegm(&tm) - fe->xqho * 900; 238 | //return timegm(&tm) - fe->xqho * 900; 239 | #endif 240 | } 241 | 242 | static DirEntry * finddir(int16_t year, int8_t mon) 243 | { 244 | d1("(%d, %d)", year + 1900, mon + 1); 245 | BB; 246 | // remember that this block needs to be thread-safe // 247 | unsigned o = G.lastdirindex; 248 | FileEntry * fe = &G.maila[G.maili[G.yearmona[o].index]]; 249 | d1("(%u) %d,%d", o, fe->year + 1900, fe->mon + 1); 250 | if (fe->year == year && fe->mon == mon) 251 | return &G.yearmona[o]; 252 | // often it is also potentially next one // 253 | // note: for this an extra item has been added to the yearmona array. 254 | fe = &G.maila[G.maili[G.yearmona[o + 1].index]]; 255 | d1("(%u) %d,%d", o+1, fe->year + 1900, fe->mon + 1); 256 | if (fe->year == year && fe->mon == mon) { 257 | G.lastdirindex = o + 1; 258 | return &G.yearmona[o + 1]; 259 | } 260 | BE; 261 | 262 | // binary search // 263 | unsigned min = 0, max = G.yearmonc; 264 | do { 265 | int o = (max + min) / 2; 266 | FileEntry * fe = &G.maila[G.maili[G.yearmona[o].index]]; 267 | d1("%d -- %d %d - %d %d", o, year, mon, fe->year, fe->mon); 268 | if (fe->year < year || (fe->year == year && fe->mon < mon)) { 269 | min = o + 1; 270 | } 271 | else if (fe->year > year || (fe->year == year && fe->mon > mon)) { 272 | max = o - 1; 273 | } 274 | else { 275 | G.lastdirindex = o; 276 | return &G.yearmona[o]; 277 | } 278 | } while (max >= min); 279 | 280 | d1("(%d, %d) -- not found", year, mon); 281 | return null; 282 | } 283 | 284 | static inline FileEntry * findfile(const char * path) 285 | { 286 | size_t i = 0; 287 | while (1) { 288 | char c = *++path; 289 | if (c >= '0' && c <= '9') 290 | i = (i << 4) + c - '0'; 291 | else if (c >= 'a' && c <= 'f') 292 | i = (i << 4) + c - 'a' + 10; 293 | else if (c == '\0') 294 | break; 295 | else 296 | return null; 297 | } 298 | if (i >= G.mailc) 299 | return null; 300 | 301 | return &G.maila[i]; 302 | } 303 | 304 | #if 0 305 | int xisdigit(int c) { d1("%c", c); return (c >= '0' && c <= '9'); } 306 | #undef isdigit 307 | #define isdigit xisdigit 308 | #endif 309 | 310 | #define PASS_YYYY_MM_OR_RETURN_ENOENT(path) \ 311 | if (! isdigit(*path++) || ! isdigit(*path++) || ! isdigit(*path++) \ 312 | || ! isdigit(*path++) \ 313 | || *path++ != '-' || ! isdigit(*path++) || ! isdigit(*path++)) \ 314 | return -ENOENT 315 | 316 | static int mbox_getattr(const char * path, struct stat * st) 317 | { 318 | d0("(\"%s\", %p)", path, (void*)st); 319 | 320 | // consistency check // 321 | if (*path++ != '/') return -ENOENT; 322 | 323 | st->st_uid = G.uid; 324 | st->st_gid = G.gid; 325 | 326 | if (*path == '\0') { // root dir 327 | st->st_atime = st->st_mtime = st->st_ctime = G.ht; 328 | st->st_nlink = G.yearmonc + 2; 329 | st->st_mode = C.dirmode; 330 | st->st_size = 4096; 331 | st->st_ino = 0x3758; 332 | return 0; 333 | } 334 | // pass yyyy-mm 335 | PASS_YYYY_MM_OR_RETURN_ENOENT(path); 336 | 337 | // directory? 338 | if (*path == '\0') { 339 | int16_t year = atoi(path - 7) - 1900; 340 | int8_t mon = atoi(path - 2) - 1; 341 | DirEntry * de = finddir(year, mon); 342 | if (de == null) 343 | return -ENOENT; 344 | st->st_atime = st->st_mtime = st->st_ctime = fetime((FileEntry *)de); 345 | st->st_nlink = 2; 346 | st->st_mode = C.dirmode; 347 | st->st_size = 4096; 348 | st->st_ino = (year + 1900) * 100 + mon + 1; 349 | return 0; 350 | } 351 | if (*path == '/') { 352 | size_t i = 0; 353 | while (1) { 354 | char c = *++path; 355 | if (c >= '0' && c <= '9') 356 | i = (i << 4) + c - '0'; 357 | else if (c >= 'a' && c <= 'f') 358 | i = (i << 4) + c - 'a' + 10; 359 | else if (c == '\0') 360 | break; 361 | else 362 | return -ENOENT; 363 | } 364 | if (i >= G.mailc) 365 | return -ENOENT; 366 | FileEntry * fe = &G.maila[i]; 367 | st->st_atime = st->st_mtime = st->st_ctime = fetime(fe); 368 | st->st_nlink = 1; 369 | st->st_mode = C.filemode; 370 | st->st_size = fe[1].offset - fe[0].offset; 371 | st->st_ino = 1e6 + i; 372 | return 0; 373 | } 374 | return -ENOENT; 375 | } 376 | 377 | static int mbox_readdir(const char * path, void * buf, fuse_fill_dir_t filler, 378 | off_t offset, struct fuse_file_info * fi) 379 | { 380 | d1("(\"%s\", %p, %ld)", path, buf, offset); 381 | 382 | UU(offset); 383 | UU(fi); 384 | 385 | // consistency check // 386 | if (*path++ != '/') return -ENOENT; 387 | 388 | if (*path == '\0') { // root dir 389 | filler(buf, ".", null, 0); 390 | filler(buf, "..", null, 0); 391 | for (unsigned i = 0; i < G.yearmonc; i++) { 392 | char ym[12]; 393 | FileEntry * fe = &G.maila[G.maili[G.yearmona[i].index]]; 394 | snprintf(ym, sizeof ym, "%d-%02d", fe->year + 1900, fe->mon + 1); 395 | d0("%s", ym); 396 | filler(buf, ym, null, 0); 397 | } 398 | return 0; 399 | } 400 | // pass yyyy-mm 401 | PASS_YYYY_MM_OR_RETURN_ENOENT(path); 402 | 403 | if (*path == '\0') { 404 | int16_t year = atoi(path - 7) - 1900; 405 | int8_t mon = atoi(path - 2) - 1; 406 | DirEntry * de = finddir(year, mon); 407 | if (de == null) 408 | return -ENOENT; 409 | filler(buf, ".", null, 0); 410 | filler(buf, "..", null, 0); 411 | for (size_t i = de->index; i < G.mailc; i++) { 412 | size_t fn = G.maili[i]; 413 | FileEntry * fe = &G.maila[fn]; 414 | d1("i: %ld fn: %ld, fe->year %d year %d fe->mon %d mon %d", 415 | i, fn, fe->year, year, fe->mon, mon); 416 | if (fe->year != year || fe->mon != mon) 417 | break; 418 | char fname[20]; 419 | #if !defined (__SIZEOF_SIZE_T__) || __SIZEOF_SIZE_T__ != 8 420 | #warning 32 bit system may have problems w/ emails Dated after 2038-01-14... 421 | #else 422 | if (fn >= ((size_t)1 << 32)) 423 | snprintf(fname, sizeof fname, "%016lx", (uint64_t)fn); 424 | else 425 | #endif 426 | snprintf(fname, sizeof fname, "%08x", (uint32_t)fn); 427 | filler(buf, fname, null, 0); 428 | } 429 | return 0; 430 | } 431 | return -ENOENT; 432 | } 433 | 434 | static int mbox_open(const char * path, struct fuse_file_info * fi) 435 | { 436 | UU(fi); 437 | d1("(\"%s\")", path); 438 | 439 | // consistency check // 440 | if (*path++ != '/') return -ENOENT; 441 | 442 | // pass yyyy-mm 443 | PASS_YYYY_MM_OR_RETURN_ENOENT(path); 444 | 445 | FileEntry * fe = findfile(path); 446 | if (fe == null) return -ENOENT; 447 | 448 | return 0; 449 | } 450 | 451 | static int mbox_read(const char * path, char * buf, size_t size, off_t offset, 452 | struct fuse_file_info * fi) 453 | { 454 | UU(fi); 455 | d1("(\"%s\", %d, %ld)", path, (int)size, offset); 456 | 457 | // consistency check // 458 | if (*path++ != '/') return -ENOENT; 459 | 460 | // pass yyyy-mm 461 | PASS_YYYY_MM_OR_RETURN_ENOENT(path); 462 | 463 | FileEntry * fe = findfile(path); 464 | if (fe == null) return -ENOENT; 465 | size_t fs = fe[1].offset - fe[0].offset; 466 | if (offset < 0 || offset > (off_t)fs) 467 | return -EINVAL; 468 | if (size + offset > fs) 469 | size = fs - offset; 470 | if (lseek(G.fd, fe[0].offset + offset, SEEK_SET) < 0) 471 | return -errno; 472 | ssize_t rv = read(G.fd, buf, size); 473 | if (rv >= 0) 474 | return rv; 475 | return -errno; 476 | } 477 | 478 | static int mbox_statfs(const char * path, struct statvfs * stvfs) 479 | { 480 | UU(path); 481 | d1("(\"%s\")", path); 482 | 483 | stvfs->f_bsize = 4096; 484 | stvfs->f_blocks = (G.mboxsize + 4095) / 4096; // *this* is needed for df(1) 485 | stvfs->f_bfree = 0; 486 | stvfs->f_bavail = 0; 487 | stvfs->f_files = G.mailc + G.yearmonc; 488 | stvfs->f_ffree = 0; 489 | //stvfs->f_favail = 0; 490 | //stvfs->f_flag = ST_RDONLY; 491 | stvfs->f_namemax = 32; 492 | 493 | return 0; 494 | } 495 | 496 | static struct fuse_operations mbox_oper = { 497 | .getattr = mbox_getattr, 498 | .readdir = mbox_readdir, 499 | .open = mbox_open, 500 | .read = mbox_read, 501 | .statfs = mbox_statfs 502 | }; 503 | 504 | const uint8_t ESTR[] = { 128, 0 }; 505 | 506 | #if defined(__GNUC__) && __GNUC__ >= 4 507 | #define ATTRIBUTE(a) __attribute__ ((a)) 508 | #else 509 | #define ATTRIBUTE(a) 510 | #endif 511 | 512 | static void ATTRIBUTE(sentinel) ATTRIBUTE(noreturn) 513 | diev(const char * str, ...) 514 | { 515 | struct iovec iov[16]; 516 | va_list ap; 517 | int errnum = errno; 518 | 519 | va_start(ap, str); 520 | *(const char **)&(iov[0].iov_base) = str; 521 | iov[0].iov_len = strlen(str); 522 | 523 | int i = 1; 524 | for (char * s = va_arg(ap, char *); s; s = va_arg(ap, char *)) { 525 | if (i == sizeof iov / sizeof iov[0]) 526 | break; 527 | iov[i].iov_base = s; 528 | iov[i].iov_len = strlen(s); 529 | i++; 530 | } 531 | if (iov[i-1].iov_len == 1 && ((uint8_t *)(iov[i-1].iov_base))[0] == 128) { 532 | iov[i-1].iov_base = strerror(errnum); 533 | iov[i-1].iov_len = strlen(iov[i-1].iov_base); 534 | } 535 | /* for writev(), iov[n].iov_base is const */ 536 | *(const char **)&(iov[i].iov_base) = ".\n"; 537 | iov[i].iov_len = 2; 538 | i++; 539 | #if (__GNUC__ >= 4 && ! (defined (__clang__) && __clang__) ) 540 | { ssize_t __i = writev(2, iov, i); (void)(__i = __i); } 541 | #else 542 | (void)writev(2, iov, i); 543 | #endif 544 | exit(1); 545 | } 546 | 547 | static int scan_tzo(const char * p) 548 | { 549 | // up to +- 29h, 99m ;) // 550 | int hh = p[1] - '0'; if (hh < 0 || hh > 2) return 0; 551 | int hl = p[2] - '0'; if (hl < 0 || hl > 9) return 0; 552 | int mh = p[3] - '0'; if (mh < 0 || mh > 9) return 0; 553 | int ml = p[4] - '0'; if (ml < 0 || ml > 9) return 0; 554 | if (p[5] != '\0' && ! isspace(p[5])) return 0; 555 | d1("(%.5s): %d %d %d %d", p, hh, hl, mh, ml); 556 | return (p[0] == '-') ? 557 | -((hh * 10 + hl) * 3600 + (mh * 10 + ml) * 60) : 558 | +((hh * 10 + hl) * 3600 + (mh * 10 + ml) * 60) ; 559 | } 560 | 561 | static inline void skipnonspace(const char ** p) 562 | { 563 | while (**p && !isspace(**p)) (*p)++; 564 | } 565 | 566 | // last resort, best heuristics w/o error checking 567 | static int last_resort_get_tm(const char * string, struct tm * tm) 568 | { 569 | // for (;; while (*string && !isspace(*string)) string++)) { 570 | for (;; skipnonspace(&string)) { 571 | while (isspace(*string)) string++; 572 | if (*string == '\0') 573 | break; 574 | unsigned hour, min, sec; 575 | if (sscanf(string, "%2u:%2u:%2u", &hour, &min, &sec) == 3) { 576 | tm->tm_hour = hour; tm->tm_min = min; tm->tm_sec = sec; 577 | continue; 578 | } 579 | if (*string == '+' || *string == '-') { 580 | tm->tm_yday = scan_tzo(string); // yday used as storage location // 581 | continue; 582 | } 583 | 584 | int i = atoi(string); 585 | if (i > 0) { 586 | d1("--- %d ---", i); 587 | // Date info may have tm y2k bug -- therefore somewhat funny 588 | // heuristic is close to when 32-bit unsigned time_t wraps... 589 | // perl -le 'print scalar localtime 1 << 32' -> 2106... 590 | if (i >= 200) { tm->tm_year = i - 1900; continue; } 591 | if (i > 31) { tm->tm_year = i; continue; } 592 | tm->tm_mday = i; continue; 593 | } 594 | int mon; 595 | switch (string[0]) { 596 | case 'J': 597 | if (strncasecmp(string, "Jan", 3) == 0) { mon = 0; break; } 598 | if (strncasecmp(string, "Jun", 3) == 0) { mon = 5; break; } 599 | if (strncasecmp(string, "Jul", 3) == 0) { mon = 6; break; } 600 | continue; 601 | case 'F': 602 | if (strncasecmp(string, "Feb", 3) == 0) { mon = 1; break; } 603 | continue; 604 | case 'M': 605 | if (strncasecmp(string, "Mar", 3) == 0) { mon = 2; break; } 606 | if (strncasecmp(string, "May", 3) == 0) { mon = 4; break; } 607 | continue; 608 | case 'A': 609 | if (strncasecmp(string, "Apr", 3) == 0) { mon = 3; break; } 610 | if (strncasecmp(string, "Aug", 3) == 0) { mon = 7; break; } 611 | continue; 612 | case 'S': 613 | if (strncasecmp(string, "Sep", 3) == 0) { mon = 8; break; } 614 | continue; 615 | case 'O': 616 | if (strncasecmp(string, "Oct", 3) == 0) { mon = 9; break; } 617 | continue; 618 | case 'N': 619 | if (strncasecmp(string, "Nov", 3) == 0) { mon = 10; break; } 620 | continue; 621 | case 'D': 622 | if (strncasecmp(string, "Dec", 3) == 0) { mon = 11; break; } 623 | default: 624 | continue; 625 | } 626 | if (! isalpha(string[3])) tm->tm_mon = mon; 627 | } 628 | d1("%d %d %d %d %d %d", tm->tm_sec, tm->tm_min, tm->tm_hour, 629 | tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900); 630 | 631 | if (tm->tm_mday > 0 && tm->tm_mon >= 0 && tm->tm_year >= 0 632 | && tm->tm_sec >= 0) 633 | return true; 634 | //tm->tm_mday = 0; 635 | return false; 636 | } 637 | 638 | static int _gettm(const char * string, struct tm * tm) 639 | { 640 | const char * p = strptime(string, "%a, %d %b %Y %T", tm); 641 | if (p == null) 642 | return last_resort_get_tm(string, tm); 643 | d1("%d %d %d %d %d %d", tm->tm_sec, tm->tm_min, tm->tm_hour, 644 | tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900); 645 | while (isspace(*p)) p++; 646 | if (*p == '+' || *p == '-') 647 | tm->tm_yday = scan_tzo(p); // yday used as storage location // 648 | else 649 | tm->tm_yday = 0; 650 | return true; 651 | } 652 | 653 | /* ************************* lineread.c ************************* */ 654 | /* 655 | * lineread.c - functions to read lines from fd:s efficiently 656 | * 657 | * Created: Mon Jan 14 06:45:00 1991 too 658 | */ 659 | 660 | struct lineread 661 | { 662 | char * currp; /* current scan point in buffer */ 663 | char * endp; /* pointer of last read character in buffer */ 664 | char * startp; /* pointer to start of output */ 665 | char * sizep; /* pointer to the end of read buffer */ 666 | int fd; /* input file descriptor */ 667 | char selected; /* has caller done select()/poll() or does he care */ 668 | char line_completed;/* line completion in LineRead */ 669 | uint8_t saved; /* saved char in LineRead */ 670 | uint8_t pad_unused; 671 | char data[32768]; /* the data buffer... */ 672 | }; 673 | typedef struct lineread LineRead; 674 | 675 | static int lineread(LineRead * lr, char ** ptr) 676 | { 677 | int i; 678 | 679 | if (lr->currp == lr->endp) 680 | 681 | if (lr->selected) /* user called select() (or wants to block) */ 682 | { 683 | if (lr->line_completed) 684 | lr->startp = lr->currp = lr->data; 685 | 686 | if ((i = read(lr->fd, 687 | lr->currp, 688 | lr->sizep - lr->currp)) <= 0) { 689 | /* 690 | * here if end-of-file or on error. set endp == currp 691 | * so if non-blocking I/O is in use next call will go to read() 692 | */ 693 | lr->endp = lr->currp; 694 | *ptr = (char *)(intptr_t)i; /* user compares ptr (NULL, (char *)-1, ... */ 695 | return -1; 696 | } 697 | else 698 | lr->endp = lr->currp + i; 699 | } 700 | else /* Inform user that next call may block (unless select()ed) */ 701 | { 702 | lr->selected = true; 703 | return 0; 704 | } 705 | else /* currp has not reached endp yet. */ 706 | { 707 | *lr->currp = lr->saved; 708 | lr->startp = lr->currp; 709 | } 710 | 711 | /* 712 | * Scan read string for next newline. 713 | */ 714 | while (lr->currp < lr->endp) 715 | if (*lr->currp++ == '\n') /* memchr ? (or rawmemchr & extra \n at end) */ 716 | { 717 | lr->line_completed = true; 718 | lr->saved = *lr->currp; 719 | *lr->currp = '\0'; 720 | lr->selected = false; 721 | *ptr = lr->startp; 722 | 723 | return lr->currp - lr->startp; 724 | } 725 | 726 | /* 727 | * Here if currp == endp, but no NLCHAR found. 728 | */ 729 | lr->selected = true; 730 | 731 | if (lr->currp == lr->sizep) { 732 | /* 733 | * Here if currp reaches end-of-buffer (endp is there also). 734 | */ 735 | if (lr->startp == lr->data) /* (data buffer too short for whole string) */ 736 | { 737 | lr->line_completed = true; 738 | *ptr = lr->data; 739 | *lr->currp = '\0'; 740 | return -1; 741 | } 742 | /* 743 | * Copy partial string to start-of-buffer and make control ready for 744 | * filling rest of buffer when next call to lineread() is made 745 | * (perhaps after select()). 746 | */ 747 | memmove(lr->data, lr->startp, lr->endp - lr->startp); 748 | lr->endp-= (lr->startp - lr->data); 749 | lr->currp = lr->endp; 750 | lr->startp = lr->data; 751 | } 752 | 753 | lr->line_completed = false; 754 | return 0; 755 | } 756 | 757 | static void lineread_init(LineRead * lr, int fd) 758 | { 759 | lr->fd = fd; 760 | lr->currp = lr->endp = NULL; /* any value works */ 761 | lr->sizep = lr->data + sizeof lr->data; 762 | lr->selected = lr->line_completed = true; 763 | } 764 | 765 | /* ^^^^^^^^^^^^^^^^^^^^^^^^^ lineread.c ^^^^^^^^^^^^^^^^^^^^^^^^^ */ 766 | 767 | static void * xrealloc(void * ptr, size_t size) 768 | { 769 | ptr = realloc(ptr, size); 770 | if (ptr == null) 771 | diev("Memory allocation failed:", ESTR, null); 772 | return ptr; 773 | } 774 | 775 | static void addmail(off_t pos, struct tm * tm) 776 | { 777 | # define MASIZE 1000 778 | if (G.mailc % MASIZE == 0) { 779 | G.maila = xrealloc(G.maila, (G.mailc + MASIZE) * sizeof (FileEntry)); 780 | d0("%p %lu", (void *)G.maila, G.mailc); 781 | } 782 | FileEntry * fe = G.maila + G.mailc++; 783 | fe->offset = pos; 784 | fe->year = tm->tm_year; fe->mon = tm->tm_mon; fe->mday = tm->tm_mday; 785 | fe->hour = tm->tm_hour; fe->min = tm->tm_min; fe->sec = tm->tm_sec; 786 | fe->xqho = tm->tm_yday / 900; 787 | 788 | if (fe->year > 8098) fe->year = 8098; 789 | 790 | d0("off %ld, time: ", pos); 791 | d0("%04lx: %d-%02d-%02d %02d:%02d:%02d", G.mailc - 1, 792 | fe->year + 1900, fe->mon + 1, fe->mday, 793 | fe->hour, fe->min, fe->sec); 794 | } 795 | 796 | // The purpose of this sorting it to get all mail belonging to year-mo 797 | // to follow each other. In the dir those need to be in the order appended 798 | // to the mbox (so that every later "arrived", but earlier time will add one 799 | // second to the directory times. Future implementation change must have same 800 | // or similar feature where every added mail to a directory will cause direc- 801 | // tory timestamt to change. 802 | static int sortmaili(const void * aa, const void * bb) 803 | //int sortmaili(off_t * a, off_t * b) 804 | //int sortmail(const FileEntry * a, const FileEntry * b) 805 | { 806 | const FileEntry * a = G.maila + *(const size_t *)aa; 807 | const FileEntry * b = G.maila + *(const size_t *)bb; 808 | 809 | # define cl(f) if (a->f != b->f) return a->f - b->f 810 | cl(year); cl(mon); 811 | // in a directory, keep files in closer to original order where 812 | // message in smaller offset is kept in smaller index. 813 | // these values are 64 bits wide -- and definitely different // 814 | // return (a->offset < b->offset)? -1: 1; 815 | // and so are just a & b 816 | return (a < b)? -1: 1; 817 | } 818 | 819 | static void scan_mbox(const char * mboxfile) 820 | { 821 | BB; 822 | int fd = open(mboxfile, O_RDONLY); 823 | if (fd < 0) 824 | diev("Opening '", mboxfile, "' failed: ", ESTR, null); 825 | G.fd = fd; 826 | BE; 827 | off_t frompos = -1; 828 | struct tm tm; 829 | off_t pos = 0; 830 | int hdrline = 0; 831 | BB; 832 | LineRead lr; 833 | lineread_init(&lr, G.fd); 834 | while (1) { 835 | int l; char * lp; 836 | for (; (l = lineread(&lr, &lp)) > 0; pos += l) { 837 | if (frompos < 0) { 838 | if (memcmp(lp, "From ", 5) == 0) { 839 | frompos = pos; 840 | memset(&tm, 0, sizeof tm); 841 | (void)_gettm(lp + 5, &tm); 842 | hdrline = 0; 843 | } 844 | continue; 845 | } 846 | // else 847 | // XXX expect full date being in one line, w/ the header // 848 | if (strncasecmp(lp, "Date:", 5) == 0) { 849 | char * p = lp + 5; 850 | while (isspace(*p)) p++; 851 | if (*p) 852 | (void)_gettm(p, &tm); 853 | addmail(frompos, &tm); 854 | frompos = -1; 855 | continue; 856 | } 857 | if (! hdrline) { 858 | char *p = lp; 859 | // check header field -- format in rfc 2822, section 2.2 // 860 | while (*p != ':' && *p >= 33 && *p <= 126) 861 | p++; 862 | if (*p == ':') 863 | hdrline = 1; 864 | else 865 | frompos = -1; 866 | continue; 867 | } 868 | // XXX could check continuation lines for dates in case we don't 869 | // have date yet. Also we could allow email if there is some 870 | // date at the end of 'From ' line. 871 | if (isblank(lp[0])) 872 | continue; 873 | char *p = lp; 874 | // check header field -- format in rfc 2822, section 2.2 // 875 | // ... this takes care of "end-of-headers", too... 876 | while (*p != ':' && *p >= 33 && *p <= 126) 877 | p++; 878 | if (*p != ':') 879 | // XXX here we could check whether there is enough headers to 880 | // XXX consider this as email -- i.e. no Date: header was found 881 | frompos = -1; 882 | //write(1, lp, l); 883 | } 884 | if (l < 0) break; 885 | // and when l == 0, just continue to lineread next buffer full of data 886 | } 887 | BE; 888 | // add eof position to yet one entry // 889 | memset(&tm, 0, sizeof tm); 890 | addmail(pos, &tm); 891 | G.mailc--; 892 | G.mboxsize = pos; 893 | //printf("%d %d\n", G.mailc, sizeof (time_t)); 894 | // create offset list and sort these date based... 895 | // this is getting a bit complicated -- restructure... 896 | G.maili = xrealloc(null, G.mailc * sizeof (off_t)); 897 | for (size_t i = 0; i < G.mailc; i++) 898 | G.maili[i] = i; 899 | qsort(G.maili, G.mailc, sizeof (size_t), sortmaili); 900 | //for (int i = 0; i < 120; i++) printf("%d %ld\n", i, G.maili[i]); exit(0); 901 | int mon = -1; 902 | time_t ht = C.time_t_small; 903 | off_t bw = 0; 904 | FileEntry * ref = null; 905 | for (size_t i = 0; i < G.mailc; i++) { 906 | if (mon != G.maila[G.maili[i]].mon) { 907 | #define YMSIZE 60 // 5 years 908 | if (G.yearmonc > 0) { 909 | DirEntry * pd = &G.yearmona[G.yearmonc-1]; 910 | pd->year = ref->year; pd->mon = ref->mon; pd->mday = ref->mday; 911 | pd->hour = ref->hour; pd->min = ref->min; pd->sec = ref->sec; 912 | d0("%d-%02d %ld", pd->year + 1900, pd->mon + 1, bw); 913 | pd->sec += bw % 60; 914 | if (bw > 60) { bw /= 60; pd->min += bw % 60; 915 | if (bw > 60) { bw /= 60; pd->min += bw % 24; 916 | if (bw > 24) { bw /= 24; pd->min += bw % 200; }}} 917 | bw = 0; 918 | pd->xqho = ref->xqho; 919 | } 920 | if (G.yearmonc % YMSIZE == 0) { 921 | G.yearmona = xrealloc(G.yearmona, (G.yearmonc + YMSIZE) 922 | * sizeof (DirEntry)); 923 | } 924 | G.yearmona[G.yearmonc++].index = i; 925 | d0("%d %d", G.maila[i].year, G.maila[i].mon); 926 | mon = G.maila[G.maili[i]].mon; 927 | ht = C.time_t_small; 928 | } 929 | FileEntry * pref = &G.maila[G.maili[i]]; 930 | time_t pt = fetime(pref); 931 | d0("%ld %ld %ld", pt, ht, pt - ht); 932 | if (pt > ht) { 933 | ref = pref; 934 | bw-= (pt - ht - 1); 935 | if (bw < 0) bw = 0; // else da("bw %ld", bw); 936 | ht = pt; 937 | } 938 | else bw++; // cumulatively adding sec to dir timestamp when ... 939 | } 940 | G.ht = ht + bw; 941 | if (G.yearmonc >= 2) { // XXX add testcase(s) 942 | time_t pt = fetime( (FileEntry *)&G.yearmona[G.yearmonc-2]); 943 | if (pt > G.ht) G.ht = pt; 944 | } 945 | if (mon >= 0) { 946 | DirEntry * pd = &G.yearmona[G.yearmonc-1]; 947 | pd->year = ref->year; pd->mon = ref->mon; pd->mday = ref->mday; 948 | pd->hour = ref->hour; pd->min = ref->min; pd->sec = ref->sec; 949 | d0("%d-%d %ld", pd->year + 1900, pd->mon + 1, bw); 950 | pd->sec += bw % 60; 951 | if (bw > 60) { bw /= 60; pd->min += bw % 60; 952 | if (bw > 60) { bw /= 60; pd->min += bw % 24; 953 | if (bw > 24) { bw /= 24; pd->min += bw % 222; }}} 954 | pd->xqho = ref->xqho; 955 | } 956 | // and extra item for finddir optimization 957 | if (G.yearmonc % YMSIZE == 0) { 958 | G.yearmona = xrealloc(G.yearmona, (G.yearmonc + YMSIZE) 959 | * sizeof (DirEntry)); 960 | } 961 | // extra item to be duplicate of previous to be sure it doesn't match... // 962 | memcpy(G.yearmona+G.yearmonc, G.yearmona+G.yearmonc-1, sizeof (DirEntry)); 963 | } 964 | 965 | int main(int argc, char * argv[]) 966 | { 967 | if (argc > 1 && argv[1][0] == '-' && 968 | (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { 969 | // XXX first line (the usage line) lacks 'mbox-file' part :( 970 | return fuse_main(2, argv, null, null); 971 | } 972 | init_G(argv[0]); 973 | if (argc < 3) 974 | diev("Usage: ", argv[0], " mbox-file mountpoint [fuse options]", null); 975 | BB; 976 | struct stat st; 977 | if (stat(argv[1], &st) < 0) 978 | diev("Cannot access file ", argv[1], ": ", ESTR, null); 979 | if (! S_ISREG(st.st_mode)) 980 | diev("File '", argv[1], "' is not (regular) file", null); 981 | if (stat(argv[2], &st) < 0) 982 | diev("Cannot access directory ", argv[2], ": ", ESTR, null); 983 | if (! S_ISDIR(st.st_mode)) 984 | diev("File '", argv[2], "' is not directory", null); 985 | BE; 986 | scan_mbox(argv[1]); 987 | 988 | // I've spent enough time trying to understand this argument stuff... 989 | // Now, just move filename to argv[0] (to be seen in df output) 990 | // and put -ouse_ino arg in the old place of filename (argv[1] that is). 991 | char * fn = strrchr(argv[1], '/'); 992 | if (fn == null) fn = argv[1]; 993 | else fn+= 1; 994 | argv[0] = fn; char a[] = "-ouse_ino"; argv[1] = a; 995 | 996 | return fuse_main(argc, argv, &mbox_oper, null); 997 | } 998 | -------------------------------------------------------------------------------- /md5mda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $Id; md5mda.sh $ 3 | # 4 | # Copyright (c) 2011-2014 Tomi Ollila 5 | # All rights reserved 6 | # 7 | # Created: Thu Jul 28 2011 21:52:56 +0300 too 8 | # Last modified: Wed 25 Feb 2015 16:35:48 +0200 too 9 | 10 | set -eu 11 | 12 | # When launched from ~/.forward, PATH not available... 13 | PATH=$HOME/bin:/usr/bin:/bin:/usr/local/bin:/usr/sbin:/sbin 14 | export PATH 15 | 16 | # "Idiomatic" ~/.forward example: (fix args and change user to your login name) 17 | #|"IFS=' '&& $HOME/.../md5mda.sh --cd $HOME/mail received wip log || exit 75 #user" 18 | 19 | case ${BASH_VERSION-} in *.*) shopt -s xpg_echo; esac 20 | case ${ZSH_VERSION-} in *.*) emulate ksh; set -eu; esac 21 | 22 | saved_IFS=$IFS 23 | readonly saved_IFS 24 | 25 | # die() will be re-defined a bit later 26 | die () { echo "$@" >&2; exit 1; } 27 | 28 | # fd 3 will be opened to a file a bit later 29 | log () { echo `exec date +'%Y-%m-%d (%a) %H:%M:%S'`: "$@" >&3; } 30 | 31 | usage () { 32 | bn=`exec basename "$0"` 33 | echo 34 | echo Usage: $bn [--cd dir] [--log-tee-stdout] maildir wipdir logdir 35 | echo 36 | } 37 | 38 | set_argval () { shift; argval="$*"; } 39 | 40 | while case ${1-} in 41 | -h|-?|--help) usage; exec sed -n '/^Options:/,$ p' "$0" ;; 42 | --cd=*) IFS==; set_argval $1; IFS=$saved_IFS; cd "$argval" ;; 43 | --cd) cd "$2"; shift ;; 44 | --log-tee-stdout) 45 | log () { 46 | date=`exec date +'%Y-%m-%d (%a) %H:%M:%S'` 47 | echo $date: "$@" >&3; echo $date: "$@" 48 | } ;; 49 | --) shift; false ;; 50 | -|-*) die "'$1': unknown option" ;; 51 | *) false 52 | esac; do shift; done 53 | 54 | case $# in 3) ;; *) 55 | exec >&2 56 | usage 57 | echo Enter '' $0 --help '' for more help 58 | echo 59 | exit 1 60 | esac 61 | 62 | nospaces () 63 | { 64 | case $2 in *["$IFS"]*) die "$1 '$2' contains whitespace"; esac 65 | } 66 | 67 | maildir=$1 wipdir=$2 logdir=$3 68 | 69 | nospaces maildir "$maildir" 70 | nospaces wipdir "$wipdir" 71 | nospaces logdir "$logdir" 72 | 73 | eval `exec date +'year=%Y mon=%m'` 74 | 75 | test -d $logdir || mkdir -p $logdir 76 | exec 3>> $logdir/md5mda-$year$mon.log 77 | 78 | # had to write the above as exec failure below is uncaughtable (in dash) 79 | #{ exec 3>> $logdir/md5mda-$year$mon.log || { 80 | # mkdir -p $logdir 81 | # exec 3>> $logdir/md5mda-$year$mon.log; } 82 | #} 2>/dev/null 83 | 84 | die () { log "$@"; echo "$@" >&2; exit 1; } 85 | 86 | if=`exec mktemp $wipdir/incoming.XXXXXX 2>/dev/null` || : 87 | case $if in '') 88 | mkdir -p $wipdir 89 | if=`exec mktemp $wipdir/incoming.XXXXXX` 90 | esac 91 | 92 | # Write mail content from stdin to a file. 93 | # 'bogofilter -p' could be used here (bogofilter keeps whole mail in memory). 94 | #cat >> $if 95 | # Replace possible 'From ' on 1st line with 'X-From-Line: ' 96 | sed '1s/^From /X-From-Line: /' >> $if 97 | 98 | # openssl md5 provides same output on Linux & BSD systems (at least). 99 | eval `openssl md5 $if | sed 's:.* \(..\):dirp=\1 filep=:'` 100 | case $filep in '') 101 | die "Executing 'openssl md5 $if' failed!" 102 | esac 103 | 104 | # try atomic move, w/ link & unlink. don't overwrite old if any 105 | trymove () 106 | { 107 | ln "$1" "$2" 2>/dev/null || return 0 # note: inverse logic in return value 108 | unlink "$1" || : # leftover if unlink (ever) fails... 109 | return 1 110 | } 111 | 112 | dof=$maildir/$dirp 113 | of=$dof/$filep 114 | 115 | movemailfile () 116 | { 117 | trymove $if $of || return 0 118 | 119 | # in most of the cases execution doesn't reach here. 120 | 121 | test -d $dof || mkdir -p $dof || : # parallel mkdir possible... 122 | trymove $if $of || return 0 123 | 124 | # if next test fails, leftover $if will be there 125 | test -f $of || die "ERROR: ln $if $of (where '$of' nonexistent) failed" 126 | 127 | for f in $of* 128 | do 129 | # duplicate mails are more probable collision reason than... 130 | if cmp -s $if $f 131 | then 132 | log "Duplicate mail '$f' ignored" 133 | rm $if 134 | exit 0 135 | fi 136 | done 137 | # hmm, same sum but not duplicate. Older edited ? 138 | osum=`openssl md5 $of | sed 's:.* \(..\):\1/:'` 139 | case $osum in $dirp/$filep) 140 | log "WHOA! '$of' with 2 different files !" 141 | echo "WHOA! '$of' with 2 different files !" >&2 142 | esac 143 | # We don't go into rename game in this script, we just want to 144 | # deliver mail files.... Note that the mktemp is done in the target 145 | # dir to assume uniqueness in first hit -- so there is temporary 146 | # zero-sized file for a short moment until it is replaced by the real 147 | # mail file (with different inode number). In the very improbable 148 | # chance the temporary file is ever there and noticed this should not 149 | # cause any other problem than slight confusion (if ever that). 150 | of=`exec mktemp $of.XXXXXX` 151 | mv -f $if $of 152 | } 153 | movemailfile 154 | 155 | log "Added '$of'" 156 | 157 | exit 0 158 | 159 | Options: 160 | 161 | --cd dir -- change current directory to 'dir' before continuing 162 | --log-tee-stdout -- write log also to stdout 163 | 164 | Parameters: 165 | 166 | maildir -- the root directory for delivered mail 167 | wipdir -- work in progress temporary location for mail in delivery 168 | logdir -- directory where delivery logs are written 169 | 170 | The mail is read from stdin and it is first written to a file in 'wipdir' 171 | and its md5 checksum is calculated there. After that the file is moved to 172 | a subdirectory(*) in 'maildir'. 173 | Maildir and wipdir needs to be in the same file system. 174 | 175 | (*) The subdirectory is the 2 first hexdigits of the md5 checksum of the 176 | mail contents and the filename is the rest 30 hexdigits of the checksum. 177 | . 178 | -------------------------------------------------------------------------------- /nottoomuch-addresses.rst: -------------------------------------------------------------------------------- 1 | nottoomuch-addresses.sh 2 | ======================= 3 | 4 | *Nottoomuch-addresses.sh* is an email address completion/matching tool 5 | to be used with `notmuch `_ mail user agents. 6 | 7 | *Nottoomuch-addresses.sh* works by caching the email addresses from users' 8 | email files and then doing (fgrep) matching against that cache when 9 | requested. 10 | 11 | The matching part is very fast. 12 | 13 | How To Install 14 | -------------- 15 | 16 | 1. Copy `nottoomuch-addresses.sh `_ to the machine 17 | you're running notmuch and find suitable location for it. 18 | 19 | 2. Run ``/path/to/nottoomuch-addresses.sh --rebuild`` 20 | When run first time this gathers email addresses from all of your mail. 21 | This may take a long while to complete -- depends on the amount of email 22 | you have. Further ``--update``\s are much faster as those just take 23 | addresses from new mail. 24 | 25 | 3. Test that it works: run ``/path/to/nottoomuch-addresses.sh notmuchmail`` 26 | 27 | 4. In case you're using emacs mua with notmuch, edit your notmuch 28 | configuration for emacs (e.g. ``~/.emacs.d/notmuch-config.el`` since 29 | notmuch 0.22) with the following content: 30 | :: 31 | 32 | (require 'notmuch-address) 33 | (setq notmuch-address-command "/path/to/nottoomuch-addresses.sh") 34 | 35 | 5. Restart emacs notmuch mua (or eval above lines) and start composing 36 | new mail. When adding recipient to To: field. press TAB after 3 37 | or more characters have been added. In case you get 2 or more address 38 | matches, use arrow keys in minibuffer to choose desired recipient... 39 | 40 | 6. (Optional) the default address completion notmuch emacs mua uses when 41 | addresses are completed using external command may be hard to use with 42 | nottoomuch-addresses. 43 | I've been using `selection-menu.el `_ happily all the 44 | time I've been using nottoomuch-addresses.sh. I'd like to know about 45 | alternatives (ido, ivy, helm) but as it works well enough haven't bothered. 46 | 47 | 7. Enjoy! 48 | 49 | ``./nottoomuch-addresses.sh --help`` provides more detailed usage information. 50 | -------------------------------------------------------------------------------- /nottoomuch-addresses.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -*- cperl -*- 3 | 4 | case $* in (''|--*) exec perl -x "$0" "$@" ;; (???*) ;; (*) exit 0 ;; esac 5 | grep -aiF "$*" "${XDG_CONFIG_HOME:-$HOME/.config}/nottoomuch/addresses.active" 6 | case $? in 0|1) exit 0; esac 7 | exit $? 8 | 9 | # $ nottoomuch-addresses.sh $ 10 | # 11 | # Created: Thu 27 Oct 2011 17:38:46 EEST too 12 | # Last modified: Sat 03 Oct 2020 03:10:10 +0300 too 13 | 14 | # Add these lines to your notmuch elisp configuration file 15 | # ;; (e.g to ~/.emacs.d/notmuch-config.el since notmuch 0.22): 16 | # 17 | # (require 'notmuch-address) 18 | # (setq notmuch-address-command "/path/to/nottoomuch-addresses.sh") 19 | 20 | # Documentation at the end. Encoding: utf-8. 21 | 22 | #!perl 23 | # line 25 24 | # - 25 -^ 25 | 26 | # HISTORY 27 | # 28 | # Version 2.5 2020-10-03 03:10:20 UTC 29 | # * Added reading of new file 'addresses.top'. The addresses listed there 30 | # are written on the top of the 'addresses.active' while doing --update 31 | # or --rebuild. These addresses are matched first when searches done. 32 | # 33 | # Version 2.4 2016-01-02 21:00:00 UTC 34 | # * Separated reading of the mail file list using notmuch(1) from scanning 35 | # the email addresses from the mail files. Now notmuch database is locked 36 | # for shorter time period. 37 | # * Some internal utf8 handling (related output warnings went away). 38 | # 39 | # Version 2.3 2014-09-17 15:59:44 UTC 40 | # * 3 new command line options for --(re)build phase: 41 | # --since -- scan mails dated since YYYY-MM-DD 42 | # --exclude-path-re -- regexps for directories to exclude 43 | # --name-conversion=lcf2flem -- convert address-matching phrase names 44 | # 45 | # See updated documentation (--help) for more information of these. 46 | # 47 | # * Changed 'addresses' file header to format v5: Following lines contain 48 | # 'since', 'exclude-path-re' and 'name-conversion' information (if any) 49 | # and new marker line '---' separates this configuration from gathered 50 | # addresses. 51 | # 52 | # * The files this program writes (and then reads) are handled as containing 53 | # utf8 data. The mail files read are read as "raw" files as incorrect utf8 54 | # there could make this program abort. Malformed utf8 is (sometimes?) 55 | # considered as being encoded as latin1 (at some time of the processing). 56 | # This all is a bit hairy to me, but at least it is getting better... 57 | # 58 | # * Slight option handling change: --update and --rebuild are now mutually 59 | # exclusive and update does not (auto) build if address cache is missing. 60 | # 61 | # Version 2.2 2014-03-29 15:12:14 UTC 62 | # * In case there is both {phrase} and (comment) in an email address, 63 | # append comment to the phrase. This will make more duplicates to be 64 | # removed. Now there can be: 65 | # 66 | # "phrase" 67 | # "phrase (comment)" 68 | # (comment) 69 | # * In case email address is in form "someuser@somehost" 70 | # i.e. the phrase is exactly the same as
, phrase is dropped. 71 | # 72 | # Version 2.1 2012-02-22 14:58:58 UTC 73 | # * Fixed a bug where decoding matching but unknown or malformed =?...?=- 74 | # encoded parts in email addresses lead to infinite loop. 75 | # 76 | # Version 2.0 2012-01-14 03:45:00 UTC 77 | # * Added regexp-based ignores using /regexp/[i] syntax in ignore file. 78 | # * Changed addresses file header to v4; 'addresses' file now contains all 79 | # found addresses plus some metainformation added at the end of the file. 80 | # Filtered (by ignores) address list is now in new 'addresses.active' 81 | # file and the fgrep code at the beginning now uses this "active" file. 82 | # Addresses file with header v2 and v3 are supported for reading. 83 | # * Encoded address content is now recursively decoded. 84 | # 85 | # Version 1.6 2011-12-29 06:42:42 UTC 86 | # * Fixed 'encoded-text' recognition and concatenations, and underscore 87 | # to space replacements. Now quite RFC 2047 "compliant". 88 | # 89 | # Version 1.5 2011-12-22 20:20:32 UTC 90 | # * Changed search to exit with zero value (also) if no match found. 91 | # * Changed addresses file header (v3) to use \t as separator. Addresses 92 | # file containing previous version header (v2) can also be read. 93 | # * Removed outdated information about sorting in ASCII order. 94 | # 95 | # Version 1.4 2011-12-14 19:24:28 UTC 96 | # * Changed to run notmuch search --sort=newest-first --output=files ... 97 | # (instead of notmuch show ...) and read headers from files internally. 98 | # * Fixed away joining uninitialized $phrase value to address line. 99 | # 100 | # Version 1.3 2011-12-12 15:41:05 UTC 101 | # * Changed to store/show addresses in 'newest first' order. 102 | # * Changed addresses file header to force address file rebuild. 103 | # 104 | # Version 1.2 2011-12-06 18:00:00 UTC 105 | # * Changed search work case-insensitively -- grep(1) does it locale-aware. 106 | # * Changed this program execute from /bin/sh (wrapper). 107 | # 108 | # Version 1.1 2011-12-02 17:11:33 UTC 109 | # * Removed Naïve assumption that no-one runs update on 'dumb' terminal. 110 | # * Check address database file first line whether it is known to us. 111 | # 112 | # Thanks to Bart Bunting for providing a good bug report. 113 | # 114 | # Version 1.0 2011-11-30 20:56:10 UTC 115 | # * Initial release. 116 | 117 | use 5.8.1; 118 | use strict; 119 | use warnings; 120 | 121 | use utf8; 122 | use open ':utf8'; # do not use with autodie (?) 123 | binmode STDOUT, ':utf8'; 124 | binmode STDERR, ':utf8'; 125 | 126 | use File::Temp 'tempfile'; 127 | 128 | use Encode qw/decode_utf8 find_encoding _utf8_on/; 129 | use MIME::Base64 'decode_base64'; 130 | use MIME::QuotedPrint 'decode_qp'; 131 | 132 | use Time::Local; 133 | 134 | my $configdir = ($ENV{XDG_CONFIG_HOME}||$ENV{HOME}.'/.config').'/nottoomuch'; 135 | my $adbpath = $configdir . '/addresses'; 136 | my $ignpath = $configdir . '/addresses.ignore'; 137 | my $actpath = $configdir . '/addresses.active'; 138 | my $toppath = $configdir . '/addresses.top'; 139 | 140 | unless (@ARGV) 141 | { 142 | require Pod::Usage; 143 | Pod::Usage::pod2usage( -verbose => 0, -exitval => 0 ); 144 | exit 1; 145 | } 146 | 147 | my ($o_update, $o_rebuild, $o_ncm) = (0, 0, {}); 148 | my ($o_since, @o_exclude); 149 | my $aref; 150 | foreach (@ARGV) { 151 | if (defined $aref) { 152 | if (ref $aref eq 'ARRAY') { 153 | push @{$aref}, $_; 154 | } else { ${$aref} = $_; } 155 | undef $aref; 156 | next; 157 | } 158 | $o_update = 1, next if $_ eq '--update'; 159 | $o_rebuild = 1, next if $_ eq '--rebuild'; 160 | $aref = \$o_ncm, next if $_ eq '--name-conversion'; 161 | $o_ncm = $1, next if $_ =~ '^--name-conversion=(.*)'; 162 | $aref = \$o_since, next if $_ eq '--since'; 163 | $o_since = $1, next if $_ =~ /^--since=(.*)/; 164 | $aref = \@o_exclude, next if $_ eq '--exclude-path-re'; 165 | push (@o_exclude, $1), next if $_ =~ '^--exclude-path-re=(.*)'; 166 | 167 | if ($_ eq '--help') { 168 | $SIG{__DIE__} = sub { 169 | $SIG{__DIE__} = 'DEFAULT'; 170 | require Pod::Usage; 171 | Pod::Usage::pod2usage(-verbose => 2,-exitval => 0,-noperldoc => 1); 172 | exit 1; 173 | }; 174 | require Pod::Perldoc; 175 | $SIG{__DIE__} = 'DEFAULT'; 176 | # in case PAGER is not set, perldoc runs /usr/bin/perl -isr ... 177 | if ( ($ENV{PAGER} || '') eq 'less') { 178 | $ENV{LESS} .= 'R' if ($ENV{LESS} || '') !~ /[rR]/; 179 | } 180 | @ARGV = ( $0 ); 181 | exit ( Pod::Perldoc->run() ); 182 | } 183 | #s/-+//; 184 | die "$0: '$_': unknown option.\n"; 185 | } 186 | 187 | die "$0: Value missing for option '$ARGV[$#ARGV]'\n" if defined $aref; 188 | 189 | my @optlines; 190 | 191 | my $sincetime; 192 | if (defined $o_since) { 193 | die "Option '--since' value format: YYYY-MM-DD\n" 194 | unless $o_since =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/; 195 | $sincetime = timelocal(0, 0, 0, $3, $2 - 1, $1); 196 | die "Since dates before Jan 1-2, 1970 not supported.\n" if $sincetime < 0; 197 | push @optlines, "since: $o_since\n"; # not used, just for future reference 198 | } 199 | else { 200 | $sincetime = -1; 201 | } 202 | 203 | unless (ref $o_ncm) { 204 | die "The only name conversion method known is 'lcf2flem' (≠ '$o_ncm')\n" 205 | unless $o_ncm eq 'lcf2flem'; 206 | push @optlines, "name-conversion: $o_ncm\n"; 207 | } 208 | 209 | if ($o_rebuild) { 210 | die "Options '--update' and '--rebuild' are mutually exclusive.\n" 211 | if $o_update; 212 | } 213 | else { 214 | die "File '$adbpath' does not exist. Use --rebuild.\n" 215 | unless -s $adbpath; 216 | die "Option '--since' not applicable when not rebuilding.\n" 217 | if $sincetime >= 0; 218 | die "Option '--exclude-path-re' not applicable when not rebuilding.\n" 219 | if @o_exclude; 220 | die "Option '--name-conversion' not applicable when not rebuilding.\n" 221 | unless ref $o_ncm; 222 | } 223 | 224 | # all arg checks done before this line! 225 | my @list; 226 | 227 | sub mkdirs($); 228 | sub mkdirs($) { 229 | die "'$_[0]': not a (writable) directory\n" if -e $_[0]; 230 | return if mkdir $_[0]; # no mode: 0777 & ~umask used 231 | local $_ = $_[0]; 232 | mkdirs $_ if s|/?[^/]+$|| and $_; 233 | mkdir $_[0] or die "Cannot create '$_[0]': $!\n"; 234 | } 235 | 236 | mkdirs $configdir unless -d $configdir; 237 | 238 | unlink $adbpath if $o_rebuild; # XXX replace later w/ atomic replacement. 239 | 240 | my @exclude; 241 | my ($sstr, $acount) = (0, 0); 242 | if (-s $adbpath) { 243 | die "Cannot open '$adbpath': $!\n" unless open I, '<', $adbpath; 244 | read I, $_, 18; 245 | # new header: "v5/dd/dd/dd/dd/dd\n" where / == '\t' (but match also v2) 246 | if (/^v([2345])\s(\d\d)\s(\d\d)\s(\d\d)\s(\d\d)\s(\d\d)\n$/) { 247 | $sstr = "$2$3$4$5$6" - 86400 * 7; # one week extra to (re)look. 248 | $sstr = 0 if $sstr < 0; 249 | if ($1 == 5) { 250 | while () { 251 | last if /^---/; 252 | push @optlines, $_; 253 | push(@exclude, $1), next if /^exclude-path-re:\s+(.*)/; 254 | $o_ncm = $1 if /^name-conversion:\s*(.*?)\s+$/ 255 | } 256 | } 257 | } 258 | close I if $sstr == 0; 259 | } 260 | if ($sstr > 0) { 261 | print "Updating '$adbpath', since $sstr.\n"; 262 | $sstr .= '..'; 263 | } 264 | else { 265 | print "Creating '$adbpath'. This may take some time...\n"; 266 | push @exclude, split(/::/, $_) foreach (@o_exclude); 267 | push @optlines, "exclude-path-re: $_\n" foreach (@exclude); 268 | 269 | if ($sincetime >= 0) { 270 | print "Reading addresses from mails since $o_since.\n"; 271 | $sstr = "$sincetime..", 272 | } 273 | else { $sstr = '*'; } 274 | } 275 | undef @o_exclude; 276 | 277 | if (ref $o_ncm) { 278 | $o_ncm = 0; 279 | } 280 | elsif ($o_ncm eq 'lcf2flem') { 281 | $o_ncm = 1; 282 | } 283 | else { 284 | warn "Unknown name conversion method '$o_ncm'. Ignored\n"; 285 | $o_ncm = 0; 286 | } 287 | 288 | my (%ign_hash, @ign_relist); 289 | if (-f $ignpath) { 290 | die "Cannot open '$ignpath': $!\n" unless open J, '<', $ignpath; 291 | while () { 292 | next if /^\s*#/; 293 | if (m|^/(.*)/(\w*)\s*$|) { 294 | if ($2 eq 'i') { 295 | push @ign_relist, qr/$1/i; 296 | } 297 | else { 298 | push @ign_relist, qr/$1/; 299 | } 300 | } 301 | else { 302 | s/\s+$/\n/; 303 | $ign_hash{$_} = 1; 304 | } 305 | } 306 | close J; 307 | } 308 | 309 | my $sometime = time; 310 | die "Cannot open '$adbpath.new': $!\n" unless open O, '>', $adbpath.'.new'; 311 | die "Cannot open '$actpath.new': $!\n" unless open A, '>', $actpath.'.new'; 312 | $_ = $sometime; s/(..)\B/$1\t/g; # FYI: s/..\B\K/\t/g requires perl 5.10. 313 | print O "v5\t$_\n"; 314 | print O $_ foreach (@optlines); 315 | print O "---\n"; 316 | undef @optlines; 317 | 318 | # The following code block is from Email::Address, almost verbatim. 319 | # The reasons to snip code I instead of just 'use Email::Address' are: 320 | # 1) Some systems ship Mail::Address instead of Email::Address 321 | # 2) Every user doesn't have ability to install Email::Address 322 | # --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<-- 323 | 324 | ## no critic RequireUseWarnings 325 | # support pre-5.6 326 | 327 | #$VERSION = '1.889'; 328 | my $COMMENT_NEST_LEVEL = 2; 329 | 330 | my $CTL = q{\x00-\x1F\x7F}; 331 | my $special = q{()<>\\[\\]:;@\\\\,."}; 332 | 333 | my $text = qr/[^\x0A\x0D]/; 334 | 335 | my $quoted_pair = qr/\\$text/; 336 | 337 | my $ctext = qr/(?>[^()\\]+)/; 338 | my ($ccontent, $comment) = (q{})x2; 339 | for (1 .. $COMMENT_NEST_LEVEL) { 340 | $ccontent = qr/$ctext|$quoted_pair|$comment/; 341 | $comment = qr/\s*\((?:\s*$ccontent)*\s*\)\s*/; 342 | } 343 | my $cfws = qr/$comment|\s+/; 344 | 345 | my $atext = qq/[^$CTL$special\\s]/; 346 | my $atom = qr/$cfws*$atext+$cfws*/; 347 | my $dot_atom_text = qr/$atext+(?:\.$atext+)*/; 348 | my $dot_atom = qr/$cfws*$dot_atom_text$cfws*/; 349 | 350 | my $qtext = qr/[^\\"]/; 351 | my $qcontent = qr/$qtext|$quoted_pair/; 352 | my $quoted_string = qr/$cfws*"$qcontent+"$cfws*/; 353 | 354 | my $word = qr/$atom|$quoted_string/; 355 | 356 | # XXX: This ($phrase) used to just be: my $phrase = qr/$word+/; It was changed 357 | # to resolve bug 22991, creating a significant slowdown. Given current speed 358 | # problems. Once 16320 is resolved, this section should be dealt with. 359 | # -- rjbs, 2006-11-11 360 | #my $obs_phrase = qr/$word(?:$word|\.|$cfws)*/; 361 | 362 | # XXX: ...and the above solution caused endless problems (never returned) when 363 | # examining this address, now in a test: 364 | # admin+=E6=96=B0=E5=8A=A0=E5=9D=A1_Weblog-- ATAT --test.socialtext.com 365 | # So we disallow the hateful CFWS in this context for now. Of modern mail 366 | # agents, only Apple Web Mail 2.0 is known to produce obs-phrase. 367 | # -- rjbs, 2006-11-19 368 | my $simple_word = qr/$atom|\.|\s*"$qcontent+"\s*/; 369 | my $obs_phrase = qr/$simple_word+/; 370 | 371 | my $phrase = qr/$obs_phrase|(?:$word+)/; 372 | 373 | my $local_part = qr/$dot_atom|$quoted_string/; 374 | my $dtext = qr/[^\[\]\\]/; 375 | my $dcontent = qr/$dtext|$quoted_pair/; 376 | my $domain_literal = qr/$cfws*\[(?:\s*$dcontent)*\s*\]$cfws*/; 377 | my $domain = qr/$dot_atom|$domain_literal/; 378 | 379 | my $display_name = $phrase; 380 | 381 | my $addr_spec = qr/$local_part\@$domain/; 382 | my $angle_addr = qr/$cfws*<$addr_spec>$cfws*/; 383 | my $name_addr = qr/$display_name?$angle_addr/; 384 | my $mailbox = qr/(?:$name_addr|$addr_spec)$comment*/; 385 | 386 | # --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<-- 387 | 388 | # In this particular purpose the cache code used in... 389 | my %seen; # ...Email::Address is "replaced" by %seen & %hash. 390 | my %hash; 391 | my $addrcount = 0; 392 | 393 | if (-f $toppath) { 394 | die "Cannot open '$toppath': $!\n" unless open J, '<', $toppath; 395 | while () { 396 | next if defined $seen{$_}; 397 | $seen{$_} = 1; 398 | $hash{$_} = 1; 399 | $addrcount++; 400 | print O $_; 401 | print A $_; 402 | } 403 | close J; 404 | } 405 | my $database_path = qx/notmuch config get database.path/; 406 | chomp $database_path; 407 | my @exclude_re = map qr(^$database_path/$_), @exclude; 408 | print((join "\n", map "Excluding '^$database_path/$_'", @exclude), "\n") 409 | if @exclude and $o_rebuild; 410 | undef $database_path; 411 | undef @exclude; 412 | 413 | my $ptime = $sometime + 5; 414 | $| = 1; 415 | my $efn = tempfile(DIR => $configdir); 416 | open P, '-|', qw/notmuch search --sort=newest-first --output=files/, $sstr; 417 | X: while (

) { 418 | foreach my $re (@exclude_re) { next X if /$re/; } 419 | print $efn $_; 420 | } 421 | close P; 422 | seek $efn, 0, 0; 423 | 424 | while (<$efn>) { 425 | chomp; 426 | # open in raw mode to avoid fatal utf8 problems. does some conversion 427 | # heuristics like latin1 -> utf8 there... -- _utf8_on used on need basis. 428 | open M, '<:raw', $_ or next; 429 | while () { 430 | last if /^\s*$/; 431 | next unless s/^(From|To|Cc|Bcc):\s+//i; 432 | s/\s+$//; 433 | my @a = ( $_ ); 434 | while () { 435 | # XXX leaks to body in case empty line is found in this loop... 436 | # XXX Note that older code leaked to mail body always... 437 | if (s/^\s+// or s/^(From|To|Cc|Bcc):\s+/,/i) { 438 | s/\s+$//; 439 | push @a, $_; 440 | next; 441 | } 442 | last; 443 | } 444 | $_ = join ' ', @a; 445 | 446 | if (time > $ptime) { 447 | my $c = qw(/ - \ |)[int ($ptime / 5) % 4]; 448 | print $c, ' active addresses gathered: ', $addrcount, "\r"; 449 | $ptime += 5; 450 | } 451 | 452 | # The parse function from Email::Address heavily modified 453 | # to fit ok in this particular purpose. New bugs are mine! 454 | # --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<-- 455 | 456 | s/[ \t]+/ /g; # this line did fail fatally on malformed utf-8 data... 457 | s/\?= =\?/\?==\?/g; 458 | my (@mailboxes) = (/$mailbox/go); 459 | L: foreach (@mailboxes) { 460 | next if defined $seen{$_}; 461 | $seen{$_} = 1; 462 | 463 | my @comments = /($comment)/go; 464 | s/$comment//go if @comments; 465 | 466 | my ($user, $host); 467 | ($user, $host) = ($1, $2) if s/<($local_part)\@($domain)>//o; 468 | if (! defined($user) || ! defined($host)) { 469 | s/($local_part)\@($domain)//o; 470 | ($user, $host) = ($1, $2); 471 | } 472 | 473 | sub decode_substring ($) { 474 | my $t = lc $2; 475 | my $s; 476 | if ($t eq 'b') { $s = decode_base64($3); } 477 | elsif ($t eq 'q') { $s = decode_qp($3); } 478 | else { 479 | $_[0] = 0; 480 | return "=?$1?$2?$3?="; 481 | } 482 | $s =~ tr/_/ /; 483 | 484 | return decode_utf8($s) if lc $1 eq 'utf-8'; 485 | 486 | my $o = find_encoding($1); 487 | $_[0] = 0, return "=?$1?$2?$3?=" unless ref $o; 488 | return $o->decode($s); 489 | } 490 | sub decode_data () { 491 | my $loopmax = 5; 492 | while ( s{ =\?([^?]+)\?(\w)\?(.*?)\?= } 493 | { decode_substring($loopmax) }gex ) { 494 | last if --$loopmax <= 0; 495 | }; 496 | } 497 | 498 | my @phrase = /($display_name)/o; 499 | decode_data foreach (@phrase); 500 | 501 | for ( @phrase, $host, $user, @comments ) { 502 | next unless defined $_; 503 | s/^[\s'"]+//; ## additions 20111123 too 504 | s/[\s'"]+$//; ## additions 20111123 too 505 | $_ = undef unless length $_; 506 | } 507 | # here we want to have email address always // 20111123 too 508 | next unless defined $user and defined $host; 509 | 510 | my $userhost = lc "<$user\@$host>"; 511 | #my $userhost = "<$user\@$host>"; 512 | 513 | @comments = grep { defined or return 0; decode_data; 1; } @comments; 514 | 515 | # "trim" phrase, if equals to user@host after trimming, drop it. 516 | if (defined $phrase[0]) { 517 | #$phrase[0] =~s/\A"(.+)"\z/$1/; 518 | $phrase[0] =~ tr/\\//d; ## 20111124 too 519 | $phrase[0] =~ s/\"/\\"/g; 520 | @phrase = () if lc "<$phrase[0]>" eq $userhost; 521 | } 522 | 523 | # In case we would have {phrase} (comment), 524 | # make that "{phrase} (comment)" ... 525 | if (defined $phrase[0]) 526 | { 527 | if ($o_ncm and $phrase[0] =~ /^(.*)\s*,\s*(.*)$/) { 528 | # Try to change "Last, First" to "First Last" 529 | # The heuristics: If either 'Last' or 'First' is having 530 | # the same length in name and address and case-insensitive 531 | # comparison where characters not matching a-z ignored. 532 | my ($mlast, $mfirst) = ($1, $2); 533 | if ($userhost =~ /^) { 581 | last if /^---/; 582 | next if defined $hash{$_}; 583 | print O $_; 584 | next if defined $ign_hash{$_}; 585 | foreach my $re (@ign_relist) { 586 | next L if $_ =~ $re; 587 | } 588 | print A $_; 589 | $addrcount++; 590 | } 591 | while () { 592 | $oldaddrcount = ($1 + 0), next if /^active:\s+(\d+)\s*$/; 593 | } 594 | close I; 595 | } 596 | print O "---\n"; 597 | print O "active: ", $addrcount, "\n"; 598 | close O; 599 | close A; 600 | undef %hash; 601 | #link $adbpath, $adbpath . '.' . $sometime; 602 | rename $adbpath . '.new', $adbpath or 603 | die "Cannot rename '$adbpath.new' to '$adbpath': $!\n"; 604 | rename $actpath . '.new', $actpath or 605 | die "Cannot rename '$actpath.new' to '$actpath': $!\n"; 606 | if ($oldaddrcount or $sstr eq '*') { 607 | $sometime = time - $sometime; 608 | my $new = $addrcount - $oldaddrcount; 609 | print "Added $new active addresses in $sometime seconds.\n"; 610 | } 611 | print "Total number of active addresses: $addrcount.\n"; 612 | exit 0; 613 | 614 | __END__ 615 | 616 | =encoding utf8 617 | 618 | =head1 NAME 619 | 620 | nottoomuch-addresses.sh -- address completion/matching (for notmuch) 621 | 622 | =head1 SYNOPSIS 623 | 624 | nottoomuch-addresses.sh (--update | --rebuild [opts] | ) 625 | 626 | B for more help 627 | 628 | =head1 VERSION 629 | 630 | 2.5 (2020-10-03) 631 | 632 | =head1 633 | 634 | In case no option argument is given on command line, the command line 635 | arguments are used as fixed search string. Search goes through all 636 | email addresses in cache and outputs every address (separated by 637 | newline) where a substring match with the given search string is 638 | found. No wildcard of regular expression matching is used. 639 | 640 | Search is not done unless there is at least 3 octets in search string. 641 | 642 | =head1 OPTIONS 643 | 644 | =head2 B<--update> 645 | 646 | This option is used to incrementally update the "address cache" with 647 | new addresses that are available in mails received since last update. 648 | 649 | All the sender and receiver addresses are collected. 650 | 651 | The addresses in cache are sorted "newest first"; recently seen addresses 652 | are matched first when doing searches (but see also the "top" file section 653 | below). 654 | 655 | =head2 B<--rebuild> 656 | 657 | With this option the address cache is created (or rebuilt from scratch). 658 | 659 | In addition to initial creation this option is useful when some build 660 | options (which affect to all addresses) are desired to be changed. 661 | 662 | Sometimes some of the new emails received may have Date: header point too 663 | much in the past (one week before last update). Update uses email Date: 664 | information to go through new emails to be checked for new addresses 665 | with one week's overlap, and only rebuild will catch these emails (albeit 666 | the rebuild option is quite heavy option to solve such a problem). 667 | 668 | =head3 B<--rebuild> options: 669 | 670 | When (re)building the address cache, there are a few options to affect 671 | the operation (and future additions). 672 | 673 | =over 2 674 | 675 | =item B<--since>=YYYY-MM-DD 676 | 677 | Start email gathering from mails dated YYYY-MM-DD. I.e. skip older. 678 | 679 | =item B<--exclude-path-re>=path-regexp 680 | 681 | Regular expression(s) of directory paths to exclude when scanning mail files. 682 | This option can be given multiple times on the command line. 683 | 684 | Given regexps are anchored to the start of the string (based on the email 685 | directory notmuch is configured with), but not to the end (for example to 686 | match anywhere prefix regexp with '.*', or conversely, to anchor end suffix 687 | regexp with '$'). 688 | 689 | =item B<--name-conversion>=lcf2flem 690 | 691 | With name conversion method 'lcf2flem' (the only method known) email addresses 692 | in format "Last, First " are converted to 693 | "First Last ". For this conversion to succeed either 694 | "First" or "Last" needs to match the corresponding string in email address. 695 | If there are non-us-ascii characters in the names those are ignored in 696 | comparisons (i.e. matches any character). 697 | 698 | This method name is modeled from 699 | "Last-comma-First-to-First-Last-either-matches". 700 | 701 | =back 702 | 703 | =head1 "TOP" FILE 704 | 705 | Some of the recipient addresses you may want to use are not collected from 706 | the emails just in the format you liked. Also some of the addresses you 707 | desire to be found first may get buried deeper in the list of collected 708 | addresses. 709 | 710 | When running B<--update> the output shows the path of address cache 711 | file (usually C<$HOME/.config/nottoomuch/addresses>). If there is file 712 | C in the same directory, that file is read as 713 | newline-separated list of addresses which are to be included on the top 714 | of the address cache file. 715 | 716 | =head1 IGNORE FILE 717 | 718 | Some of the addresses collected may be valid but those still seems to 719 | be noisy junk. You may additionally want to just hide some email 720 | addresses. 721 | 722 | If there is file C in the same directory as C 723 | (and perhaps C), that file is read as newline-separated list 724 | of addresses which are *not* to be included in the address cache file. 725 | 726 | Use your text editor to open both of these files. Then move address 727 | lines to be ignored from B to B. After 728 | saving these 2 files the moved addresses will not reappear in 729 | B file again. 730 | 731 | Since 2012 nottoomuch-addresses.sh has supported regular expressions in 732 | ignore file. Lines in format I or I defines (perl) 733 | Is which are used to match email addresses for ignoring. The 734 | I format makes regular expression case-insensitive -- although this 735 | is only applied to characters in ranges I and I. Remember that 736 | I and I provides same set of matching lines. 737 | 738 | =head1 LICENSE 739 | 740 | This program uses code from Email::Address perl module. Inclusion of 741 | that makes it easy to define license for the whole of this code base: 742 | 743 | Terms of Perl itself 744 | 745 | a) the GNU General Public License as published by the Free 746 | Software Foundation; either version 1, or (at your option) any 747 | later version, or 748 | 749 | b) the "Artistic License" 750 | 751 | =head1 SEE ALSO 752 | 753 | L, L 754 | 755 | =head1 AUTHOR 756 | 757 | Tomi Ollila -- too ät iki piste fi 758 | 759 | =head1 ACKNOWLEDGMENTS 760 | 761 | This program uses code from Email::Address, Copyright (c) by Casey West 762 | and maintained by Ricardo Signes. Thank you. All new bugs are mine, 763 | though. 764 | -------------------------------------------------------------------------------- /nottoomuch-emacs-mailto.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -*- mode: cperl; cperl-indent-level: 4 -*- 3 | # $ nottoomuch-emacs-mailto.pl $ 4 | # 5 | # Author: Tomi Ollila -- too ät iki piste fi 6 | # 7 | # Copyright (c) 2014-2019 Tomi Ollila 8 | # All rights reserved 9 | # 10 | # Created: Sun 29 Jun 2014 16:20:24 EEST too 11 | # Last modified: Sat 09 Jan 2021 00:25:50 +0200 too 12 | 13 | # Handle mailto: links with notmuch emacs client. In case of 14 | # graphical display (the usual case), use emacs in server mode 15 | # (with special server socket) and run emacsclient on it. 16 | # On non-graphical terminal run emacs in terminal mode. 17 | # For special use cases (e.g. for wrappers) there are some extra 18 | # command line options (currently -nw and --from=

). 19 | 20 | # There are some hacks involved to get things working, and may break in the 21 | # future. However, my guess is that this will just work for years to come. 22 | 23 | use 5.8.1; 24 | use strict; 25 | use warnings; 26 | use Cwd; 27 | 28 | # override "default" From: (by setting $from to non-empty string) if desired 29 | my $from = ''; 30 | 31 | # fyi: default emacs server socket name is 'server' (in /tmp/emacs$UID/)... 32 | my $socket_name = 'mailto-server'; 33 | 34 | #open O, '>', "/tmp/mmm.$$"; print O "@ARGV\n"; close O; 35 | 36 | while (@ARGV) { 37 | $_ = $ARGV[0]; 38 | delete $ENV{DISPLAY}, shift, next if $_ eq '-nw'; 39 | $from=$1, shift, next if /^--from=(.*)/; 40 | $ENV{EMACS} = $ENV{EMACSCLIENT} = 'echo', shift, next if $_ eq '--dry-run'; 41 | last; 42 | } 43 | 44 | die "Usage: $0 [-nw] [--from=address] mailto-url\n" unless @ARGV; 45 | 46 | my $use_emacsclient = defined $ENV{'DISPLAY'} && $ENV{DISPLAY} ne ''; 47 | 48 | sub mail($$) 49 | { 50 | my $rest; 51 | ($_, $rest) = split /\?/, $_[1], 2; 52 | warn("skipping '$_' (does not start with 'mailto:')\n"), return 53 | unless s/^mailto://; #s/\s+//g; 54 | my %hash = ( to => [], subject => [], cc => [], bcc => [], 55 | 'in-reply-to' => [], keywords => [], body => [] ); 56 | push @{$hash{to}}, $_ if $_; 57 | if (defined $rest) { 58 | foreach (split /&/, $rest) { 59 | my $hfname; 60 | ($hfname, $_) = split /=/, $_, 2; 61 | $hfname = lc $hfname; 62 | next unless defined $hash{$hfname}; 63 | s/%([\da-fA-F][\da-fA-F])/chr(hex($1)) unless lc($1) eq '0d'/ge; 64 | #s/%([\da-fA-F][\da-fA-F])/chr(hex($1))/ge; 65 | s/(["\\])/\\$1/g; 66 | push @{$hash{$hfname}}, $_; 67 | } 68 | } 69 | $" = ', '; 70 | sub liornil($) { 71 | no warnings; # maybe the warning is effective when %hash reassigned ? 72 | return @{$hash{$_[0]}}? "\"@{$hash{$_[0]}}\"": "nil"; 73 | } 74 | my $to = liornil 'to'; 75 | my $subject = liornil 'subject'; 76 | my $other_hdrs = 'nil'; 77 | if ($from) { 78 | $from =~ s/("|\\)/\\$1/g; 79 | $other_hdrs = "'((From . \"$from\")"; 80 | } 81 | if (@{$hash{'in-reply-to'}}) { 82 | my $m = "@{$hash{'in-reply-to'}}"; 83 | $other_hdrs = "'(" if $other_hdrs eq 'nil'; 84 | $other_hdrs .= "(In-Reply-to . \"$m\")"; 85 | } 86 | $other_hdrs .= ')' if $other_hdrs ne 'nil'; 87 | 88 | my @elisp = ( "(with-demoted-errors", " (require 'notmuch)", 89 | " (notmuch-mua-mail", 90 | " $to", 91 | " $subject", 92 | " $other_hdrs", 93 | " (notmuch-mua-get-switch-function))" ); 94 | sub ideffi($) { 95 | no warnings; # ditto, now with @elisp too... 96 | return unless @{$hash{$_[0]}}; 97 | push @elisp, " (message-goto-$_[0]) (insert \"@{$hash{$_[0]}}\")"; 98 | } 99 | ideffi 'cc'; 100 | ideffi 'bcc'; 101 | ideffi 'keywords'; 102 | $" = "\n"; 103 | 104 | if (@{$hash{body}}) { 105 | # hacking body addition just before signature setup (since message 106 | # setup hook, which is called later, may add e.g. MML header[s]) 107 | splice @elisp, 2, 0, ( 108 | ' (let ((message-signature-setup-hook message-signature-setup-hook))', 109 | " (add-hook 'message-signature-setup-hook", 110 | qq' (lambda () (message-goto-body) (insert "@{$hash{body}}")', 111 | ' (if (/= (point) (line-beginning-position))', 112 | ' (newline))))' ) 113 | } 114 | if ($use_emacsclient) { 115 | my $cwd = cwd(); $cwd =~ s/("|\\)/\\$1/g; 116 | push @elisp, qq' (cd "$cwd")'; 117 | } 118 | my @cmdline; 119 | 120 | if ($use_emacsclient) { 121 | my $emacsclient = $ENV{EMACSCLIENT} || 'emacsclient'; 122 | @cmdline = ( $emacsclient, 123 | qw/-c --alternate-editor= --no-wait -s/, $socket_name ); 124 | # code to stop emacs if all frames are closed, there are no 125 | # clients and no modified buffers which have file name 126 | splice @elisp, 2, 0, ( 127 | ' (defun delete-mailto-frame-function (frame) 128 | (if (and (= (length server-clients) 0) 129 | (< (length (delq frame (frame-list))) 2) 130 | (not (memq t (mapcar (lambda (buf) (and (buffer-file-name buf) 131 | (buffer-modified-p buf))) 132 | (buffer-list))))) 133 | (kill-emacs)))', 134 | " (add-hook 'delete-frame-functions 'delete-mailto-frame-function)" ); 135 | } 136 | else { 137 | my $emacs = $ENV{EMACS} || 'emacs'; 138 | @cmdline = ( $emacs, '-nw' ); 139 | } 140 | push @elisp, '))'; 141 | push @cmdline, '--eval', "@elisp"; 142 | 143 | #print "@cmdline\n"; exit 0; 144 | exec @cmdline unless $_[0]; 145 | system @cmdline; 146 | } 147 | 148 | my $last = pop @ARGV; 149 | mail 1, $_ foreach (@ARGV); 150 | mail 0, $last; 151 | -------------------------------------------------------------------------------- /nottoomuch-emacs-mailto.rst: -------------------------------------------------------------------------------- 1 | nottoomuch-emacs-mailto.pl 2 | ========================== 3 | 4 | *nottoomuch-emacs-mailto.pl* is a tool typically used from web browser 5 | ``mailto:`` links to send email using notmuch emacs client. 6 | 7 | When used in graphical environment, a new emacs frame is started 8 | and filled with the information provided in ``mailto:`` link. After 9 | sending the frame will stay on desktop and can be closed the usual 10 | emacs way (*). 11 | 12 | On non-graphical terminal, new emacs process is started on the terminal 13 | and from there it works like in graphical display. 14 | 15 | In addition to taking ``mailto:`` arguments from command line, there are 16 | (currently) 2 other options, ``-nw`` and ``--from=
``. These are, 17 | and can be used in special applications (e.g. wrappers) to e.g. get some 18 | work done. 19 | 20 | How To "Install" 21 | ---------------- 22 | 23 | 1. Copy `nottoomuch-emacs-mailto.pl `_ to 24 | the machine you intent to use it (or clone this repository). 25 | 26 | 2. Configure web browser to use this when following ``mailto:`` links. 27 | In Firefox this was easy: *Edit->Preferences->Applications* and set 28 | ``mailto`` there. In Chrome I could not find how this is done; 29 | perhaps some mystic *xdg* things or something... I'll update this 30 | whenever I figure out... OK, the solution is to use 31 | nottoomuch-xdg-email_ to wrap ``xdg-email``... see its embedded 32 | documentation for more info. 33 | 34 | .. _nottoomuch-xdg-email: nottoomuch-xdg-email.sh 35 | 36 | (*) In graphical display, emacs is started in server mode and the frame 37 | is opened using ``emacsclient(1)``. The emacs server uses special server 38 | socket named ``mailto-server``. When last frame closes, there are no 39 | clients connected and no modified buffers with file name, the emacs 40 | in daemon mode exits. 41 | -------------------------------------------------------------------------------- /nottoomuch-remote-emacs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | :; export REMOTE_NOTMUCH_SSHCTRL_SOCK=${1##*/}; shift 3 | :; exec "${EMACS:-emacs}" --debug-init --load "$0" "$@"; exit 4 | 5 | ;; wrapper to set up remote notmuch for emacs; used with nottoomuch-remote.bash 6 | 7 | ;; setting REMOTE_NOTMUCH_SSHCTRL_SOCK from command line has been put 8 | ;; into the shell script part so that it does not stay in ps output 9 | 10 | ;; alternatively, this file can be copied as new one, and embed the socket 11 | ;; path in. in that case it is also convenient to add more eval-after-load 12 | ;; settings (e.g. mail send configurations) 13 | 14 | ;; (setenv "REMOTE_NOTMUCH_SSHCTRL_SOCK" "master-notmuch@remote:22") 15 | 16 | (setq sshctrl-sock (getenv "REMOTE_NOTMUCH_SSHCTRL_SOCK")) 17 | 18 | (when (string-equal sshctrl-sock "") 19 | (insert "\nUsage: " (file-name-nondirectory load-file-name) " {ctl_name} " 20 | "[other emacs options]\n" 21 | "\n{cl_name} is ssh control socket located in ~/.ssh/...") 22 | (error "missing argument")) 23 | 24 | (setq ctl_path (concat (expand-file-name "~/.ssh/") sshctrl-sock)) 25 | 26 | (unless (file-exists-p ctl_path) 27 | (insert "\nSocket '" ctl_path "' does not exist!") 28 | (error "missing file")) 29 | 30 | ;; XXX common cases -- don't know how to check for socket 31 | (when (or (file-regular-p ctl_path) (file-directory-p ctl_path)) 32 | (insert "\nFile '" ctl_path "' is not socket!") 33 | (error "file not socket")) 34 | 35 | (eval-after-load "notmuch" 36 | (lambda () 37 | (setq notmuch-command (concat (file-name-directory load-file-name) 38 | "nottoomuch-remote.bash")) 39 | ;; add more in your own copy, if desired 40 | )) 41 | 42 | (load "notmuch") 43 | 44 | (insert " 45 | To start notmuch (hello) screen, evaluate 46 | (notmuch-hello) <- type C-x C-e or C-j between \")\" and \"<-\"") 47 | 48 | ;; Local Variables: 49 | ;; mode: emacs-lisp 50 | ;; End: 51 | -------------------------------------------------------------------------------- /nottoomuch-remote.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- shell-script -*- 3 | # 4 | # Created: Tue 29 May 2012 21:30:17 EEST too 5 | # Last modified: Sun 19 Dec 2021 11:59:37 +0200 too 6 | 7 | # See first ./nottoomuch-remote.rst and then maybe: 8 | # http://notmuchmail.org/remoteusage/ 9 | # http://notmuchmail.org/remoteusage/124/ <- this script 10 | 11 | set -euf 12 | # To trace execution, uncomment next line: 13 | #exec 6>>remote-errors; BASH_XTRACEFD=6; echo -- >&6; set -x; env >&6 14 | 15 | : ${REMOTE_NOTMUCH_SSHCTRL_SOCK:=master-notmuch@remote:22} 16 | : ${REMOTE_NOTMUCH_COMMAND:=notmuch} 17 | 18 | readonly REMOTE_NOTMUCH_SSHCTRL_SOCK REMOTE_NOTMUCH_COMMAND 19 | 20 | SSH_CONTROL_ARGS='-oControlMaster=no -S ~'/.ssh/$REMOTE_NOTMUCH_SSHCTRL_SOCK 21 | readonly SSH_CONTROL_ARGS 22 | 23 | printf -v ARGS '%q ' "$@" # bash feature 24 | readonly ARGS 25 | 26 | if ssh -q $SSH_CONTROL_ARGS .1 "$REMOTE_NOTMUCH_COMMAND" $ARGS 27 | then exit 0 28 | else ev=$? 29 | fi 30 | 31 | # continuing here in case ssh exited with nonzero value 32 | 33 | case $* in 34 | 'config get user.primary_email') echo 'nobody@nowhere.invalid'; exit 0 ;; 35 | 'config get user.name') echo 'nobody'; exit 0 ;; 36 | 'count'*'--batch'*) while read line; do echo 1; done; exit 0 ;; 37 | 'count'*) echo 1; exit 0 ;; 38 | 'search-tags'*) echo 'errors'; exit 0 ;; 39 | 'search'*'--output=tags'*) echo 'errors'; exit 0 ;; 40 | esac 41 | 42 | # fallback exit handler; print only to stderr... 43 | exec >&2 44 | 45 | if ssh $SSH_CONTROL_ARGS -O check 0.1 46 | then 47 | echo " Control socket is alive but something exited with status $ev" 48 | exit $ev 49 | fi 50 | 51 | case $0 in */*) dn0=${0%/*} ;; *) dn0=. ;; esac 52 | echo "See $dn0/nottoomuch-remote.rst for more information" 53 | 54 | exit $ev 55 | -------------------------------------------------------------------------------- /nottoomuch-remote.rst: -------------------------------------------------------------------------------- 1 | Notmuch remoteusage without password-free login requirement 2 | =========================================================== 3 | 4 | This solution uses one pre-made ssh connection where the client is put into 5 | "master" mode (-M) for connection sharing. The wrapper script then uses the 6 | control socket created by this pre-made ssh connection for its own 7 | connection. As long as master ssh connection is live, slave can use 8 | it. Disconnecting master all future attempts to connect from the script 9 | will fail. 10 | 11 | The script 12 | ---------- 13 | 14 | Is available at `nottoomuch-remote.bash `_ 15 | 16 | While viewing the script, notice the ``0.1`` in ssh command line. It is 17 | used to avoid any opportunistic behaviour ssh might do; for example if 18 | control socket is not alive ssh would attempt to do it's own ssh connection 19 | to remote ssh server. As address ``0.1`` is invalid this attempt will fail 20 | early. 21 | 22 | Test 23 | ---- 24 | 25 | Easiest way to test this script is to run the pre-made ssh connection using 26 | the following command line: 27 | :: 28 | $ ssh -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost sleep 600 29 | 30 | (replace ``[user@]remotehost`` above with your login info). Doing this the 31 | above wrapper script can be run unmodified. After the above command has 32 | been run on one terminal, enter ``chmod +x nottoomuch-remote.bash`` in another 33 | terminal and then test the script with 34 | :: 35 | $ ./nottoomuch-remote.bash help 36 | 37 | Note that the '~' in the ssh command line above is inside single quotes for 38 | a reason. In this case shell never expand it to $HOME -- ssh does it by not 39 | reading $HOME but checking the real user home directory from 40 | ``/etc/passwd``. For security purposes this is just how it should be. 41 | 42 | Tune 43 | ---- 44 | 45 | The path ``'~'/.ssh/master-notmuch@remote:22`` might look too generic to be 46 | used as is as the control socket after initial testing (but it can be used). 47 | It is presented as a template for what could be configured to 48 | ``$HOME/.ssh/config``. For example: 49 | :: 50 | Host * 51 | ControlPath ~/.ssh/master-%h@%p:%r 52 | 53 | is a good entry to have been written in ``$HOME/.ssh/config``. 54 | Now, let's say you'd make your pre-made ssh connection with command 55 | :: 56 | $ ssh -M robin@example.org 57 | 58 | There is 3 options how to handle this with ``./nottoomuch-remote.bash``: 59 | 60 | 1) Edit ``./nottoomuch-remote.bash`` and change ``REMOTE_NOTMUCH_SSHCTRL_SOCK`` 61 | to contain the new value (being *master-robin@example.org:22* in this 62 | case) 63 | 64 | 2) Make symlink: 65 | ``$ ln -sfT master-robin@example.org:22 ~/.ssh/master-notmuch@remote:22`` 66 | 67 | 3) ``REMOTE_NOTMUCH_SSHCTRL_SOCK`` can be used via environment; like: 68 | :: 69 | $ REMOTE_NOTMUCH_SSHCTRL_SOCK=master-robin@example.org:22 ./nottoomuch-remote.bash help 70 | 71 | Alternative 3 provides way to use remote notmuch without editing 72 | nottoomuch-remote.bash -- also the same script can be used with multiple 73 | clients to separate (local +) remotes simultaneously! 74 | 75 | 4) Use the new script, ``nottoomuch-remote-emacs.sh`` briefly mentioned 76 | below. More information of it will be added later... 77 | 78 | Configure Emacs on the client computer 79 | -------------------------------------- 80 | 81 | (there is now a new tool available: 82 | `nottoomuch-remote-emacs.sh `_ 83 | use it or this other stuff below...) 84 | 85 | Add something like the following functions to your Emacs (general(*) or 86 | notmuch specific) configuration files: 87 | :: 88 | ;; this should work as backend function when copied verbatim 89 | (defun user/notmuch-remote-setup (sockname) 90 | (setq notmuch-command "/path/to/nottoomuch-remote.bash") 91 | (setenv "REMOTE_NOTMUCH_SSHCTRL_SOCK" sockname) 92 | ;; If you use Fcc, you may want to do something like this on the client, 93 | ;; to Bcc mails to yourself (if not, remove in your implementation): 94 | (setq notmuch-fcc-dirs nil) 95 | (add-hook 'message-header-setup-hook 96 | (lambda () (insert (format "Bcc: %s <%s>\n" 97 | (notmuch-user-name) 98 | (notmuch-user-primary-email)))))) 99 | 100 | ;; this is just an example to configure using "default" master socket 101 | (defun user/notmuch-remote-default () 102 | (interactive) 103 | (user/notmuch-remote-setup "master-notmuch@remote:22") 104 | 105 | ;; usage example2: set USER & HOST1 according to your remote... 106 | (defun user/notmuch-remote-at-HOST1 () 107 | (interactive) 108 | (user/notmuch-remote-setup "master-USER@HOST1:22") 109 | 110 | ;; ... you probably got the point now -- add relevant funcs to your config 111 | (defun user/notmuch-remote-at-HOST2 () 112 | (interactive) 113 | (user/notmuch-remote-setup "master-USER@HOST2:22") 114 | 115 | ... and if you want to activate your remote by default just call 116 | ``(user/notmuch-remote-setup "master-USER@HOST:22")`` without function call 117 | wrapper. 118 | 119 | (*) general most likely being ~/.emacs 120 | 121 | Creating master connection 122 | -------------------------- 123 | 124 | **(Note: all the examples below use the default master socket written in** 125 | ``./nottoomuch-remote.bash`` **for initial test easiness; remove/change the** 126 | ``-S '~'/.ssh/master-notmuch@remote:22`` **in case you don't need it.)** 127 | 128 | As mentioned so many times, using this solution requires one pre-made ssh 129 | connection in *master* mode. The simplest way is to dedicate one terminal 130 | for the connection with shell access to the remote machine: 131 | :: 132 | $ ssh -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost 133 | 134 | One possibility is to have this dedicated terminal in a way that the 135 | connection has (for example 1 hour) timeout: 136 | :: 137 | $ ssh -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost sleep 3600 138 | 139 | The above holds the terminal. The next alternative puts the command in 140 | background: 141 | :: 142 | $ ssh -f -M -S '~'/.ssh/master-notmuch@remote:22 [user@]remotehost sleep 3600 143 | 144 | If you don't want this to timeout so soon, use a longer sleep, like 145 | 99999999 (8 9:s, 1157 days, a bit more than 3 years). 146 | 147 | A more "exotic" solution would be to make a shell script running on remote 148 | machine, checking/inotifying when new mail arrives. When mail arrives it 149 | could send message back to local host, where a graphical client (to be 150 | written) pops up on display providing info about received mail (and exiting 151 | this graphical client connection to remote host is terminated). 152 | 153 | Troubleshooting 154 | --------------- 155 | 156 | If you experience strange output when using from emacs first attempt to 157 | just run 158 | :: 159 | $ ./nottoomuch-remote.bash help 160 | 161 | from command line and observe output. If it looks as it should be next 162 | uncomment the line 163 | :: 164 | #exec 6>>remote-errors; BASH_XTRACEFD=6; echo -- >&6; set -x 165 | 166 | in ``./nottoomuch-remote.bash`` and attempt to use it from emacs again -- 167 | and then examine the contents of remote-errors in the working directory 168 | emacs was started. 169 | -------------------------------------------------------------------------------- /nottoomuch-xdg-email.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # wrap system xgd-email to run nottoomuch-emacs-mailto.pl for mailto: links 4 | # for those (including me) who don't know (or want or cannot) how to configure 5 | # xdg-email to run specific email program. 6 | 7 | # these steps usually work: 8 | # 9 | # $ mkdir $HOME/bin 10 | # $ cd $HOME/bin 11 | # $ ln -s ../path/to/nottoomuch/nottoomuch-emacs-mailto.pl . 12 | # $ ln -s ../path/to/nottoomuch/nottoomuch-xdg-email.sh xdg-email 13 | 14 | # use MAILER=echo /usr/bin/xdg-email ... to test how system xdg-email works... 15 | 16 | set -euf 17 | 18 | MAILER=`command -v nottoomuch-emacs-mailto.pl` 19 | #if test -h "$MAILER" 20 | #then MAILER=`exec readlink -f "$mailer"` 21 | #fi 22 | export MAILER 23 | 24 | 25 | #saved_IFS=$IFS 26 | IFS=: # this setting does not affect expansion of "$@" (vs "$*") 27 | 28 | for d in $PATH 29 | do if test -x "$d"/xdg-email 30 | then 31 | test "$0" -ef "$d"/xdg-email || exec "$d"/xdg-email "$@" 32 | fi 33 | done 34 | 35 | exec >&2 36 | echo Cannot find xdg-email 37 | exit 1 38 | -------------------------------------------------------------------------------- /selection-menu.el: -------------------------------------------------------------------------------- 1 | ;;; selection-menu.el --- "generic" menu to choose one string. 2 | ;;; 3 | ;;; Author: Tomi Ollila -- too ät iki piste fi 4 | ;;; 5 | ;;; Contributions (see git log for details): 6 | ;;; 7 | ;;; Mark Walters: 8 | ;;; 2013-12-15: list-based interface with possibility to have multiline 9 | ;;; menu entries and keybindings for those. 10 | ;;; 11 | ;;; License: GPLv2+ 12 | 13 | ;; read-key is available in emacs 23.2 & newer... 14 | (if (fboundp 'read-key) 15 | (defalias 'selection-menu--read-key 'read-key) 16 | (defalias 'selection-menu--read-key 17 | (lambda (msg) (aref (read-key-sequence-vector msg) 0)))) 18 | 19 | ;; popup.el or company.el could give insight how to "improve" 20 | ;; key reading (and get mouse events into picture, too) 21 | ;; (or maybe mouse events could already be read but how to handle...) 22 | 23 | (defun selection-menu-current-option () 24 | (get-text-property (point) 'selection-menu-option)) 25 | 26 | (defun selection-menu-current-start () 27 | (get-text-property (point) 'selection-menu-option-start)) 28 | 29 | (defun selection-menu-current-end () 30 | (get-text-property (point) 'selection-menu-option-end)) 31 | 32 | (defun selection-menu-adjust () 33 | (let ((start (selection-menu-current-start))) 34 | (when start 35 | (goto-char start)))) 36 | 37 | (defun selection-menu-up () 38 | (goto-char (selection-menu-current-start)) 39 | (unless (bobp) 40 | (forward-line -1) 41 | (selection-menu-adjust))) 42 | 43 | (defun selection-menu-down () 44 | (let ((current-point (point))) 45 | (goto-char (selection-menu-current-end)) 46 | (unless (selection-menu-adjust) 47 | (goto-char current-point)))) 48 | 49 | (defun selection-menu--select (ident &optional unread key-short-cut-list) 50 | (let ((helpmsg "Type ESC to abort, Space or Enter to select.") 51 | (buffer-read-only t) 52 | first last overlay pevent select) 53 | (forward-line -1) 54 | (setq last (point)) 55 | (goto-char (point-min)) 56 | (setq first (point)) 57 | (save-window-excursion 58 | (pop-to-buffer (current-buffer)) 59 | (setq mode-name "Selection Menu" 60 | mode-line-buffer-identification (concat "*" ident "*")) 61 | (setq overlay (make-overlay (selection-menu-current-start) (selection-menu-current-end))) 62 | (overlay-put overlay 'face 'highlight) 63 | (while 64 | (let ((event (selection-menu--read-key helpmsg))) 65 | (cond ((or (eq event 'up) (eq event 16)) 66 | (selection-menu-up) 67 | (move-overlay overlay (selection-menu-current-start) (selection-menu-current-end)) 68 | t) 69 | ((or (eq event 'down) (eq event 14)) 70 | (selection-menu-down) 71 | (move-overlay overlay (selection-menu-current-start) (selection-menu-current-end)) 72 | t) 73 | ((or (eq event 32) (eq event 13) (eq event 'return)) 74 | (setq select 75 | (selection-menu-current-option)) 76 | nil) 77 | ((setq select (plist-get key-short-cut-list event)) 78 | nil) 79 | ((eq event 'escape) 80 | nil) 81 | (t (setq pevent event) 82 | nil) 83 | )))) 84 | (if (and unread pevent) 85 | (push pevent unread-command-events)) 86 | (message nil) 87 | select)) 88 | 89 | (defun selection-menu (ident items &optional unread) 90 | "Pops up a buffer listing lines given ITEMS one per line. 91 | Use arrow keys (or C-p/C-n) to select and SPC/RET to select. 92 | Return to parent buffer when any other key is pressed. 93 | In this case if optional UNREAD is non-nil return the 94 | read key back to input queue for parent to consume." 95 | (if (eq (length items) 0) nil 96 | (save-excursion 97 | (with-temp-buffer 98 | (let (key-short-cut-list) 99 | (dolist (item items) 100 | (let ((option (if (listp item) (nth 0 item) item)) 101 | (description (if (listp item) (nth 1 item) (concat " " item))) 102 | (key-short-cuts (if (listp item) (nth 2 item))) 103 | (start (point-marker)) 104 | end) 105 | (insert description "\n") 106 | (setq end (point-marker)) 107 | (dolist (key key-short-cuts) 108 | (setq key-short-cut-list (plist-put key-short-cut-list key option))) 109 | (put-text-property start end 'selection-menu-option option) 110 | (put-text-property start end 'selection-menu-option-start start) 111 | (put-text-property start end 'selection-menu-option-end end))) 112 | (selection-menu--select ident unread key-short-cut-list)))))) 113 | 114 | ;;(selection-menu "foo" (list)) 115 | ;;(selection-menu "foo" (list "a")) 116 | ;;(selection-menu "Send mail to:" (list "a" "b" "c" "d" "faaarao") t) 117 | ;;(selection-menu "Send mail to:" (list '("a" "alpha") '("b" "beta") '("c" "gamma") '("d" "delta")) t) 118 | ;;(selection-menu "Send mail to:" (list (list "alpha" "alpha" '(?a ?A)) (list "bravo" "beta" '(?b ?B)) (list "cat" "gamma" '(?c ?C))) t) 119 | ;; test by entering c-x c-e at the end of previous lines 120 | 121 | (provide 'selection-menu) 122 | -------------------------------------------------------------------------------- /selection-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domo141/nottoomuch/729bf5b344de2a757d74fde9f82826f0fcede38c/selection-menu.png -------------------------------------------------------------------------------- /selection-menu.rst: -------------------------------------------------------------------------------- 1 | selection-menu.el 2 | ================= 3 | 4 | `selection-menu.el `_ is an emacs lisp module/function 5 | providing: 6 | :: 7 | 8 | (defun selection-menu (ident items &optional unread) 9 | "Pops up a buffer listing lines given ITEMS one per line. 10 | Use arrow keys (or C-p/C-n) to select and SPC/RET to select. 11 | Return to parent buffer when any other key is pressed. 12 | In this case if optional UNREAD is non-nil return the 13 | read key back to input queue for parent to consume." ...) 14 | 15 | Notmuch-emacs address completion usage 16 | -------------------------------------- 17 | 18 | With notmuch 0.16 (or newer) adding the following emacs lisp piece 19 | to your notmuch initialization file will make notmuch address completion 20 | feature use selection menu: 21 | :: 22 | 23 | (require 'selection-menu) 24 | (setq notmuch-address-selection-function 25 | (lambda (prompt collection initial-input) 26 | (selection-menu "Send To:" (cons initial-input collection) t))) 27 | 28 | See also 29 | -------- 30 | 31 | `nottoomuch-addresses `_ 32 | 33 | 34 | Example picture 35 | --------------- 36 | 37 | In the illustrated example picture below you can see selection-menu in 38 | action when completing email addresses. 39 | 40 | .. image:: selection-menu.png 41 | -------------------------------------------------------------------------------- /startfetchmail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -*- mode: shell-script; sh-basic-offset: 8; tab-width: 8 -*- 3 | # $ startfetchmail.sh $ 4 | # 5 | # Created: Wed 06 Mar 2013 17:17:58 EET too 6 | # Last modified: Fri 08 Sep 2017 17:31:02 +0300 too 7 | 8 | # Fetchmail does not offer an option to daemonize it after first authentication 9 | # is successful (and report if it failed). After 2 fragile attempts to capture 10 | # the password in one-shot fetch (using tty play) and if that successful 11 | # run fetchmail in daemon mode I've settled to run this script and just check 12 | # from log whether authentication succeeded... 13 | 14 | set -euf # hint: sh -x thisfile [args] to trace execution 15 | 16 | case ${1-} in -I) idle=; shift ;; *) idle=idle 17 | esac 18 | 19 | case $# in 5) ;; *) exec >&2 20 | echo 21 | echo Usage: $0 [-I] '(143|993|995) (keep|nokeep)' user server mda_cmdline 22 | echo 23 | echo This script runs fetchmail with options to use encrypted IMAP 24 | echo '(or POP3)' connection when fetching email. STARTTLS is required 25 | echo when using port 143. IMAP IDLE feature used when applicable... 26 | echo 27 | echo ... except -I option can be used to inhibit IMAP IDLE usage, in 28 | echo cases where mail delivery is delayed or does not happen with IDLE. 29 | echo 30 | echo fetchmail is run in background '(daemon mode)' and first few 31 | echo seconds of new fetchmail log is printed to terminal so that user 32 | echo can determine whether authentication succeeded. 33 | echo 34 | echo Examples: 35 | echo 36 | echo ' ' $0 993 keep $USER mailhost.example.org \\ 37 | echo " '/usr/bin/procmail -d %T'" 38 | echo 39 | echo ' Deliver mail from imap[s] server to user mbox in spool' 40 | echo ' directory (usually /var[/spool]/mail/$USER, or $MAIL).' 41 | echo ' Mails are not removed from imap server.' 42 | echo 43 | echo ' ' $0 143 nokeep $USER mailhost.example.org \\ 44 | echo " './nottoomuch/md5mda.sh --cd mail received wip log'" 45 | echo 46 | echo ' Deliver mail from imap server (STARTTLS required) to' 47 | echo ' separate mails in $HOME/mail/received/??/ directories.' 48 | echo ' Mails are removed from imap server.' 49 | echo 50 | exit 1 51 | esac 52 | 53 | proto=IMAP 54 | case $1 in 143) ssl='sslproto TLS1' 55 | ;; 993) ssl=ssl 56 | ;; 995) ssl=ssl idle= proto='POP3 uidl' # uidl there is a bit hacky... 57 | ;; *) exec >&2 58 | echo 59 | echo "$0: '$1' is not '143', '993' nor '995'". 60 | exit 1 61 | echo 62 | esac 63 | 64 | case $2 in keep) keep='keep';; nokeep) keep= ;; *) exec >&2 65 | echo 66 | echo "$0: '$2' is not either 'keep' or 'nokeep'". 67 | echo 68 | exit 1 69 | esac 70 | shift 2 71 | 72 | imap_user=$1 imap_server=$2 mda_cmdline=$3 73 | shift 3 74 | readonly ssl keep imap_server imap_user mda_cmdline 75 | 76 | echo cd "$HOME" 77 | cd "$HOME" 78 | 79 | mda_cmd=`exec expr "$mda_cmdline" : ' *\([^ ]*\)'` 80 | test -s $mda_cmd || { 81 | exec >&2 82 | case $mda_cmd 83 | in ./*) echo "Cannot find command '$HOME/$mda_cmd'" 84 | ;; /*) echo "Cannot find command '$mda_cmd'" 85 | ;; *) echo "Cannot find command '$HOME/$mda_cmd'" 86 | esac 87 | exit 1 88 | } 89 | 90 | if test -f .fetchmail.pid 91 | then 92 | read pid < .fetchmail.pid 93 | if kill -0 "$pid" 2>/dev/null 94 | then 95 | echo "There is (fetchmail) process running in pid $pid" 96 | ps -p "$pid" 97 | echo "If this is not fetchmail, remove the file" 98 | exit 1 99 | fi 100 | fi 101 | 102 | logfile=.fetchmail.log 103 | if test -f $logfile 104 | then 105 | echo "Rotating logfile '$logfile'" 106 | mv "$logfile".2 "$logfile".3 2>/dev/null || : 107 | mv "$logfile".1 "$logfile".2 2>/dev/null || : 108 | mv "$logfile" "$logfile".1 109 | fi 110 | touch $logfile 111 | 112 | tail -f $logfile & 113 | logfilepid=$! 114 | 115 | trap 'rm -f fmconf; kill $logfilepid' 0 116 | 117 | umask 077 118 | echo " 119 | set daemon 120 120 | set logfile '$logfile' 121 | 122 | poll '$imap_server' proto $proto 123 | user '$imap_user' $ssl $keep $idle 124 | mda '$mda_cmdline' 125 | " | tee fmconf 126 | 127 | ( set -x; exec fetchmail -f fmconf -v ) 128 | 129 | #x fetchmail -f /dev/null -k -v -p IMAP --ssl --idle -d 120 --logfile $logfile\ 130 | # -u USER --mda '/usr/bin/procmail -d %T' SERVER 131 | 132 | sleep 2 133 | test -s $logfile || sleep 2 134 | ol=0 135 | for i in 1 2 3 4 5 6 7 8 9 0 136 | do 137 | nl=`exec stat -c %s $logfile 2>/dev/null` || break 138 | test $nl != $ol || break 139 | ol=$nl 140 | sleep 1 141 | done 142 | rm -f fmconf 143 | trap '' 15 # TERM may not be supported in all shells... 144 | exec 2>/dev/null # be quiet from no on... 145 | kill $logfilepid 146 | trap - 0 15 147 | echo 148 | ps x | grep '\' 149 | echo 150 | echo "Above the end of current fetchmail log '$HOME/$logfile'" 151 | echo "is shown. Check there that your startup was successful." 152 | echo 153 | -------------------------------------------------------------------------------- /wip/frm-md5mdalog.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Created: Fri Aug 19 16:53:45 2011 +0300 too 4 | # Last Modified: Fri 15 Apr 2016 18:44:06 +0300 too 5 | 6 | # This program examines the log files md5mda.sh has written to 7 | # $HOME/mail/log directory (XXX hardcoded internally to this script) 8 | # and prints the from & subject lines from those. 9 | # The -u (update) option marks all currently viewed files read and 10 | # don't read those again (trusting file name ordering and those files 11 | # not truncating). 12 | # The -v (verbose?) option causes the mail filenames to be printed 13 | # so that those can be e.g. removed ahead-of-time. 14 | # 15 | # ( cd $HOME/bin; ln -s path/to/frm-md5mdalog.pl frm ) may be useful... 16 | 17 | # elegance is not a strong point in this program; hacked on the need basis... 18 | # maybe when the desired set of features is known this will be polished. 19 | 20 | #use 5.014; # for tr///r 21 | use 5.10.1; # for \K 22 | use strict; 23 | use warnings; 24 | 25 | use MIME::Base64 'decode_base64'; 26 | use MIME::QuotedPrint 'decode_qp'; 27 | use Encode qw/encode_utf8 find_encoding _utf8_on/; 28 | 29 | no warnings 'utf8'; # do not warn on malformed utf8 data in input... 30 | 31 | binmode STDOUT, ':utf8'; 32 | 33 | sub usage () { die "Usage: $0 [-uvdfqw] [re...]\n"; } 34 | 35 | my ($updateloc, $filenames, $filesonly, $showdels, $fromnew, $wideout) = 36 | (0, 0, 0, 0, 0, 0); 37 | my $quieter = 0; 38 | if (@ARGV > 0 and ord($ARGV[0]) == ord('-')) { 39 | my $arg = $ARGV[0]; 40 | $fromnew = $1 if $arg =~ s/^-\K(\d+)$//; 41 | $showdels = 1 if $arg =~ s/-\w*\Kd//; 42 | $updateloc = 1 if $arg =~ s/-\w*\Ku//; 43 | $filenames = 1 if $arg =~ s/-\w*\Kv//; 44 | $filesonly = 1 if $arg =~ s/-\w*\Kf//; 45 | $quieter = 1 if $arg =~ s/-\w*\Kq//; 46 | $wideout = 1 if $arg =~ s/-\w*\Kw//; 47 | usage unless $arg eq '-'; 48 | shift @ARGV; 49 | } 50 | 51 | my @relist = map { qr/$_/i } @ARGV; 52 | 53 | $_ = $0; s|.*/||; 54 | 55 | my $md = $ENV{'HOME'} . '/mail'; 56 | 57 | chdir $md or die; 58 | 59 | my ($mdlf, $mio); 60 | if (open I, '<', 'log/md5mda-frmloc') { 61 | my $fl = -s I; 62 | if ($fl > 100) { 63 | seek I, -50, 2 or die $!; # seek-end 64 | } 65 | $mdlf = $_ while (); 66 | if (defined $mdlf) { 67 | $mdlf =~ s/^.*\s+(\S+)\s+(\d+)\s*$/$1/; 68 | $mio = (defined $2)? $2: undef; 69 | } 70 | close I; 71 | } 72 | 73 | my @logfiles; 74 | 75 | if (defined $mio) { 76 | $mdlf = 'log/' . $mdlf; 77 | my @alf = ; 78 | my $last; 79 | while (@alf) { 80 | $last = shift @alf; 81 | @logfiles = ( $last ), last if $last eq $mdlf; 82 | } 83 | push @logfiles, $_ foreach (@alf); 84 | 85 | unless (@logfiles and defined $last) { 86 | $mio = '0'; 87 | @logfiles = ( $last ); 88 | } 89 | } 90 | else { 91 | # last file, 0 loc. 92 | $mdlf = $_ foreach (); 93 | $mio = '0'; 94 | @logfiles = ( $mdlf ) if defined $mdlf; 95 | } 96 | 97 | die "No log files to dig mail files from...\n" unless @logfiles; 98 | 99 | my $frmllnk = readlink 'log/md5mda-frmlast'; 100 | my ($frmlast, $frmloff); 101 | if (defined $frmllnk && $frmllnk) { 102 | ($frmlast = $frmllnk) =~ s/^(\d+),(\d+)$/log\/md5mda-$1.log/; 103 | $frmloff = $2 if defined $2; 104 | } 105 | #print $frmlast, ',', $frmloff, "\n"; 106 | 107 | my $cols = int(qx{stty -a | sed -n 's/.*columns //; T; s/;.*//p'} + 0); 108 | $cols = 80 unless ($cols); #print "Columns: $cols.\n"; 109 | my $fw = int ($cols / 3 - 1); my $sw = int ($cols / 3 * 2 - 1); 110 | 111 | my $hdrline; 112 | sub init_next_hdr() 113 | { 114 | $hdrline = ; $. = 0; 115 | } 116 | sub get_next_hdr() 117 | { 118 | return 0 if $hdrline =~ /^$/; 119 | while () { 120 | chomp $hdrline, $hdrline = $hdrline . $_, next if s/^[ \t]+/ /; 121 | ($_, $hdrline) = ($hdrline, $_); 122 | return 1; 123 | } 124 | $_ = $hdrline; $hdrline = ''; $.++; 125 | return 1; 126 | } 127 | 128 | sub decode_data() { 129 | local $_ = $1; 130 | if (s/^utf-8\?(q|b)\?//i) { 131 | return (lc $1 eq 'q')? (tr/_/ /, decode_qp($_)): decode_base64($_); 132 | } 133 | if (s/^([\w-]+)\?(q|b)\?//i) { 134 | my $t = lc $2; 135 | my $o = find_encoding($1); 136 | if (ref $o) { 137 | my $s = ($t eq 'q')? (tr/_/ /, decode_qp($_)): decode_base64($_); 138 | # Encode(3p) is fuzzy whether encode_utf8 is needed... 139 | return encode_utf8($o->decode($s)); 140 | } 141 | } 142 | return "=?$_?="; 143 | } 144 | 145 | my ($mails, $smails, $odate) = (0, 0, ''); 146 | 147 | my %lastfns; 148 | sub mailfrm($) 149 | { 150 | my ($sbj, $frm, $dte, $spam); 151 | init_next_hdr; 152 | while (get_next_hdr) 153 | { 154 | $sbj = $1 if (/^Subject:\s*(.*?)\s*$/i); 155 | $frm = $1 if (/^From:\s*(.*?)\s*$/i); 156 | $dte = $1 if (/^Date:\s*(.*?)\s*$/i); 157 | $spam = 1 if /^X-Bogosity:.*\bSpam\b/i; 158 | } 159 | 160 | $dte =~ s/ (\d\d\d\d).*/ $1/; $dte =~ s/\s(0|\s)/ /g; 161 | $odate = $dte, print "*** $dte\n" 162 | if $dte ne $odate and not $filesonly and not $quieter; 163 | 164 | $sbj = "" unless defined $sbj; 165 | #$frm = "" unless defined $frm; 166 | if ($spam) { $smails++; } 167 | else { 168 | # could split to $1, $2 & $3... 169 | $frm =~ s/\?=\s+=\?/\?==\?/g; 170 | $frm =~ s/=\?([^?]+\?.\?.+?)\?=/decode_data/ge; 171 | unless ($filesonly) { 172 | $_ = $_[0]; s|.*/||; 173 | $frm="!$frm" if defined $lastfns{$_}; 174 | } 175 | $sbj =~ s/\?=\s+=\?/\?==\?/g; 176 | $sbj =~ s/=\?([^?]+\?.\?.+?)\?=/decode_data/ge; 177 | my $line; 178 | if ($wideout) { $line = $frm . ' ' . $sbj; 179 | } else { 180 | _utf8_on($frm); _utf8_on($sbj); # for print widths... 181 | $line = sprintf '%-*.*s %-.*s', $fw, $fw, $frm, $sw, $sbj; 182 | } 183 | sub rechk ($) { foreach (@relist) { return 0 if ($_[0] =~ $_); } 1; } 184 | unless (@relist and rechk $line) { 185 | print $line, "\n" unless $filesonly; 186 | print " $md/$_[0]\n" if $filenames or $filesonly; 187 | print "\n" if $filenames; 188 | } 189 | } 190 | $mails += 1; 191 | } 192 | 193 | # read filenames of last mails imported, from new-* files, to know whether 194 | # taken by notmuch already (XXX 20140821 is this consistent XXX) 195 | 196 | my @_i = ( 1 ); 197 | while () { 198 | $_i[$_i[0]++] = $_; 199 | $_i[0] = 1 if $_i[0] > ($fromnew || 3); 200 | } 201 | 202 | shift @_i; 203 | foreach (@_i) { 204 | open L, '<', $_ or die "$_: $!\n"; 205 | while () { 206 | if ($fromnew) { 207 | # XXX so much duplicate w/ loop below 208 | /\s(\/\S+)\s+$/ || next; 209 | open I, '<', "$1" or do { 210 | print " ** $1: deleted **\n" if $showdels; next; 211 | }; 212 | my $f = $1; 213 | mailfrm $1; 214 | close I; 215 | } 216 | else { 217 | $lastfns{$1} = 1 if /\/([a-z0-9]{9,})$/; 218 | } 219 | } 220 | close L; 221 | } 222 | undef @_i; 223 | exit if $fromnew; 224 | 225 | my $omio = $mio; 226 | my ($cmio, $ltime); 227 | foreach (@logfiles) { 228 | $mdlf = $_; 229 | print "Opening $mdlf... (offset $mio)\n" unless $filesonly or $quieter; 230 | open L, '<', $_ or die "Cannot open '$mdlf': $!\n"; 231 | seek L, $mio, 0 if $mio > 0; 232 | 233 | while () 234 | { 235 | $mio += length; 236 | if (m|(\w\w\w. \d\d:\d\d).*'(.*)'\s*$|) { 237 | $ltime = $1; 238 | open I, '<', "$2" or do { 239 | print " ** $2: deleted **\n" if $showdels; next; 240 | }; 241 | my $f = $2; 242 | mailfrm $f; 243 | close I; 244 | } 245 | } 246 | close L; 247 | $cmio = $mio; 248 | $mio = 0; 249 | } 250 | 251 | if (defined $ltime and ! $filesonly and ! $quieter) { 252 | $ltime =~ tr/)//d; 253 | print "*** Last mail received: $ltime.\n"; 254 | } 255 | 256 | if ($cmio != $omio or @logfiles > 1) { 257 | 258 | $mdlf =~ /md5mda-(\d+)/; 259 | my $newlink = "$1,$cmio"; 260 | if (not defined $frmllnk or $newlink ne $frmllnk) { 261 | unlink 'log/md5mda-frmlast'; 262 | symlink "$1,$cmio", 'log/md5mda-frmlast'; 263 | } 264 | 265 | if ($updateloc) { 266 | my @lt = localtime; 267 | my @wds = qw/Sun Mon Tue Wed Thu Fri Sat Sun/; 268 | my $date = sprintf("%d-%02d-%02d (%s) %02d:%02d:%02d", 269 | $lt[5] + 1900, $lt[4] + 1, $lt[3], $wds[$lt[6]], 270 | $lt[2], $lt[1], $lt[0]); 271 | open O, '>>', 'log/md5mda-frmloc'; 272 | $mdlf =~ s/log\///; 273 | syswrite O, "$date $mdlf $cmio\n"; 274 | print "Offset updated to $mdlf: $cmio\n" unless $filesonly; 275 | close O; 276 | } 277 | elsif (not $filesonly and $showdels and defined $frmloff 278 | and $mdlf eq $frmlast and $frmloff eq $cmio) { 279 | my @l = lstat 'log/md5mda-frmlast'; 280 | @l = localtime $l[9]; 281 | my @d = qw/Sun Mon Tue Wed Thu Fri Sat Sun/; 282 | my $time = sprintf '%02d:%02d', $l[2], $l[1]; 283 | print "*** No new mail since last frm run ($d[$l[6]] $time).\n"; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /wip/mbox-to-mda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $Id; mbox-to-mda.sh $ 3 | # 4 | # Copyright (c) 2011 Tomi Ollila 5 | # All rights reserved 6 | # 7 | # Created: Tue Jul 26 11:45:58 2011 (+0300) too 8 | # Last modified: Sun 19 Oct 2014 12:03:52 +0300 too 9 | 10 | set -eu 11 | 12 | case ${BASH_VERSION-} in *.*) shopt -s xpg_echo; esac 13 | case ${ZSH_VERSION-} in *.*) setopt shwordsplit; esac 14 | 15 | saved_IFS=$IFS 16 | readonly saved_IFS 17 | 18 | die () { echo "$@" >&2; exit 1; } 19 | 20 | usage () { 21 | bn=`basename "$0"` 22 | echo 23 | echo Usage: $bn [-q] [--movemail to-file] mbox-file mda-cmd [mda-args] 24 | echo 25 | } 26 | 27 | set_argval () { shift; argval="$*"; } 28 | 29 | mmmboxf= 30 | verbose_echo () { echo "$@"; } 31 | while case ${1-} in 32 | -q) verbose_echo () { :; } ;; 33 | -h|-?|--help) usage; exec sed -n '/^This program /,$ p' "$0" ;; 34 | --movemail=*) IFS==; set_argval $1; IFS=$saved_IFS; mmmboxf=$argval ;; 35 | --movemail) mmmboxf=$2; shift ;; 36 | --) shift; false ;; 37 | -|-*) die "'$1': unknown option" ;; 38 | *) false 39 | esac; do shift; done 40 | 41 | case $# in 0|1) 42 | exec >&2 43 | usage 44 | echo Enter '' $0 --help '' for more help 45 | echo 46 | exit 1 47 | esac 48 | 49 | mbox=$1 50 | shift 51 | 52 | test -f "$mbox" || die "'$mbox': no such file" 53 | test -x "$1" || die "'$1' cannot be executed" 54 | test -s "$mbox" || { verbose_echo "Mail file '$mbox' is empty"; exit 0; } 55 | 56 | case $mmmboxf in '') ;; *) 57 | # search for movemail(1) 58 | movemail=`exec which movemail 2>/dev/null` || : 59 | case $movemail in /*/movemail) echo foo ;; 60 | *) movemail=`emacs --batch --eval '(progn (defun find-bin (path) 61 | (if (file-exists-p (concat (car path) "/movemail")) 62 | (message (concat (car path) "/movemail")) 63 | (if (not (eq (cdr path) nil)) 64 | (find-bin (cdr path))))) 65 | (find-bin exec-path))' 2>&1 | tail -1 || :` 66 | case $movemail in /*/movemail) ;; 67 | *) die "Cannot find 'movemail' program" 68 | esac 69 | esac 70 | 71 | verbose_echo Using movemail $movemail 72 | esac 73 | 74 | tmmf= 75 | case $mmmboxf in '') ;; *) 76 | tmmf=$mmmboxf.wip 77 | 78 | "$movemail" "$mbox" "$tmmf" 79 | 80 | test -s "$tmmf" || { 81 | verbose_echo "Moved mail file '$tmmf' is empty." 82 | rm -f "$tmmf" 83 | exit 0 84 | } 85 | esac 86 | 87 | # This used to be... 88 | # "$formail" -bz -R 'From ' X-From-Line: -s "$@" < "${tmmf:-$mbox}" 89 | # ... but that just split too eagerly (saw >= 2 headers (w/o From ) - new mail) 90 | 91 | # XXX this is a bit naive, 'From ' w/o some following headers is also accepted 92 | # XXX but it would be considerable more complex to support caching of lines... 93 | perl -e 'my $el = 1; while () { if ($el && s/^From /X-From-Line: /) { 94 | open P, "|-", @ARGV or die "Executing @ARGV: $!\n"; 95 | } 96 | $el = (/^$/)? 1: 0; 97 | print P $_; 98 | } close P' "$@" < "${tmmf:-$mbox}" 99 | 100 | case $tmmf in '') ;; *) mv "$tmmf" "$mmmboxf" || : 101 | esac 102 | exit 103 | 104 | # Old thoughts about lockfile(1) usage.. now replaced with movemail(1). 105 | 106 | # If I ctrl-c when lockfile running, It seems to be possible 107 | # that lockfile gets created (but not deleted), if ctrl-c 108 | # hits between creating lock and trap instantiation. 109 | # trap cannot be instantiated earlyer as it would make possible 110 | # removing lock created by another process. 111 | # I wish there were way to determine (random) content in lockfile 112 | # then we could check the contents and if matches, then delete 113 | # the lockfile is content matches. 114 | 115 | # Internally this script uses 'movemail' to move mails from mbox file 116 | # (supposedly atomically) and 'formail' to split these mails to 117 | # separate streams each given to mda-cmd provided in command line... 118 | 119 | This program delivers all mail from mbox file to an MDA program given 120 | on command line. 121 | 122 | Options: 123 | -q keep quiet on non-fatal messages 124 | --movemail to-file move mail file to new location (to avoid 125 | concurrent updates) before continuing 126 | 127 | mbox-file the mbox file where mails are to be extracted 128 | mda-cmd mail delivery agent command which is executed for each 129 | individual email which are present in mbox-file 130 | [mda-args] optional arguments for mda-cmd 131 | 132 | Example: Shell wrapper which uses md5mda.sh as MDA. 133 | 134 | cat > mbox-to-md5mda.sh <&2 16 | die () { printf '%s\n' "$@"; exit 1; } >&2 17 | 18 | usage () { print %s\\n "Usage: $0 $cmd $*"; exit 1; } >&2 19 | 20 | x () { printf '+ %s\n' "$*" >&2; "$@"; } 21 | x_eval () { printf '+ %s\n' "$*" >&2; eval "$*"; } 22 | x_exec () { printf '+ %s\n' "$*" >&2; exec "$@"; die "exec '$*' failed"; } 23 | 24 | yesno () 25 | { 26 | echo 27 | printf '%s (yes/NO)? ' "$*" 28 | read ans 29 | echo 30 | case $ans in ([yY][eE][sS]) return 0; esac 31 | return 1 32 | } /dev/null` || : 70 | case $_xd0 in /*) d0=$_xd0; esac 71 | esac 72 | } 73 | 74 | cmd_mua () # Launch emacs as mail user agent. 75 | { 76 | case ${1-} in -y) ;; *) 77 | if ps x | grep 'emacs[ ].*[ ]notmuch' 78 | then echo 79 | echo '^^^ notmuch emacs mua already running ? ^^^' 80 | echo 81 | echo "to run another, add '-y' to the command line" 82 | echo 83 | exit 0 84 | fi 85 | esac 86 | if test "${DISPLAY-}" 87 | then set -x 88 | #exec nohup setsid emacs -g 108x38 -f notmuch >/dev/null 89 | exec nohup setsid emacs -f notmuch >/dev/null 90 | # the command above works best on linux interactive terminal 91 | else 92 | exec emacs -f notmuch 93 | fi 94 | } 95 | 96 | mbox2md5mda () 97 | { 98 | set_d0 99 | tmpfile=`cd $HOME/mail; LC_ALL=C exec perl -e ' 100 | mkdir q"wip" unless -d q"wip"; 101 | system q"mktemp", (sprintf q"wip/mbox-%x,XXXX", time)'` 102 | x $d0/mbox-to-mda.sh --movemail $HOME/mail/$tmpfile $MAIL \ 103 | $d0/md5mda.sh --cd $HOME/mail received wip log || 104 | : ::: mbox-to-mda.sh exited nonzero ::: : 105 | test -s $HOME/mail/$tmpfile || rm $HOME/mail/$tmpfile || : 106 | } 107 | 108 | cmd_new () # Import new mail. 109 | { 110 | #TIMEFORMAT is used by (bash) shell builtin, TIME by gnu /usr/bin/time.. 111 | TIMEFORMAT='%Us user %Ss system %P%% cpu %Rs total' 112 | TIME='%Us user, %Ss system, %P cpu, %E total (max resident mem %Mk)' 113 | export TIME 114 | ymdhms=`exec date +%Y%m%d-%H%M%S` 115 | case ${MAIL-} in '') # nothing to do when '' 116 | ;; *"[$IFS]"*) warn "'$MAIL' contains whitespace. Ignored." 117 | ;; /var/*mail/*) test ! -s $MAIL || mbox2md5mda 118 | ;; *) warn "Suspicious '$MAIL' path. Ignored." 119 | esac 120 | case ${1-} in -) exit; esac 121 | set -x 122 | time notmuch new --verbose | tee -a $HOME/mail/wip/new-$ymdhms.log 123 | read line < $HOME/mail/wip/new-$ymdhms.log 124 | case $line in 'No new mail.'*) rm $HOME/mail/wip/new-$ymdhms.log 125 | ;; *) mv $HOME/mail/wip/new-$ymdhms.log $HOME/mail/log/new-$ymdhms.log 126 | esac 127 | } 128 | 129 | cmd_help () # emulate notmuch help by fetching pages from notmuch wiki 130 | { 131 | test $# = 1 || exec notmuch help 132 | if command -v wget >/dev/null 133 | then fcl='wget -O-' 134 | elif command -v curl >/dev/null 135 | then fcl='curl -L' 136 | else 137 | die 'no wget nor curl available' 138 | fi 139 | set -x 140 | $fcl http://notmuchmail.org/manpages/notmuch-$1-1/ | \ 141 | sed -e '1,/id="content"/d' -e 's|<.>||g' -e 's|||g' \ 142 | -e 's/]*>//g' -e '/id="footer"/q' | less -s 143 | } 144 | 145 | cmd_frm () # Run frm-md5mdalog.pl. 146 | { 147 | set_d0 148 | case ${1-} in mvto:*) ;; *) exec $d0/frm-md5mdalog.pl "$@" ;; esac 149 | case $# in 1) usage "mvto:path" match-re ;; esac 150 | ddir=${1#*:} 151 | test -d "$ddir" || die "'$ddir': no such directory" 152 | shift 153 | echo 154 | tf=`exec mktemp`; trap "rm -f $tf" 0 INT TERM HUP QUIT 155 | $d0/frm-md5mdalog.pl -qvw "$@" | tee $tf |\ 156 | grep -v -e '^ ' -e '^$' || exit 0 157 | yesno "Move the messages listed above to '$ddir'" 158 | mv -fv `exec grep '^ */.*/' $tf` "$ddir" 159 | } 160 | 161 | cmd_startfemmda5 () # startfetchmail.sh using md5mda.sh mda 162 | { 163 | case $# in 4|5) ;; *) 164 | usage '[-I]' '(143|993)' '(keep|nokeep)' user server 165 | esac 166 | set_d0 167 | try_canon_d0 168 | cd $HOME 169 | test -d mail || mkdir mail 170 | case $d0 in $PWD/*) d0=${d0#$PWD/}; esac 171 | set -x 172 | exec $d0/startfetchmail.sh $@ \ 173 | "$d0/md5mda.sh --cd mail received wip log" 174 | } 175 | 176 | cmd_delete () # remove emails with tag deleted 177 | { 178 | case $#${1-} 179 | in '1!') 180 | x_eval 'notmuch search --output=files tag:deleted | xargs rm -v' 181 | exit 182 | ;; '1s') 183 | x notmuch search tag:deleted 184 | ;; 0) 185 | x notmuch address tag:deleted 186 | echo "Append '!' to the the end of the command line to do actual deletion." 187 | ;; *) 188 | usage "('!' | 's')" 189 | esac 190 | } 191 | 192 | # -- 193 | 194 | case ${1-} in -x) maysetx='set -x'; shift ;; *) maysetx= ;; esac 195 | 196 | bn=${0##*/} # basename 197 | 198 | # if $0 is not 'mm' (is it linked to another name), use it as cmd 199 | case $bn in mm) ;; *) set "$bn" "$@" ;; esac 200 | 201 | case ${1-} in '') 202 | echo 203 | echo Usage: $0 '[-x] [args]' 204 | echo 205 | echo $bn commands available: 206 | echo 207 | sed -n '/^cmd_[a-z0-9_]/ { s/cmd_/ /; s/ () [ #]*/ / 208 | s/$0/'"$bn"'/g; s/\(.\{14\}\) */\1/p; }' $0 209 | echo 210 | echo Commands may be abbreviated until ambiguous. 211 | echo 212 | exit 0 213 | ;; esac 214 | 215 | cm=$1; shift 216 | 217 | #case $cm in 218 | # d) cm=diff ;; 219 | #esac 220 | 221 | cc= cp= 222 | for m in `LC_ALL=C exec sed -n 's/^cmd_\([a-z0-9_]*\) (.*/\1/p' "$0"` 223 | do 224 | case $m in $cm) cp= cc=1 cmd=$cm; break 225 | ;; $cm*) cp=$cc; cc="$m $cc"; cmd=$m 226 | esac 227 | done 228 | 229 | test "$cc" || { echo $0: \'$cm\' -- command not found.; exit 1; } 230 | test ! "$cp" || { echo $0: \'$cm\' -- ambiguous command: matches $cc; exit 1; } 231 | unset cc cp cm 232 | readonly cmd 233 | 234 | #set -x 235 | $maysetx 236 | cmd_$cmd "$@" 237 | 238 | 239 | # Local variables: 240 | # mode: shell-script 241 | # sh-basic-offset: 8 242 | # tab-width: 8 243 | # End: 244 | # vi: set sw=8 ts=8 245 | -------------------------------------------------------------------------------- /wip/nottoomuch-emacs-mua.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # -*- mode: shell-script; sh-basic-offset: 8; tab-width: 8 -*- 3 | 4 | # Created: Fri 11 Jul 2014 00:12:59 EEST too 5 | # Last Modified: Wed 14 Oct 2015 22:55:05 +0300 too 6 | 7 | set -eu 8 | 9 | # this script uses some bash features. therefore attempt to ensure it is 10 | case ${BASH_VERSION-} in '') echo 'Not BASH!' >&2; exit 1; esac 11 | 12 | if test $# = 0 13 | then 14 | bn=${0##*/} 15 | exec sed -e '1,/^exit/d' -e "s/\$0/$bn/" "$0" 16 | exit not reached 17 | fi 18 | 19 | # escape: "expand" '\' as '\\' and '"' as '\"' 20 | # calling convention: escape -v var "$arg" (like in bash printf). 21 | # printf -v var and ${var//...} are bash features (the only one used...) 22 | escape () 23 | { 24 | local __escape_arg__=${3//\\/\\\\} 25 | printf -v $2 '%s' "${__escape_arg__//\"/\\\"}" 26 | } 27 | 28 | append_body_text () 29 | { 30 | bodycode=$bodycode$nl" (insert \"$body\n\")" 31 | body= 32 | } 33 | insert_body_file () 34 | { 35 | [ -f "$1" ] || { echo "$0: '$1': no such file" >&2; exit 1; } 36 | test "$body" = '' || append_body_text 37 | escape -v arg "$1" 38 | ## XXX yhdistä ? 39 | bodycode=$bodycode$nl" (forward-char (cadr (insert-file-contents \"${arg}\" nil 0 1048576)))" 40 | bodycode=$bodycode$nl" (unless (bolp) (insert \"\\n\"))" 41 | } 42 | 43 | 44 | # note: dollar-single i.e. $'...' is bash (ksh, zsh, mksh) but not dash feature 45 | #nl=$'\n' 46 | nl=' 47 | ' 48 | exec=exec nw= sep=' ' 49 | from= to= subject= cc= bcc= body= bodycode= 50 | var=to 51 | while test $# -gt 0 # note $# does not need quoting 52 | do 53 | arg=$1; shift 54 | case $arg 55 | in -n | --n ) 56 | [ "$arg" = "${1-}" ] && shift || { 57 | print_instead () { printf '\n%s\n' "$*"; } 58 | exec=print_instead 59 | continue 60 | } 61 | ;; -nw | --nw ) 62 | [ "$arg" = "${1-}" ] && shift || { 63 | nw=-nw 64 | continue 65 | } 66 | ;; -from | --from | [Ff]rom:) 67 | [ "$arg" = "${1-}" ] && shift || { 68 | message_goto='(messge-goto-from)' 69 | [ $# != 0 ] || continue 70 | from=$1; shift 71 | continue 72 | } 73 | ;; -to | -cc | -bcc | --to | --cc | --bcc | [Tt]o: | [Cc]c: | [Bb]cc: ) 74 | [ "$arg" = "${1-}" ] && shift || { 75 | var=${arg#-}; var=${var#-}; var=${var%:} 76 | eval "message_goto='(message-goto-$var)'" 77 | [ $# != 0 ] || continue 78 | arg=$1; eval "sep=\"\${$var:+, }\""; shift 79 | } 80 | ;; -subject | --subject | [Ss]ubject: ) 81 | [ "$arg" = "${1-}" ] && shift || { 82 | var=subject 83 | eval "message_goto='(message-goto-$var)'" 84 | [ $# != 0 ] || continue 85 | var=subject arg=$1; eval "sep=\"\${$var:+ }\""; shift 86 | } 87 | ;; -file | --file ) 88 | [ "$arg" = "${1-}" ] && shift || { 89 | message_goto= 90 | [ $# != 0 ] || continue 91 | insert_body_file "$1"; shift 92 | continue; 93 | } 94 | ;; -text | --text ) 95 | [ "$arg" = "${1-}" ] && shift || { 96 | message_goto= 97 | [ $# != 0 ] || continue 98 | var=body arg=$1; eval "sep=\"\${$var:+ }\""; shift 99 | } 100 | ;; -body | --body ) 101 | [ "$arg" = "${1-}" ] && shift || { 102 | exec >&2 103 | echo 104 | echo "'$arg' is not supported; use -file or -text instead" 105 | echo 106 | exit 1 107 | } 108 | esac 109 | escape -v arg "$arg" 110 | eval "$var=\${$var:+\$$var$sep}\$arg" 111 | sep=' ' 112 | done 113 | 114 | [ "$body" = '' ] || append_body_text 115 | 116 | elisp="\ 117 | ${cc:+$nl (message-goto-cc) (insert \"$cc\")}\ 118 | ${bcc:+$nl (message-goto-bcc) (insert \"$bcc\")}\ 119 | ${bodycode:+$nl (message-goto-body)$bodycode}" 120 | 121 | escape -v _pwd "$PWD" 122 | 123 | exec_mua () { $exec ${EMACS:-emacs} $nw --eval "$@"; } 124 | 125 | elisp_start="prog1 'done (require 'notmuch)" 126 | 127 | if [ "$to" = '' -o "$to" = . ] && [ "$subject" = '' ] && [ "$elisp" = '' ] 128 | then 129 | exec_mua "($elisp_start (notmuch-hello) (cd \"$_pwd\"))" 130 | else 131 | [ "$from" != '' ] && oh="'((From . \"$from\"))" || oh=' nil' 132 | 133 | if [ "$subject" == '' ] 134 | then subject=nil 135 | message_goto=' (message-goto-subject)' 136 | else 137 | subject=\"$subject\" 138 | fi 139 | if [ "$to" == '' ] 140 | then to=nil 141 | message_goto=' (message-goto-to)' 142 | else 143 | to=\"$to\" 144 | fi 145 | exec_mua "(${elisp_start} 146 | ;;(setq message-exit-actions '(save-buffers-kill-terminal)) 147 | (notmuch-mua-mail ${to} ${subject} 148 | ${oh} nil (notmuch-mua-get-switch-function)) 149 | (cd \"$_pwd\")\ 150 | ${elisp}${nl} (set-buffer-modified-p nil)${message_goto})" 151 | fi 152 | exit 153 | 154 | Execute `$0 .` to run just notmuch-hello 155 | 156 | Otherwise enter content on command line: initial active option is '-to' 157 | 158 | The list of options are: 159 | -nw (no window) -n (dry run) 160 | -to -subject -cc -bcc -text -file -from 161 | 162 | In all options -- -prefixed alternatives are also recognized. 163 | In to:, subject:, cc:, bcc:, and from: this format is also accepted. 164 | 165 | Duplicating option (e.g. -to -to) will "escape" it -- produce just 166 | one of it as content of the currently active option. 167 | 168 | If option is last argument on command line it just sets final 169 | cursor position (provided that -to and -subject are set). 170 | 171 | Currently --opt=val is not supported, because it is SMOP. Maybe later... 172 | 173 | Example: 174 | $0 user@example.org -subject give me some -cc -cc 175 | . 176 | -------------------------------------------------------------------------------- /wip/nottoomuch-gmail-emacs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | :; exec "${EMACS:-emacs}" --debug-init --load "$0" "$@"; exit 4 | 5 | ;; wrapper to set up notmuch emacs MUA, configured emacs smtpmail 6 | ;; to send email via gmail 7 | 8 | ;; you may need https://myaccount.google.com/lesssecureapps 9 | ;; (and possibly https://accounts.google.com/DisplayUnlockCaptcha) 10 | ;; (working alternative not requiring the above would be nice) 11 | 12 | ;; when emacs suggest to save authinfo, press 'e' to edit and remove 13 | ;; password. alternatively, if you know how, save authinfo.gpg 14 | 15 | (require 'smtpmail) 16 | 17 | (eval-after-load "notmuch" 18 | (lambda () 19 | (setq smtpmail-smtp-server "smtp.gmail.com" 20 | smtpmail-smtp-service 587 21 | smtpmail-stream-type 'starttls 22 | smtpmail-debug-info t 23 | smtpmail-debug-verb t 24 | message-send-mail-function 'message-smtpmail-send-it))) 25 | 26 | (load "notmuch") 27 | 28 | (notmuch-hello) 29 | 30 | ;; Local Variables: 31 | ;; mode: emacs-lisp 32 | ;; End: 33 | -------------------------------------------------------------------------------- /wip/nottoomuch-wrapper.c: -------------------------------------------------------------------------------- 1 | #if 0 /* -*- mode: c; c-file-style: "stroustrup"; tab-width: 8; -*- 2 | # 3 | # Enter: sh nottoomuch-wrapper.c [/path/to/]notmuch 4 | # in order to compile this program. 5 | # Tip: enter /bin/echo as notmuch command when testing code changes... 6 | set -eu 7 | case ${1-} in '') echo "Usage: sh $0 [/path/to/]notmuch" >&2; exit 1; esac 8 | notmuch=$1; shift; trg=`exec basename "$0" .c`; rm -f "$trg" 9 | WARN="-Wall -Wstrict-prototypes -Winit-self -Wformat=2" # -pedantic 10 | WARN="$WARN -Wcast-align -Wpointer-arith " # -Wfloat-equal #-Werror 11 | WARN="$WARN -Wextra -Wwrite-strings -Wcast-qual -Wshadow" # -Wconversion 12 | WARN="$WARN -Wmissing-include-dirs -Wundef -Wbad-function-cast -Wlogical-op" 13 | WARN="$WARN -Waggregate-return -Wold-style-definition" 14 | WARN="$WARN -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls" 15 | WARN="$WARN -Wnested-externs -Winline -Wvla -Woverlength-strings -Wpadded" 16 | case ${1-} in '') set x -O2; shift; esac 17 | #case ${1-} in '') set x -ggdb; shift; esac 18 | set -x 19 | exec ${CC:-gcc} --std=c99 $WARN "$@" -o "$trg" "$0" -DNOTMUCH="\"$notmuch\"" 20 | exit $? 21 | */ 22 | #endif 23 | /* 24 | * $ nottoomuch-wrapper.c $ 25 | * 26 | * Created: Tue 13 Mar 2012 12:34:26 EET too 27 | * Last modified: Wed 19 May 2021 21:15:19 +0300 too 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #define null ((void *)0) 44 | 45 | #define WriteCS(f, s) write((f), ("" s ""), sizeof (s) - 1) 46 | 47 | time_t gt; 48 | 49 | static const char * get_time(const char * str, long * timep) 50 | { 51 | unsigned int mult; 52 | char * endptr; 53 | long t; 54 | 55 | long val = strtol(str, &endptr, 10); 56 | 57 | /* no digits at all */ 58 | if (endptr == str) 59 | return null; 60 | /* what to do with negative value... nothing */ 61 | if (val < 0) 62 | return null; 63 | 64 | switch (*endptr) { 65 | case 's': mult = 1; endptr++; break; 66 | case 'm': mult = 60; endptr++; break; 67 | case 'h': mult = 3600; endptr++; break; 68 | case 'd': mult = 86400; endptr++; break; 69 | case 'w': mult = 7 * 86400; endptr++; break; 70 | case 'M': mult = 30 * 86400; endptr++; break; 71 | case 'y': mult = 365 * 86400; endptr++; break; 72 | case '.': 73 | case ' ': 74 | case '\0': 75 | if (val < 1000) { 76 | mult = 60; 77 | break; 78 | } 79 | *timep = val; 80 | return endptr; 81 | default: 82 | return null; 83 | } 84 | 85 | t = (gt - (mult * val)); 86 | if (t < 0) 87 | t = 0; 88 | *timep = t; 89 | 90 | return endptr; 91 | } 92 | 93 | char spcstr[2] = " "; 94 | char nlstr[2] = "\n"; 95 | 96 | int main(int argc, char * argv[]) 97 | { 98 | char buf[16384]; 99 | char * p = buf; 100 | int i, tl = 0; 101 | int fd = 0; /* avoid compiler warning */ 102 | 103 | if (argc < 3) { 104 | execvp(NOTMUCH, argv); 105 | /* not reached */ 106 | return 1; 107 | } 108 | 109 | gt = time(null); 110 | 111 | #if 0 /* log all commands from now on... */ 112 | if (strcmp(argv[1], "tag") == 0 || strcmp(argv[1], "search") == 0 || 113 | strcmp(argv[1], "show") == 0 || strcmp(argv[1], "reply") == 0) 114 | #endif 115 | { 116 | char * home = getenv("HOME"); 117 | if (home) { 118 | sprintf(buf, "%s/nottoomuch-wrapper.log", home); 119 | fd = open(p, O_WRONLY|O_CREAT|O_APPEND, 0644); 120 | if (fd >= 0) { 121 | struct tm * tm = localtime(>); 122 | tl = strftime(buf, 1024, "%Y-%m-%d (%a) %H:%M:%S:", tm); 123 | p += tl; 124 | } 125 | } 126 | } 127 | 128 | /* XXX ugly hack (to do date expansion at 2012-03), got by trial & errror */ 129 | for (i = 2; i < argc && argv[i]; i++) { 130 | const char * arg = argv[i]; 131 | char * q; 132 | int prefixlen = 0; 133 | char * s = p; 134 | 135 | while ( (q = strstr(arg, "..")) != null) { 136 | /* split into prefix(len), ltd, rtd, postdata */ 137 | long st = 0, tt = 0; 138 | 139 | if (q - arg > 0) { 140 | char * r = q - 1; 141 | while (r != arg) { 142 | if (isspace(*r)) { 143 | prefixlen = r - arg + 1; 144 | break; 145 | } 146 | r--; 147 | } 148 | if (r != arg && ! isspace(*r)) { 149 | s = p; 150 | break; /* XXX no conversions on 'error's */ 151 | } 152 | if (isspace(*r)) 153 | r++; 154 | 155 | if (*r != '.') { 156 | const char * ep = get_time(r, &st); 157 | if (ep != q) { 158 | s = p; 159 | break; /* XXX no conversions on 'error's */ 160 | } 161 | } 162 | } 163 | /* right end of .. */ 164 | q += 2; 165 | const char *ep; 166 | if (*q == '\0' || isspace(*q)) 167 | ep = q; 168 | else { 169 | ep = get_time(q, &tt); 170 | if (ep == null || (*ep != '\0' && ! isspace(*ep))) { 171 | s = p; 172 | break; /* XXX no conversions on 'error's */ 173 | } 174 | } 175 | 176 | /* if (buf + 200 - s - prefixlen - 100 < 0) { */ 177 | if (buf + sizeof buf - s - prefixlen - 100 < 0) { 178 | s = p; 179 | break; /* XXX data does not fit */ 180 | } 181 | 182 | if (prefixlen) { 183 | memcpy(s, arg, prefixlen); 184 | s += prefixlen; 185 | } 186 | prefixlen = 1; 187 | arg = ep; 188 | if (st) { 189 | int l = snprintf(s, 20, "%ld", st); 190 | s += l; 191 | } 192 | *s++ = '.'; *s++ = '.'; 193 | if (tt) { 194 | int l = snprintf(s, 20, "%ld", tt); 195 | s += l; 196 | } 197 | } 198 | if (s != p) { 199 | if (*arg) { 200 | int l = strlen(arg); 201 | memcpy(s, arg, l); 202 | s += l; 203 | } 204 | *s++ = '\0'; 205 | #if 0 206 | printf("Mangled '%s' to '%s'\n", argv[i], p); 207 | #endif 208 | argv[i] = p; 209 | p = s; 210 | } 211 | } 212 | if (tl > 0) { 213 | struct iovec iov[256]; 214 | iov[0].iov_base = buf; 215 | iov[0].iov_len = tl; 216 | for (i = 1; i < 255; i += 2) { 217 | char * s = argv[i/2 + 1]; 218 | if (!s) 219 | break; 220 | iov[i].iov_base = spcstr; /* " "; */ 221 | iov[i].iov_len = 1; 222 | iov[i+1].iov_base = s; 223 | iov[i+1].iov_len = strlen(s); 224 | } 225 | iov[i].iov_base = nlstr; /* "\n"; */ 226 | iov[i++].iov_len = 1; 227 | writev(fd, iov, i); 228 | close(fd); 229 | } 230 | execvp(NOTMUCH, argv); 231 | return 1; 232 | } 233 | --------------------------------------------------------------------------------