Commit 363f28b7 363f28b7bf807bb678987b41ddce0fcc1c72b9ec by Sergey Poznyakoff

New file. Implementation of the folder command

1 parent 29702c75
Showing 1 changed file with 467 additions and 0 deletions
1 /* GNU mailutils - a suite of utilities for electronic mail
2 Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
17
18 /* MH folder command */
19
20 #include <mh.h>
21 #include <sys/types.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <time.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <errno.h>
28
29 #include <dirent.h>
30
31 #define obstack_chunk_alloc malloc
32 #define obstack_chunk_free free
33 #include <obstack.h>
34
35 const char *argp_program_version = "folder (" PACKAGE_STRING ")";
36 static char doc[] = "GNU MH folder";
37 static char args_doc[] = "[action][msg]";
38
39 #define ARG_PUSH 1
40 #define ARG_POP 2
41
42 static struct argp_option options[] = {
43 {"Actions are:", 0, 0, OPTION_DOC, "", 0 },
44 {"print", 'p', NULL, 0, "List the folders (default)", 1 },
45 {"list", 'l', NULL, 0, "List the contents of the folder stack", 1},
46 {"push", ARG_PUSH, "FOLDER", OPTION_ARG_OPTIONAL, "Push the folder on the folder stack. If FOLDER is specified, it is pushed. Otherwise, if a folder is given in the command line (via + or --folder), it is pushed on stack. Otherwise, the current folder and the top of the folder stack are exchanged", 1},
47 {"pop", ARG_POP, NULL, 0, "Pop the folder off the folder stack", 1},
48
49 {"Options are:", 0, 0, OPTION_DOC, "", 2 },
50 {"folder", 'f', "FOLDER", 0, "Specify folder to operate upon", 3},
51 {"all", 'a', NULL, 0, "List all folders", 3},
52 {"create", 'c', "BOOL", OPTION_ARG_OPTIONAL, "Create non-existing folders", 3},
53 {"fast", 'F', "BOOL", OPTION_ARG_OPTIONAL, "List only the folder names", 3},
54 {"header", 'h', "BOOL", OPTION_ARG_OPTIONAL, "Print the header line", 3},
55 {"recurse", 'r', "BOOL", OPTION_ARG_OPTIONAL, "Scan folders recursively", 3},
56 {"total", 't', "BOOL", OPTION_ARG_OPTIONAL, "Output the total statistics", 3},
57 { "\nUse -help switch to obtain the list of traditional MH options. ", 0, 0, OPTION_DOC, "", 4 },
58
59 {NULL},
60 };
61
62 /* Traditional MH options */
63 struct mh_option mh_option[] = {
64 {"print", 2, 'p', 0, NULL },
65 {"list", 1, 'l', 0, NULL },
66 {"push", 2, ARG_PUSH, 0, NULL },
67 {"pop", 2, ARG_POP, 0, NULL },
68 {"all", 1, 'a', 0, NULL },
69 {"create", 1, 'c', MH_OPT_BOOL, NULL},
70 {"fast", 1, 'F', MH_OPT_BOOL, NULL},
71 {"header", 1, 'h', MH_OPT_BOOL, NULL},
72 {"recurse", 1, 'r', MH_OPT_BOOL, NULL},
73 {"total", 1, 't', MH_OPT_BOOL, NULL},
74 {NULL},
75 };
76
77 typedef int (*folder_action) ();
78
79 static int action_print ();
80 static int action_list ();
81 static int action_push ();
82 static int action_pop ();
83
84 static folder_action action = action_print;
85
86 int show_all = 0; /* List all folders. Raised by --all switch */
87 int create_flag = 0; /* Create non-existent folders (--create) */
88 int fast_mode = 0; /* Fast operation mode. (--fast) */
89 int print_header = 0; /* Display the header line (--header) */
90 int recurse = 0; /* Recurse sub-folders */
91 int print_total; /* Display total stats */
92
93 char *push_folder; /* Folder name to push on stack */
94
95 char *mh_seq_name; /* Name of the mh sequence file (defaults to
96 .mh_sequences) */
97
98 static int
99 opt_handler (int key, char *arg, void *unused)
100 {
101 switch (key)
102 {
103 case 'p':
104 action = action_print;
105 break;
106
107 case 'l':
108 action = action_list;
109 break;
110
111 case ARG_PUSH:
112 action = action_push;
113 if (arg)
114 {
115 push_folder = mh_current_folder ();
116 current_folder = arg;
117 }
118 break;
119
120 case ARG_POP:
121 action = action_pop;
122 break;
123
124 case 'a':
125 show_all++;
126 break;
127
128 case 'c':
129 create_flag = is_true (arg);
130 break;
131
132 case 'F':
133 fast_mode = is_true (arg);
134 break;
135
136 case 'h':
137 print_header = is_true (arg);
138 break;
139
140 case 'r':
141 recurse = is_true (arg);
142 break;
143
144 case 't':
145 print_total = is_true (arg);
146 break;
147
148 case '+':
149 case 'f':
150 push_folder = mh_current_folder ();
151 current_folder = arg;
152 break;
153
154 default:
155 return 1;
156 }
157 return 0;
158 }
159
160 struct folder_info {
161 char *name; /* Folder name */
162 size_t message_count; /* Number of messages in this folder */
163 size_t min; /* First used sequence number (=uid) */
164 size_t max; /* Last used sequence number */
165 size_t cur; /* UID of the current message */
166 size_t others; /* Number of non-message files */
167 };
168
169 struct obstack folder_info_stack; /* Memory storage for folder infp */
170 struct folder_info *folder_info; /* Finalized array of information
171 structures */
172 size_t folder_info_count; /* Number of the entries in the array */
173
174 size_t message_count; /* Total number of messages */
175
176 int name_prefix_len; /* Length of the mu_path_folder_dir */
177
178
179 void
180 install_folder_info (const char *name, struct folder_info *info)
181 {
182 info->name = strdup (name) + name_prefix_len;
183 obstack_grow (&folder_info_stack, info, sizeof (*info));
184 folder_info_count++;
185 message_count += info->message_count;
186 }
187
188 static int
189 folder_info_comp (const void *a, const void *b)
190 {
191 return strcmp (((struct folder_info *)a)->name,
192 ((struct folder_info *)b)->name);
193 }
194
195 static void
196 read_seq_file (struct folder_info *info, const char *prefix, const char *name)
197 {
198 char *pname = NULL;
199 mh_context_t *ctx;
200 char *p;
201
202 asprintf (&pname, "%s/%s", prefix, name);
203 if (!pname)
204 abort ();
205 ctx = mh_context_create (pname, 1);
206 mh_context_read (ctx);
207
208 p = mh_context_get_value (ctx, "cur", NULL);
209 if (p)
210 info->cur = strtoul (p, NULL, 0);
211 free (pname);
212 free (ctx);
213 }
214
215 static void
216 _scan (const char *name, int depth)
217 {
218 DIR *dir;
219 struct dirent *entry;
220 struct folder_info info;
221 char *p;
222 struct stat st;
223 size_t uid;
224
225 if (fast_mode && !recurse && depth > 0)
226 {
227 memset (&info, 0, sizeof (info));
228 info.name = strdup (name);
229 install_folder_info (name, &info);
230 return;
231 }
232
233 if (depth > 1 && !recurse)
234 return;
235
236 dir = opendir (name);
237
238 if (!dir && errno == ENOENT)
239 {
240 mh_check_folder (name, 0);
241 dir = opendir (name);
242 }
243
244 if (!dir)
245 {
246 mh_error ("can't scan folder %s: %s", name, strerror (errno));
247 return;
248 }
249
250 memset (&info, 0, sizeof (info));
251 info.name = strdup (name);
252 while ((entry = readdir (dir)))
253 {
254 switch (entry->d_name[0])
255 {
256 case '.':
257 if (strcmp (entry->d_name, mh_seq_name) == 0)
258 read_seq_file (&info, name, entry->d_name);
259 break;
260
261 case ',':
262 continue;
263
264 case '0':case '1':case '2':case '3':case '4':
265 case '5':case '6':case '7':case '8':case '9':
266 uid = strtoul (entry->d_name, &p, 10);
267 if (*p)
268 info.others++;
269 else
270 {
271 info.message_count++;
272 if (info.min == 0 || uid < info.min)
273 info.min = uid;
274 else if (uid > info.max)
275 info.max = uid;
276 }
277 break;
278
279 default:
280 asprintf (&p, "%s/%s", name, entry->d_name);
281 if (stat (p, &st) < 0)
282 {
283 mh_error ("can't stat %s: %s", p, strerror (errno));
284 info.others++;
285 }
286 else if (S_ISDIR (st.st_mode))
287 {
288 _scan (p, depth+1);
289 }
290 else
291 /* Invalid entry. */
292 info.others++;
293 free (p);
294 }
295
296 }
297 closedir (dir);
298 install_folder_info (name, &info);
299 }
300
301 static void
302 print_all ()
303 {
304 struct folder_info *info, *end = folder_info + folder_info_count;
305
306 for (info = folder_info; info < end; info++)
307 {
308 printf ("%19.19s%c", info->name,
309 (strcmp (info->name, current_folder) == 0) ? '+' : ' ');
310 if (info->message_count)
311 {
312 printf (" has %4lu messages (%4lu-%4lu)",
313 (unsigned long) info->message_count,
314 (unsigned long) info->min,
315 (unsigned long) info->max);
316 if (info->cur)
317 printf ("; cur=%4lu", (unsigned long) info->cur);
318
319 if (info->others)
320 {
321 if (!info->cur)
322 printf ("; ");
323 else
324 printf ("; ");
325 printf ("(others)");
326 }
327 printf (".\n");
328 }
329 else
330 {
331 printf (" has no messages.\n");
332 }
333 }
334 }
335
336 static void
337 print_fast ()
338 {
339 struct folder_info *info, *end = folder_info + folder_info_count;
340
341 for (info = folder_info; info < end; info++)
342 {
343 printf ("%s", info->name);
344 if (strcmp (info->name, current_folder) == 0)
345 printf ("+");
346 printf ("\n");
347 }
348 }
349
350 static int
351 action_print ()
352 {
353 mh_seq_name = mh_global_profile_get ("mh-sequences", MH_SEQUENCES_FILE);
354
355 name_prefix_len = strlen (mu_path_folder_dir);
356 if (mu_path_folder_dir[name_prefix_len - 1] == '/')
357 name_prefix_len++;
358 name_prefix_len++; /* skip past the slash */
359
360 obstack_init (&folder_info_stack);
361
362 if (show_all)
363 {
364 _scan (mu_path_folder_dir, 0);
365 folder_info_count--; /* do not count folder directory */
366 }
367 else
368 {
369 char *p;
370 asprintf (&p, "%s/%s", mu_path_folder_dir, current_folder);
371 _scan (p, 1);
372 free (p);
373 }
374
375 folder_info = obstack_finish (&folder_info_stack);
376 qsort (folder_info, folder_info_count, sizeof (folder_info[0]),
377 folder_info_comp);
378
379 if (fast_mode)
380 print_fast ();
381 else
382 print_all ();
383
384 if (print_total)
385 printf ("%24.24s=%4lu messages in %4lu folders\n",
386 "TOTAL",
387 (unsigned long) message_count,
388 (unsigned long) folder_info_count);
389
390 if (push_folder)
391 mh_global_save_state ();
392
393 return 0;
394 }
395
396 static int
397 action_list ()
398 {
399 char *stack = mh_global_context_get ("Folder-Stack", NULL);
400
401 printf ("%s", current_folder);
402 if (stack)
403 printf (" %s\n", stack);
404 return 0;
405 }
406
407 static char *
408 make_stack (char *folder, char *old_stack)
409 {
410 char *stack = NULL;
411 if (old_stack)
412 asprintf (&stack, "%s %s", folder, old_stack);
413 else
414 stack = strdup (folder);
415 return stack;
416 }
417
418 static int
419 action_push ()
420 {
421 char *stack = mh_global_context_get ("Folder-Stack", NULL);
422 char *new_stack = NULL;
423
424 if (push_folder)
425 new_stack = make_stack (push_folder, stack);
426 else
427 {
428 char *s, *p = strtok_r (stack, " ", &s);
429 new_stack = make_stack (current_folder, stack);
430 current_folder = p;
431 }
432
433 mh_global_context_set ("Folder-Stack", new_stack);
434 action_list ();
435 mh_global_save_state ();
436 return 0;
437 }
438
439 static int
440 action_pop ()
441 {
442 char *stack = mh_global_context_get ("Folder-Stack", NULL);
443 char *s, *p = strtok_r (stack, " ", &s);
444 mh_global_context_set ("Folder-Stack", s);
445 current_folder = p;
446 action_list ();
447 mh_global_save_state ();
448 return 0;
449 }
450
451 int
452 main (int argc, char **argv)
453 {
454 int index = 0;
455 mh_argp_parse (argc, argv, options, mh_option, args_doc, doc,
456 opt_handler, NULL, &index);
457
458 /* If folder is invoked by a name ending with "s" (e.g., folders),
459 `-all' is assumed */
460 if (program_invocation_short_name[strlen (program_invocation_short_name) - 1] == 's')
461 show_all++;
462
463 if (show_all)
464 print_header = print_total = 1;
465
466 return (*action) ();
467 }