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