1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2 
3 #include "retention.h"
4 
5 #include "utf.h"
6 #include "query.h"
7 #include "search.h"
8 #include "selector.h"
9 #include "mailbox.h"
10 #include "transaction.h"
11 #include "searchsyntax.h"
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 
16 
17 class RetainMessagesData
18     : public Garbage
19 {
20 public:
RetainMessagesData()21     RetainMessagesData()
22         : state( 0 ), duration( 0 ), m( 0 ), selector( 0 ), t( 0 )
23     {}
24 
25     int state;
26     EString action;
27     int duration;
28     Mailbox * m;
29     Selector * selector;
30     Transaction * t;
31 };
32 
33 
34 /*! \class RetainMessages retention.h
35     Sets mailbox retention policies.
36 */
37 
38 static AoxFactory<RetainMessages>
39 a( "retain", "messages", "Create a new message retention policy",
40    "    Synopsis: aox retain mail <days> [mailbox] [search]\n\n"
41    "    This command creates a retention policy: mail is retained for as many\n"
42    "    days as specified (by either a positive integer or \"forever\"). An\n"
43    "    optional mailbox name and search expression may be specified to limit\n"
44    "    the scope of the policy to matching messages.\n" );
45 
46 static AoxFactory<RetainMessages>
47 a2( "retain", "mail", &a );
48 
49 
RetainMessages(EStringList * args,bool retain)50 RetainMessages::RetainMessages( EStringList * args, bool retain )
51     : AoxCommand( args ), d( new RetainMessagesData )
52 {
53     if ( retain )
54         d->action = "retain";
55     else
56         d->action = "delete";
57 }
58 
59 
execute()60 void RetainMessages::execute()
61 {
62     if ( d->state == 0 ) {
63         parseOptions();
64 
65         EString ds( next() );
66         if ( ds != "forever" ) {
67             bool ok = false;
68             d->duration = ds.number( &ok );
69             if ( !ok )
70                 error( "Invalid retention duration: " + ds );
71         }
72 
73         if ( d->action == "delete" && d->duration == 0 )
74             error( "'delete after forever' is not a valid policy." );
75 
76         d->state = 1;
77 
78         database( true );
79         Mailbox::setup();
80     }
81 
82     if ( d->state == 1 ) {
83         if ( !choresDone() )
84             return;
85 
86         // Is a mailbox name specified?
87 
88         if ( !args()->isEmpty() ) {
89             EString s( *args()->first() );
90             if ( s[0] == '/' ) {
91                 Utf8Codec c;
92                 UString m = c.toUnicode( s );
93 
94                 if ( !c.valid() )
95                     error( "Encoding error in mailbox name: " + c.error() );
96                 d->m = Mailbox::find( m, true );
97                 if ( !d->m )
98                     error( "No such mailbox: " + m.utf8() );
99 
100                 (void)args()->shift();
101             }
102         }
103 
104         // Are any search terms specified?
105 
106         if ( !args()->isEmpty() ) {
107             d->selector = parseSelector( args() );
108             if ( !d->selector )
109                 exit( 1 );
110             d->selector->simplify();
111         }
112 
113         end();
114 
115         d->state = 2;
116     }
117 
118     if ( d->state == 2 ) {
119         d->t = new Transaction( this );
120         Query * q;
121         q = new Query( "delete from retention_policies "
122                        "where mailbox=$1 and action=$2 and selector=$3", 0 );
123         if ( d->m )
124             q->bind( 1, d->m->id() );
125         else
126             q->bindNull( 1 );
127         q->bind( 2, d->action );
128         if ( d->selector )
129             q->bind( 3, d->selector->string() );
130         else
131             q->bindNull( 3 );
132         d->t->enqueue( q );
133 
134         q = new Query( "insert into retention_policies "
135                        "(action, duration, mailbox, selector) "
136                        "values ($1, $2, $3, $4)", 0 );
137         q->bind( 1, d->action );
138         q->bind( 2, d->duration );
139         if ( d->m )
140             q->bind( 3, d->m->id() );
141         else
142             q->bindNull( 3 );
143         if ( d->selector )
144             q->bind( 4, d->selector->string() );
145         else
146             q->bindNull( 4 );
147         d->t->enqueue( q );
148 
149         d->t->commit();
150         d->state = 3;
151     }
152 
153     if ( d->state == 3 ) {
154         if ( !d->t->done() )
155             return;
156 
157         if ( d->t->failed() )
158             error( "Couldn't set retention policy: " + d->t->error() );
159     }
160 
161     finish();
162 }
163 
164 
165 static AoxFactory<DeleteMessages>
166 b( "delete", "messages", "Create a new message deletion policy",
167    "    Synopsis: aox delete mail <days> [mailbox] [search]\n\n"
168    "    This command creates a deletion policy: mail is deleted after as many\n"
169    "    days as specified (by a positive integer). An optional mailbox name and\n"
170    "    search expression may be specified to limit the scope of the policy to\n"
171    "    matching messages.\n" );
172 
173 static AoxFactory<DeleteMessages>
174 b2( "delete", "mail", &b );
175 
176 /*! \class DeleteMessages retention.h
177     Creates a mail deletion policy through a suitably-inverted
178     RetainMessages object.
179 */
180 
DeleteMessages(EStringList * e)181 DeleteMessages::DeleteMessages( EStringList * e )
182     : RetainMessages( e, false )
183 {
184 }
185 
186 
187 class ShowRetentionData
188     : public Garbage
189 {
190 public:
ShowRetentionData()191     ShowRetentionData()
192         : state( 0 ), q( 0 )
193     {}
194 
195     int state;
196     Query * q;
197 };
198 
199 
200 static AoxFactory<ShowRetention>
201 c( "show", "retention", "Display mailbox retention policies",
202    "    Synopsis: aox show retention [mailbox]\n\n"
203    "    This command displays the retention policies related to the\n"
204    "    specified mailbox, or all existing policies if no mailbox is\n"
205    "    specified.\n" );
206 
207 
208 /*! \class ShowRetention retention.h
209     Displays mailbox retention policies created with "set retention".
210 */
211 
ShowRetention(EStringList * args)212 ShowRetention::ShowRetention( EStringList * args )
213     : AoxCommand( args ), d( new ShowRetentionData )
214 {
215 }
216 
execute()217 void ShowRetention::execute()
218 {
219     if ( d->state == 0 ) {
220         parseOptions();
221 
222         d->state++;
223         database( true );
224         Mailbox::setup();
225     }
226 
227     if ( d->state == 1 ) {
228         if ( !choresDone() )
229             return;
230 
231         Mailbox * m = 0;
232         if ( !args()->isEmpty() ) {
233             EString s( *args()->first() );
234             Utf8Codec c;
235             UString name = c.toUnicode( s );
236 
237             if ( !c.valid() )
238                 error( "Encoding error in mailbox name: " + c.error() );
239             m = Mailbox::find( name, true );
240             if ( !m )
241                 error( "No such mailbox: " + name.utf8() );
242 
243             (void)args()->shift();
244         }
245 
246         end();
247 
248         EString q(
249             "select m.name, action, duration, selector, rp.id "
250             "from retention_policies rp left join mailboxes m "
251             "on (m.id=rp.mailbox)"
252         );
253 
254         if ( m )
255             q.append( " where m.name=any($1::text[])" );
256 
257         q.append( " order by lower(name) asc," // mailbox name, '/' first
258                   " action desc," // retain before delete
259                   " duration asc," // and increasing time
260                   " rp.id" ); // and as tiebreaker, older policy first
261 
262         d->q = new Query( q, this );
263 
264         if ( m ) {
265             IntegerSet ids;
266             while ( m ) {
267                 if ( m->id() )
268                     ids.add( m->id() );
269                 m = m->parent();
270             }
271             d->q->bind( 1, ids );
272         }
273 
274         d->state++;
275         d->q->execute();
276     }
277 
278     UString last;
279     while ( d->q->hasResults() ) {
280         Row * r = d->q->nextRow();
281 
282         UString name( r->getUString( "name" ) );
283 
284         if ( name != last ) {
285             printf( "%s:\n", name.utf8().cstr() );
286             last = name;
287         }
288 
289         printf( "  %s %d days, policy %d:\n",
290                 r->getEString( "action" ).cstr(),
291                 r->getInt( "duration" ),
292                 r->getInt( "id" ) );
293         if ( r->isNull( "selector" ) ) {
294             printf( "    Unconditional\n" );
295         }
296         else {
297             Selector * s = Selector::fromString( r->getEString( "selector" ) );
298             if ( s )
299                 dumpSelector( s, 2 );
300         }
301     }
302 
303     if ( !d->q->done() )
304         return;
305 
306     if ( d->q->failed() )
307         error( "Couldn't fetch retention policies: " + d->q->error() );
308 
309     finish();
310 }
311