1 /*
2 * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
3 * Copyright (c) 1996-2005 Michael T Pins. All rights reserved.
4 *
5 * Folder handling
6 */
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <pwd.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include "config.h"
14 #include "global.h"
15 #include "articles.h"
16 #include "db.h"
17 #include "digest.h"
18 #include "dir.h"
19 #include "match.h"
20 #include "menu.h"
21 #include "pack_date.h"
22 #include "pack_name.h"
23 #include "pack_subject.h"
24 #include "save.h"
25 #include "sort.h"
26 #include "nn_term.h"
27
28 /* folder.c */
29
30 static char *tilde_expansion(char **srcp, int compl);
31 static int folder_header(void);
32 static void rewrite_folder(void);
33
34
35 extern char *home_directory;
36 extern int current_folder_type;
37 extern int use_mail_folders;
38 extern int use_mmdf_folders;
39
40 extern int bypass_consolidation;
41
42 int dont_sort_folders = 0;
43 char *folder_directory = NULL;
44 int folder_rewrite_trace = 1;
45 char *backup_folder_path = "BackupFolder~";
46 int keep_backup_folder = 1;
47 int convert_folder_mode = 0; /* ignore folder's format on
48 * rewrite */
49 int consolidated_manual = 0;
50 int ignore_fancy_select = 0; /* turn off select features
51 * for folders */
52
53 extern int fmt_linenum;
54 extern char *header_lines;
55
56 extern struct passwd *getpwnam();
57
58 /*
59 * expand ~[user][/...] form
60 * src ptr is advanced to last char of user name.
61 */
62
63 static char *
tilde_expansion(char ** srcp,int compl)64 tilde_expansion(char **srcp, int compl)
65 {
66 struct passwd *pwd;
67 register char *name = *srcp;
68 register char *tail, x;
69
70 tail = ++name; /* skip ~ */
71 while (*tail && isascii(*tail) && !isspace(*tail) && *tail != '/')
72 tail++;
73
74 if (compl && *tail != '/')
75 return NULL;
76 if (tail == name)
77 return home_directory;
78
79 *srcp = tail - 1;
80 x = *tail;
81 *tail = NUL;
82 pwd = getpwnam(name);
83 if (pwd == NULL && !compl)
84 msg("User %s not found", name);
85 *tail = x;
86
87 return (pwd == NULL) ? NULL : pwd->pw_dir;
88 }
89
90 /*
91 * file name completion and expansion
92 *
93 * expand_mode bits:
94 * 1: expand path names
95 * 2: don't expand $N
96 * 4: don't expand any $? (but $(...) is expanded)
97 * 8: don't complain about ~... (shell will do that)
98 * 10: doing filename completion
99 */
100
101
102 int
expand_file_name(char * dest,char * src,int expand_mode)103 expand_file_name(char *dest, char *src, int expand_mode)
104 {
105 register char *cp, *dp, c;
106 int parse, remap;
107 char *cur_grp, *cur_art;
108
109 cur_grp = current_group ? current_group->group_name : NULL;
110 cur_art = (group_file_name && *group_file_name) ? group_path_name : NULL;
111
112 for (dp = dest, parse = 1; ((c = *src)); src++) {
113
114 if (parse) {
115
116 if ((expand_mode & 1) && c == '+') {
117 if (folder_directory == NULL) {
118 if (!(cp = getenv("FOLDER")))
119 cp = FOLDER_DIRECTORY;
120 folder_directory = home_relative(cp);
121 }
122 cp = folder_directory;
123 goto cp_str;
124 }
125 if ((expand_mode & 1) && c == '~') {
126 if ((cp = tilde_expansion(&src, (expand_mode & 0x10))) == NULL) {
127 return 0;
128 }
129 cp_str:
130 while (*cp)
131 *dp++ = *cp++;
132 if (dp[-1] != '/')
133 *dp++ = '/';
134 goto no_parse;
135 }
136 if ((expand_mode & 4) == 0 &&
137 cur_art && c == '%' && (src[1] == ' ' || src[1] == NUL)) {
138 cp = cur_art;
139 while (*cp)
140 *dp++ = *cp++;
141 goto no_parse;
142 }
143 }
144 if (c == '$' && src[1] == '(') {
145 char envar[64];
146 for (src += 2, cp = envar; (c = *src) != NUL && c != ')'; src++)
147 *cp++ = c;
148 *cp = NUL;
149 if (cp != envar) {
150 if ((cp = getenv(envar)) != NULL)
151 while (*cp)
152 *dp++ = *cp++;
153 else {
154 msg("Environment variable $(%s) not set", envar);
155 return 0;
156 }
157 }
158 goto no_parse;
159 }
160 if ((expand_mode & 4) == 0 && c == '$' && !isalnum(src[2])) {
161 remap = 0;
162 cp = NULL;
163
164 switch (src[1]) {
165 case 'A':
166 cp = cur_art;
167 break;
168 case 'F':
169 cp = cur_grp;
170 remap = 1;
171 break;
172 case 'G':
173 cp = cur_grp;
174 break;
175 case 'L':
176 if ((cp = strrchr(cur_grp, '.')))
177 cp++;
178 else
179 cp = cur_grp;
180 break;
181 case 'N':
182 if (expand_mode & 2)
183 goto copy;
184 if (cur_art)
185 cp = group_file_name;
186 if (cp == NULL)
187 goto copy;
188 break;
189 default:
190 goto copy;
191 }
192 src++;
193
194 if (!cp) {
195 msg("$%c not defined on this level", c);
196 return 0;
197 }
198 while (*cp)
199 if (remap && *cp == '.')
200 cp++, *dp++ = '/';
201 else
202 *dp++ = *cp++;
203 goto no_parse;
204 }
205 if (c == '/')
206
207 #ifdef ALLOW_LEADING_DOUBLE_SLASH
208 if (dp != &dest[1])
209 #endif
210
211 if (dp != dest && dp[-1] == '/')
212 goto no_parse;
213
214 copy:
215 *dp++ = c;
216 parse = isspace(c);
217 continue;
218
219 no_parse:
220 parse = 0;
221 }
222
223 *dp = NUL;
224
225 return 1;
226 }
227
228
229 int
file_completion(char * path,int index)230 file_completion(char *path, int index)
231 {
232 static int dir_in_use = 0;
233 static char *head, *tail = NULL;
234 static int tail_offset;
235
236 char nbuf[FILENAME], buffer[FILENAME];
237 char *dir, *base;
238
239 if (path) {
240 if (dir_in_use) {
241 close_directory();
242 dir_in_use = 0;
243 }
244 if (index < 0)
245 return 0;
246
247 head = path;
248 tail = path + index;
249 }
250 if (!dir_in_use) {
251 path = head;
252 *tail = NUL;
253
254 if (*path == '|')
255 return -1; /* no completion for pipes */
256
257 if (*path == '+' || *path == '~') {
258 if (!expand_file_name(nbuf, path, 0x11))
259 return 0; /* no completions */
260 } else
261 strcpy(nbuf, path);
262
263 if ((base = strrchr(nbuf, '/'))) {
264 if (base == nbuf) {
265 dir = "/";
266 base++;
267 } else {
268 *base++ = NUL;
269 dir = nbuf;
270 }
271 } else {
272 base = nbuf;
273 dir = ".";
274 }
275
276 tail_offset = strlen(base);
277
278 dir_in_use = list_directory(dir, base);
279
280 return dir_in_use;
281 }
282 if (index)
283 return compl_help_directory();
284
285 if (!next_directory(buffer, 1))
286 return 0;
287
288 strcpy(tail, buffer + tail_offset);
289
290 return 1;
291 }
292
293
294 static int cancel_count;
295
296 void
fcancel(article_header * ah)297 fcancel(article_header * ah)
298 {
299 if (ah->attr == A_CANCEL) {
300 cancel_count--;
301 ah->attr = 0;
302 } else {
303 cancel_count++;
304 ah->attr = A_CANCEL;
305 }
306 }
307
308 static int
folder_header(void)309 folder_header(void)
310 {
311 so_printxy(0, 0, "Folder: %s", current_group->group_name);
312
313 return 1; /* number of header lines */
314 }
315
316 /*
317 * mode values:
318 * 0: normal folder or digest
319 * 1: online manual
320 * 2: mailbox
321 */
322
323 int
folder_menu(char * path,int mode)324 folder_menu(char *path, int mode)
325 {
326 FILE *folder;
327 register article_header *ah;
328 news_header_buffer dgbuf;
329 char buffer[256];
330 int more, length, re, menu_cmd, was_raw;
331 memory_marker mem_marker;
332 group_header fake_group;
333 int cc_save;
334 char folder_name[FILENAME], folder_file[FILENAME];
335 int orig_layout;
336 char *orig_hdr_lines;
337
338 orig_layout = fmt_linenum;
339 orig_hdr_lines = header_lines;
340
341 strcpy(folder_name, path);
342 fake_group.group_name = folder_name;
343 fake_group.kill_list = NULL;
344 if (!expand_file_name(folder_file, folder_name, 1))
345 return ME_NO_REDRAW;
346 fake_group.archive_file = path = folder_file;
347 fake_group.next_group = fake_group.prev_group = NULL;
348 fake_group.group_flag = G_FOLDER | G_FAKED;
349 fake_group.master_flag = 0;
350 fake_group.save_file = NULL;
351 current_group = NULL;
352 init_group(&fake_group);
353
354 folder = open_file(path, OPEN_READ);
355 if (folder == NULL) {
356 msg("%s not found", path);
357 return ME_NO_REDRAW;
358 }
359 switch (get_folder_type(folder)) {
360 case 0:
361 msg("Reading: %-.65s", path);
362 break;
363 case 1:
364 case 2:
365 msg("Reading %s folder: %-.50s",
366 current_folder_type == 1 ? "mail" : "mmdf", path);
367 break;
368 default:
369 msg("Folder is empty");
370 fclose(folder);
371 return ME_NO_REDRAW;
372 }
373 rewind(folder);
374
375 was_raw = no_raw();
376 s_keyboard = 0;
377
378 current_group = &fake_group;
379
380 mark_memory(&mem_marker);
381
382 ah = alloc_art();
383
384 more = 1;
385 while (more && (more = get_digest_article(folder, dgbuf)) >= 0) {
386 if (s_keyboard)
387 break;
388
389 ah->a_number = 0;
390 ah->flag = A_FOLDER;
391 ah->attr = 0;
392
393 ah->lines = digest.dg_lines;
394
395 ah->hpos = digest.dg_hpos;
396 ah->fpos = digest.dg_fpos;
397 ah->lpos = digest.dg_lpos;
398
399 if (digest.dg_from) {
400 length = pack_name(buffer, digest.dg_from, Name_Length);
401 ah->sender = alloc_str(length);
402 strcpy(ah->sender, buffer);
403 ah->name_length = length;
404 if (mode == 1)
405 fold_string(ah->sender);
406 } else {
407 ah->sender = "";
408 ah->name_length = 0;
409 }
410
411 if (digest.dg_subj) {
412 length = pack_subject(buffer, digest.dg_subj, &re, 255);
413 ah->replies = re;
414 ah->subject = alloc_str(length);
415 strcpy(ah->subject, buffer);
416 ah->subj_length = length;
417 if (mode == 1 && length > 1)
418 fold_string(ah->subject + 1);
419 } else {
420 ah->replies = 0;
421 ah->subject = "";
422 ah->subj_length = 0;
423 }
424
425 ah->t_stamp = digest.dg_date ? pack_date(digest.dg_date) : 0;
426
427 add_article(ah);
428 ah = alloc_art();
429 }
430
431 fclose(folder);
432
433 if (s_keyboard) {
434 menu_cmd = ME_NO_REDRAW;
435 } else if (n_articles == 0) {
436 msg("Not a folder (no article header)");
437 menu_cmd = ME_NO_REDRAW;
438 } else {
439 if (n_articles > 1) {
440 clrdisp();
441 prompt_line = 2;
442 if (mode == 0 && !dont_sort_folders)
443 sort_articles(-1);
444 else if (mode == 1) {
445 article_number n;
446 for (n = 0; n < n_articles; n++) {
447 ah = articles[n];
448 if (n == 0)
449 ah->flag |= A_ROOT_ART;
450 else if (strcmp(ah->sender, articles[n - 1]->sender) == 0)
451 articles[n - 1]->flag |= A_NEXT_SAME;
452 else
453 ah->flag |= A_ROOT_ART;
454 }
455 bypass_consolidation = consolidated_manual ? 2 : 1;
456 } else
457 no_sort_articles();
458 } else
459 no_sort_articles();
460
461 cc_save = cancel_count;
462 cancel_count = 0;
463 if (mode == 1) {
464 fmt_linenum = -1;
465 header_lines = "*";
466 }
467 reenter_menu:
468 ignore_fancy_select = 1;
469 menu_cmd = menu(folder_header);
470 ignore_fancy_select = 0;
471
472 if (mode == 0 && cancel_count) {
473 register article_number cur;
474
475 cancel_count = 0;
476 for (cur = 0; cur < n_articles; cur++) {
477 if (articles[cur]->attr == A_CANCEL)
478 cancel_count++;
479 }
480 }
481 if (mode == 0 && cancel_count) {
482 clrdisp();
483 tprintf("\rFolder: %s\n\rFile: %s\n\n\r", folder_name, folder_file);
484 if (cancel_count == n_articles)
485 tprintf("Cancel all articles and remove folder? ");
486 else
487 tprintf("Remove %d article%s from folder? ",
488 cancel_count, plural((long) cancel_count));
489 fl;
490
491 switch (yes(1)) {
492 case 1:
493 tprintf("\n\n");
494 if (cancel_count == n_articles) {
495 if (unlink(group_path_name) < 0 &&
496 nn_truncate(group_path_name, (off_t) 0) < 0) {
497 tprintf("\rCould not unlink %s\n\r", group_path_name);
498 any_key(0);
499 }
500 } else
501 rewrite_folder();
502 break;
503 case 0:
504 break;
505 default:
506 goto reenter_menu;
507 }
508 }
509 cancel_count = cc_save;
510 }
511
512 release_memory(&mem_marker);
513 if (fmt_linenum == -1) {
514 fmt_linenum = orig_layout;
515 header_lines = orig_hdr_lines;
516 }
517 if (was_raw)
518 nn_raw();
519
520 return menu_cmd;
521 }
522
523 static void
rewrite_folder(void)524 rewrite_folder(void)
525 {
526 register FILE *src, *dst;
527 char *oldfile;
528 register int c;
529 register long cnt;
530 register article_header *ah, **ahp;
531 register article_number n;
532
533 if (strchr(backup_folder_path, '/'))
534 oldfile = backup_folder_path;
535 else
536 oldfile = relative(nn_directory, backup_folder_path);
537
538 if (move_file(group_path_name, oldfile, 1) < 0) {
539 tprintf("\r\n\nCannot backup folder in %s\n", oldfile);
540 goto confirm;
541 }
542 if ((src = open_file(oldfile, OPEN_READ)) == NULL) {
543 tprintf("\rCannot open %s\n\r", oldfile);
544 goto move_back;
545 }
546 if ((dst = open_file(group_path_name, OPEN_CREATE)) == NULL) {
547 fclose(src);
548 tprintf("\rCannot create %s\n\r", group_path_name);
549 goto move_back;
550 }
551 sort_articles(-2);
552
553 tprintf("\rCompressing folder...\n\r");
554 fl;
555
556 get_folder_type(src);
557
558 for (ahp = articles, n = n_articles; --n >= 0; ahp++) {
559 ah = *ahp;
560 cnt = ah->lpos - ah->hpos;
561 if (folder_rewrite_trace)
562 tprintf("%s\t%s (%ld-%ld=%ld)\n\r",
563 ah->attr == A_CANCEL ? "CANCEL" : "KEEP",
564 ah->subject, ah->hpos, (long) (ah->lpos), cnt);
565 if (ah->attr == A_CANCEL)
566 continue;
567 fseek(src, ah->hpos, 0);
568 mailbox_format(dst, -1);
569 while (--cnt >= 0) {
570 if ((c = getc(src)) == EOF)
571 break;
572 putc(c, dst);
573 }
574 mailbox_format(dst, 0);
575 }
576
577 fclose(src);
578 if (fclose(dst) == EOF)
579 goto move_back;
580 if (!keep_backup_folder)
581 unlink(oldfile);
582 if (folder_rewrite_trace)
583 goto confirm;
584 return;
585
586 move_back:
587 if (move_file(oldfile, group_path_name, 2) == 0)
588 tprintf("Cannot create new file -- Folder restored\n\r");
589 else
590 tprintf("Cannot create new file\n\n\rFolder saved in %s\n\r", oldfile);
591
592 confirm:
593 any_key(0);
594 }
595