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