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