summaryrefslogtreecommitdiff
path: root/document.rest.txt
blob: 11ed647239afda012de105d91d24b2a2eda615e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
==================================================================
 Un sistema automatico di incartellamento della posta elettronica
==================================================================
 
ovvero, come io gestisco la mia posta elettronica
=================================================
 
:Author: dakkar@thenautilus.net
:Date: 2009-01-10
 
Il mio archivio di posta elettronica conta, al momento, 74711
messaggi. Sono iscritto a circa 40 mailing list (per la maggior parte
di esse non tengo copia di tutti i messaggi). Come faccio a
raccapezzarmici?
 
Innanzi tutto, ogni mailing list ha una sua cartella, e |procmail|_ è
sufficiente per gestire questa parte; ad esempio, per mandare nella
cartella apposita i messaggi della lista `mongers`::
 
  # mongers
  :0:
  * (^From|^TO|List-Id:).*mongers[@.](lists\.)?perl\.it
  .mail.Perl.mongers/
 
Come potreste notare, se aveste perso un eccesso di tempo a studiare
|procmail|, la mia posta è archiviata in formato |maildir|_.
 
Altre regole |procmail| mettono in cartelle apposite i messaggi
provenienti da mittenti particolari (gente che conosco, negozi, etc).
 
Accedo alla posta tramite |dovecot|_ (server) e |claws|_ (client).
 
*Non* uso una cartella "messaggi inviati": |claws| è configurato in
modo da salvare i messaggi che scrivo nella cartella che "sto
guardando" [#savehere]_. In pratica, il risultato è che le mie
risposte stanno nella stessa cartella dei messaggi a cui
rispondono. In questo modo ho sempre i thread *interamente* visibili.
 
.. [#savehere] Tasto destro sulla radice delle cartelle (nella lista
   cartelle), "Properties…", sezione "Compose", marcare "Save copy of
   outgoing messages to this folder instead of Sent", marcare anche il
   chekcbox a destra ("Apply to subfolders"), pulsante "Apply".
 
A questo punto, se le mie cartelle fossero tutte e sole quelle in cui
|procmail| mette i messaggi, sarei a posto. "Peccato" che mi servano
anche cartelle di altro genere: ad esempio, per argomento. I messaggi
finiscono in queste cartelle solo perché ce li sposto io, manualmente,
dal client. Per completare l'automatizzazione del tutto, manca un
componente: un sistema per cui un messaggio di risposta che arriva,
finisca nella stessa cartella in cui sta il messaggio a cui risponde,
anche se quest'ultimo è stato spostato a mano.
 
Incartellamento delle risposte
------------------------------
 
Come dice la |rfc2822|_, se un messaggio è una risposta, dovrà avere
un campo (nell'header) ``In-Reply-To:`` il cui valore sia il "message id" del
messaggio a cui sta rispondendo. Inoltre, il campo ``References:``
contiene i "message id" di tutta una sequenza di messaggi del thread
[#thread]_.
 
.. [#thread] Sì, lo so che è un po' più complicato di così, ma questa
   è un'approssimazione sufficiente per i nostri fini. Chi vuole i
   dettagli può leggersi la |rfc2822|_ e |jwz|_.
 
Di conseguenza, per sapere in quale cartella mettere un messaggio,
basta trovare in quale cartella stia il messaggio il cui campo
``Message-ID:`` abbia lo stesso valore di uno dei "message id"
riferiti nel messaggio in arrivo. Ovviamente, se il messaggio in
arrivo riferisce più di un messaggio, potremmo avere una situazione
ambigua; per fortuna, per come sono usati di solito i campi dei
riferimenti, l'ambiguità si risolve esaminando prima il valore in
``In-Reply-To:``, poi quelli in ``References:`` dall'ultimo al primo
(al solito, i dettagli sono nella |rfc2822|). Che esista un solo
messaggio con un dato "message id" è vero a meno che io non abbia due
copie di uno stesso messaggio; possiamo ignorare il problema.
 
Un'implementazione banale consisterebbe in poco più che un ``grep``
ricorsivo. Funziona, ma è un po' lenta: rischia di impiegare svariati
secondi, e andrebbe eseguita per ciascun messaggio che arriva.
 
Un metodo più furbo sta nel tenersi un indice che mappi i "message id"
alle cartelle. Per tenerlo aggiornato, però, dobbiamo tenere conto che
nuovi messaggi vengono aggiunti (o perché arrivano, o perché sono
copie dei messaggi che io invio) e quelli esistenti spostati. Ci serve
un modo per intercettare questi cambiamenti, e aggiornare l'indice.
 
Il problema di notare modifiche in una cartella è tanto comune (serve,
ad esempio, ai file manager) che Linux fornisce una soluzione a
livello di kernel: |inotify|_. Siccome usarlo non è esattamente
banale, mi sono appoggiato a |inotify-tools|_.
 
A questo punto ho tutti i pezzi che servono per scrivere il server che
gestisca l'indice dei messaggi:
 
- all'inizio, il server si scandisce l'archivio di posta e si segna in
  quale cartella sta ciascun "message id"
- nel frattempo, usa ``inotifywait`` (parte di |inotify-tools|) per
  tenere traccia delle modifiche
- il server ascolta su una socket, e quando gli viene inviato l'header
  di un messaggio, ne estrae i campi dei riferimenti, e risponde con
  il nome della cartella in cui deve andare questo messaggio (o nulla,
  se il messaggio non è una risposta, o non abbiamo il messaggio cui
  risponde)
 
Avendo questo, si scrive un semplice client::
 
  #!/bin/bash
  netcat localhost 9000 | tr -d '\015\012'
 
e due regole |procmail|::
 
 :0 hi
 AUTOFOLDER=| ~/bin/find_folder
 
 :0
 * AUTOFOLDER ?? ^\.mail\.
 $AUTOFOLDER/
 
e i messaggi finiscono automaticamente dove devono.
 
.. warning:: |procmail| e ``Out of memory``
 
   Alcune versioni di |procmail| (di sicuro la 3.22-r7 distribuita in
   Gentoo) hanno un bug che causa un'allocazione esagerata di memoria
   quando si tenta di catturare l'output di un programma in una
   variabile (come nelle regole precedenti). Se vi capita, controllate
   se la vostra distribuzione ha una versione corretta; altrimenti,
   potete sempre applicare la patch_.
 
Come è fatto il server
----------------------
 
Dalla descrizione del server si può notare come esso debba occuparsi
di svariate funzioni contemporaneamente: scandire l'archivio, ricevere
le notifiche di cambiamenti, rispondere alle richieste del client. In
Perl ci sono sostanzialmente due strategie per affrontare il problema:
 
* gestione asincrona degli eventi (ad esempio con |poe|_)
* multi-threading
 
A me i thread, pure con i piccoli problemi che hanno in Perl,
risultano più "naturali". Ho perciò strutturato il server in questo
modo:
 
- un thread riceve le notifiche da ``inotifywait``, e aggiorna l'indice
- un thread scandisce l'archivio, e aggiorna l'indice (questo thread
  termina una volta scandito tutto l'indice)
- il thread principale si sospende sulla socket
- quando arriva una richiesta sulla socket, un nuovo thread viene
  creato per servirla
 
Il programma_ non è molto complesso. Il grosso del lavoro è fatto da
|email-simple|_ (per estrarre i campi dai messaggi) e |file-next|_
(per scandire l'archivio).
 
Un paio di note sulle strutture dati: per gestire correttamente
aggiunte, rimozioni e spostamenti, e non confondersi con le manovre
che fa |dovecot| [#manovre]_, uso due hash: ``%files2id`` mappa dalla
coppia mailbox-nomefile al "message id" (ovvero,
``$files2id{$mailbox}->{$file}=$id``); ``%id2mailbox`` mappa da un
"message id" a una *lista* di mailbox. Sì, una lista: questo sia per
non avere errori in caso di messaggi duplicati, sia per le menzionate
manovre di |dovecot|. Il server restituisce sempre l'ultima mailbox
nella lista, che è tenuta in ordine cronologico.
 
Inoltre, siccome queste strutture sono lette e scritte da thread
diversi, è necessario assicurare la mutua esclusione degli accessi
tramite lock.
 
.. [#manovre] Ad esempio, per spostare un messaggio, prima lo duplica
   nella cartella di destinazione, poi lo cancella da quella di
   origine.
 
Altro materiale
---------------
 
* il programma_, colorato da |ppi|
* il sorgente_ nudo e crudo
* il pod_
 
Licenza
-------
 
Questo software è protetto dalle leggi sul diritto d'autore, copyright
(c) 2009 Gianni Ceccarelli.
 
Questo programma è software libero; potete ri-distribuirlo e/o
modificarlo secondo i termini della GNU Affero General Public License,
pubblicata dalla Free Software Foundation, versione 3.
 
.. |procmail| replace:: ``procmail``
.. _procmail: http://www.procmail.org/
.. |maildir| replace:: ``MailDir++``
.. _maildir: http://www.inter7.com/courierimap/README.maildirquota.html
.. |dovecot| replace:: ``dovecot``
.. _dovecot: http://www.dovecot.org/
.. |claws| replace:: `Claws Mail`
.. _claws: http://www.claws-mail.org/
.. |rfc2822| replace:: `RFC` 2822
.. _rfc2822: http://www.faqs.org/rfcs/rfc2822.html
.. |jwz| replace:: l'algoritmo di threading di Jamie Zawinski
.. _jwz: http://www.jwz.org/doc/threading.html
.. |inotify| replace:: ``inotify``
.. _inotify: http://en.wikipedia.org/wiki/Inotify
.. |inotify-tools| replace:: ``inotify-tools``
.. _`inotify-tools`: http://inotify-tools.sourceforge.net/
.. |poe| replace:: ``POE``
.. _poe: http://search.cpan.org/dist/POE/
.. |email-simple| replace:: ``Email::Simple``
.. _`email-simple`: http://search.cpan.org/dist/Email-Simple/
.. |file-next| replace:: ``File::Next``
.. _`file-next`: http://search.cpan.org/dist/File-Next/
.. |ppi| replace:: ``PPI::HTML``
.. _ppi: http://search.cpan.org/dist/PPI-HTML/
.. _programma: maildir-indexer.pl.html
.. _sorgente: maildir-indexer.pl
.. _pod: maildir-indexer.pod.html
.. _patch: http://bugs.gentoo.org/200006