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