]> diplodocus.org Git - nmh/blob - uip/refile.c
copyip.c: Move interface to own file.
[nmh] / uip / refile.c
1 /* refile.c -- move or link message(s) from a source folder
2 * -- into one or more destination folders
3 *
4 * This code is Copyright (c) 2002, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
7 */
8
9 #include "h/mh.h"
10 #include "sbr/m_draft.h"
11 #include "sbr/m_convert.h"
12 #include "sbr/getfolder.h"
13 #include "sbr/folder_read.h"
14 #include "sbr/folder_free.h"
15 #include "sbr/folder_delmsgs.h"
16 #include "sbr/folder_addmsg.h"
17 #include "sbr/context_save.h"
18 #include "sbr/context_replace.h"
19 #include "sbr/context_find.h"
20 #include "sbr/ambigsw.h"
21 #include "sbr/path.h"
22 #include "sbr/print_version.h"
23 #include "sbr/print_help.h"
24 #include "sbr/seq_getnum.h"
25 #include "sbr/seq_add.h"
26 #include "sbr/arglist.h"
27 #include "sbr/error.h"
28 #include "h/done.h"
29 #include "h/utils.h"
30 #include "sbr/m_maildir.h"
31 #include "sbr/m_mktemp.h"
32 #include <fcntl.h>
33
34 #define REFILE_SWITCHES \
35 X("draft", 0, DRAFTSW) \
36 X("link", 0, LINKSW) \
37 X("nolink", 0, NLINKSW) \
38 X("preserve", 0, PRESSW) \
39 X("nopreserve", 0, NPRESSW) \
40 X("retainsequences", 0, RETAINSEQSSW) \
41 X("noretainsequences", 0, NRETAINSEQSSW) \
42 X("unlink", 0, UNLINKSW) \
43 X("nounlink", 0, NUNLINKSW) \
44 X("src +folder", 0, SRCSW) \
45 X("file file", 0, FILESW) \
46 X("rmmproc program", 0, RPROCSW) \
47 X("normmproc", 0, NRPRCSW) \
48 X("version", 0, VERSIONSW) \
49 X("help", 0, HELPSW) \
50
51 #define X(sw, minchars, id) id,
52 DEFINE_SWITCH_ENUM(REFILE);
53 #undef X
54
55 #define X(sw, minchars, id) { sw, minchars, id },
56 DEFINE_SWITCH_ARRAY(REFILE, switches);
57 #undef X
58
59 static char maildir[BUFSIZ];
60
61 struct st_fold {
62 char *f_name;
63 struct msgs *f_mp;
64 };
65
66 /*
67 * static prototypes
68 */
69 static void opnfolds (struct msgs *, struct st_fold *, int);
70 static void clsfolds (struct st_fold *, int);
71 static void remove_files (int, char **);
72 static int m_file (struct msgs *, char *, int, struct st_fold *, int, int, int);
73 static void copy_seqs (struct msgs *, int, struct msgs *, int);
74
75
76 int
77 main (int argc, char **argv)
78 {
79 bool linkf = false;
80 bool preserve = false;
81 bool retainseqs = false;
82 int filep = 0;
83 int foldp = 0, isdf = 0;
84 bool unlink_msgs = false;
85 int i, msgnum;
86 char *cp, *folder = NULL, buf[BUFSIZ];
87 char **argp, **arguments;
88 char *filevec[NFOLDERS + 2];
89 char **files = &filevec[1]; /* leave room for remove_files:vec[0] */
90 struct st_fold folders[NFOLDERS + 1];
91 struct msgs_array msgs = { 0, 0, NULL };
92 struct msgs *mp;
93
94 if (nmh_init(argv[0], true, true)) { return 1; }
95
96 arguments = getarguments (invo_name, argc, argv, 1);
97 argp = arguments;
98
99 /*
100 * Parse arguments
101 */
102 while ((cp = *argp++)) {
103 if (*cp == '-') {
104 switch (smatch (++cp, switches)) {
105 case AMBIGSW:
106 ambigsw (cp, switches);
107 done (1);
108 case UNKWNSW:
109 die("-%s unknown\n", cp);
110
111 case HELPSW:
112 snprintf (buf, sizeof(buf), "%s [msgs] [switches] +folder ...",
113 invo_name);
114 print_help (buf, switches, 1);
115 done (0);
116 case VERSIONSW:
117 print_version(invo_name);
118 done (0);
119
120 case LINKSW:
121 linkf = true;
122 continue;
123 case NLINKSW:
124 linkf = false;
125 continue;
126
127 case PRESSW:
128 preserve = true;
129 continue;
130 case NPRESSW:
131 preserve = false;
132 continue;
133
134 case RETAINSEQSSW:
135 retainseqs = true;
136 continue;
137 case NRETAINSEQSSW:
138 retainseqs = false;
139 continue;
140
141 case UNLINKSW:
142 unlink_msgs = true;
143 continue;
144 case NUNLINKSW:
145 unlink_msgs = false;
146 continue;
147
148 case SRCSW:
149 if (folder)
150 die("only one source folder at a time!");
151 if (!(cp = *argp++) || *cp == '-')
152 die("missing argument to %s", argp[-2]);
153 folder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp,
154 *cp != '@' ? TFOLDER : TSUBCWF);
155 continue;
156 case DRAFTSW:
157 if (filep > NFOLDERS)
158 die("only %d files allowed!", NFOLDERS);
159 isdf = 0;
160 files[filep++] = mh_xstrdup(m_draft(NULL, NULL, 1, &isdf));
161 continue;
162 case FILESW:
163 if (filep > NFOLDERS)
164 die("only %d files allowed!", NFOLDERS);
165 if (!(cp = *argp++) || *cp == '-')
166 die("missing argument to %s", argp[-2]);
167 files[filep++] = path (cp, TFILE);
168 continue;
169
170 case RPROCSW:
171 if (!(rmmproc = *argp++) || *rmmproc == '-')
172 die("missing argument to %s", argp[-2]);
173 continue;
174 case NRPRCSW:
175 rmmproc = NULL;
176 continue;
177 }
178 }
179 if (*cp == '+' || *cp == '@') {
180 if (foldp > NFOLDERS)
181 die("only %d folders allowed!", NFOLDERS);
182 folders[foldp++].f_name =
183 pluspath (cp);
184 } else
185 app_msgarg(&msgs, cp);
186 }
187
188 if (!context_find ("path"))
189 free (path ("./", TFOLDER));
190 if (foldp == 0)
191 die("no folder specified");
192
193 /*
194 * We are refiling a file to the folders
195 */
196 if (filep > 0) {
197 if (folder || msgs.size)
198 die("use -file or some messages, not both");
199 opnfolds (NULL, folders, foldp);
200 for (i = 0; i < filep; i++)
201 if (m_file (0, files[i], 0, folders, foldp, preserve, 0))
202 done (1);
203 /* If -nolink, then "remove" files */
204 if (!linkf)
205 remove_files (filep, filevec);
206 done (0);
207 }
208
209 if (!msgs.size)
210 app_msgarg(&msgs, "cur");
211 if (!folder)
212 folder = getfolder (1);
213 strncpy (maildir, m_maildir (folder), sizeof(maildir));
214
215 if (chdir (maildir) == NOTOK)
216 adios (maildir, "unable to change directory to");
217
218 /* read source folder and create message structure */
219 if (!(mp = folder_read (folder, 1)))
220 die("unable to read folder %s", folder);
221
222 /* check for empty folder */
223 if (mp->nummsg == 0)
224 die("no messages in %s", folder);
225
226 /* parse the message range/sequence/name and set SELECTED */
227 for (msgnum = 0; msgnum < msgs.size; msgnum++)
228 if (!m_convert (mp, msgs.msgs[msgnum]))
229 done (1);
230 seq_setprev (mp); /* set the previous-sequence */
231
232 /* create folder structures for each destination folder */
233 opnfolds (mp, folders, foldp);
234
235 /* Link all the selected messages into destination folders.
236 *
237 * This causes the add hook to be run for messages that are
238 * linked into another folder. The refile hook is run for
239 * messages that are moved to another folder.
240 */
241 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
242 if (is_selected (mp, msgnum)) {
243 cp = mh_xstrdup(m_name (msgnum));
244 if (m_file (mp, cp, retainseqs ? msgnum : 0, folders, foldp,
245 preserve, !linkf))
246 done (1);
247 free (cp);
248 }
249 }
250
251 /*
252 * Update this now, since folder_delmsgs() will save the context
253 * but doesn't have access to the folder name.
254 */
255
256 context_replace (pfolder, folder); /* update current folder */
257
258 /*
259 * Adjust "cur" if necessary
260 */
261
262 if (mp->hghsel != mp->curmsg
263 && (mp->numsel != mp->nummsg || linkf))
264 seq_setcur (mp, mp->hghsel);
265
266 /*
267 * Close destination folders now; if we are using private sequences
268 * we need to have all of our calls to seq_save() complete before we
269 * call context_save().
270 */
271
272 clsfolds (folders, foldp);
273
274 /* If -nolink, then "remove" messages from source folder.
275 *
276 * Note that folder_delmsgs does not call the delete hook
277 * because the message has already been handled above.
278 */
279 if (!linkf) {
280 folder_delmsgs (mp, unlink_msgs, 1);
281 } else {
282 seq_save (mp); /* synchronize message sequences */
283 context_save (); /* save the context file */
284 }
285
286 folder_free (mp); /* free folder structure */
287 done (0);
288 return 1;
289 }
290
291
292 /*
293 * Read all the destination folders and
294 * create folder structures for all of them.
295 */
296
297 static void
298 opnfolds (struct msgs *src_folder, struct st_fold *folders, int nfolders)
299 {
300 char nmaildir[BUFSIZ];
301 struct st_fold *fp, *ep;
302 struct msgs *mp;
303
304 for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
305 if (chdir (m_maildir ("")) < 0) {
306 advise (m_maildir (""), "chdir");
307 }
308 strncpy (nmaildir, m_maildir (fp->f_name), sizeof(nmaildir));
309
310 /*
311 * Null src_folder indicates that we are refiling a file to
312 * the folders, in which case we don't want to short-circuit
313 * fp->f_mp to any "source folder".
314 */
315 if (! src_folder || strcmp (src_folder->foldpath, nmaildir)) {
316 create_folder (nmaildir, 0, done);
317
318 if (chdir (nmaildir) == NOTOK)
319 adios (nmaildir, "unable to change directory to");
320 if (!(mp = folder_read (fp->f_name, 1)))
321 die("unable to read folder %s", fp->f_name);
322 mp->curmsg = 0;
323
324 fp->f_mp = mp;
325 } else {
326 /* Source and destination folders are the same. */
327 fp->f_mp = src_folder;
328 }
329
330 if (maildir[0] != '\0' && chdir (maildir) < 0) {
331 advise (maildir, "chdir");
332 }
333 }
334 }
335
336
337 /*
338 * Set the Previous-Sequence and then synchronize the
339 * sequence file, for each destination folder.
340 */
341
342 static void
343 clsfolds (struct st_fold *folders, int nfolders)
344 {
345 struct st_fold *fp, *ep;
346 struct msgs *mp;
347
348 for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
349 mp = fp->f_mp;
350 seq_setprev (mp);
351 seq_save (mp);
352 }
353 }
354
355
356 /*
357 * If you have a "rmmproc" defined, we called that
358 * to remove all the specified files. If "rmmproc"
359 * is not defined, then just unlink the files.
360 */
361
362 static void
363 remove_files (int filep, char **files)
364 {
365 int i, vecp;
366 char **vec, *program;
367
368 /* If rmmproc is defined, we use that */
369 if (rmmproc) {
370 vec = argsplit(rmmproc, &program, &vecp);
371 files++; /* Yes, we need to do this */
372 for (i = 0; i < filep; i++)
373 vec[vecp++] = files[i];
374 vec[vecp] = NULL; /* NULL terminate list */
375
376 fflush (stdout);
377 execvp (program, vec);
378 adios (rmmproc, "unable to exec");
379 }
380
381 /* Else just unlink the files */
382 files++; /* advance past filevec[0] */
383 for (i = 0; i < filep; i++) {
384 if (m_unlink (files[i]) == NOTOK)
385 admonish (files[i], "unable to unlink");
386 }
387 }
388
389
390 /*
391 * Link (or copy) the message into each of the destination folders.
392 * If oldmsgnum is not 0, call copy_seqs().
393 */
394
395 static int
396 m_file (struct msgs *mp, char *msgfile, int oldmsgnum,
397 struct st_fold *folders, int nfolders, int preserve, int refile)
398 {
399 int msgnum;
400 struct st_fold *fp, *ep;
401
402 for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
403 /*
404 * With same source and destination folder, don't indicate that
405 * the new message is selected so that 1) folder_delmsgs() doesn't
406 * delete it later and 2) it is not reflected in mp->hghsel, and
407 * therefore won't be assigned to be the current message.
408 */
409 if ((msgnum = folder_addmsg (&fp->f_mp, msgfile,
410 mp != fp->f_mp,
411 0, preserve, nfolders == 1 && refile,
412 maildir)) == -1)
413 return 1;
414 if (oldmsgnum) copy_seqs (mp, oldmsgnum, fp->f_mp, msgnum);
415 }
416 return 0;
417 }
418
419
420 /*
421 * Copy sequence information for a refiled message to its
422 * new folder. Skip the cur sequence.
423 */
424 static void
425 copy_seqs (struct msgs *oldmp, int oldmsgnum, struct msgs *newmp, int newmsgnum)
426 {
427 char **seq;
428 size_t seqnum;
429
430 for (seq = svector_strs (oldmp->msgattrs), seqnum = 0;
431 *seq && seqnum < svector_size (oldmp->msgattrs);
432 ++seq, ++seqnum) {
433 if (strcmp (current, *seq)) {
434 assert ((int) seqnum == seq_getnum (oldmp, *seq));
435 if (in_sequence (oldmp, seqnum, oldmsgnum)) {
436 seq_addmsg (newmp, *seq, newmsgnum,
437 !is_seq_private (oldmp, seqnum), 0);
438 }
439 }
440 }
441 }