dirscan.c 9.88 KB
Newer Older
Richard Curnow's avatar
Richard Curnow committed
1 2 3 4
/*
  mairix - message index builder and finder for maildir folders.

 **********************************************************************
5
 * Copyright (C) Richard P. Curnow  2002,2003,2004,2005,2006,2007
Richard P. Curnow's avatar
Richard P. Curnow committed
6
 *
Richard Curnow's avatar
Richard Curnow committed
7 8 9
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
Richard P. Curnow's avatar
Richard P. Curnow committed
10
 *
Richard Curnow's avatar
Richard Curnow committed
11 12 13 14
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
Richard P. Curnow's avatar
Richard P. Curnow committed
15
 *
Richard Curnow's avatar
Richard Curnow committed
16 17
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
18
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Richard P. Curnow's avatar
Richard P. Curnow committed
19
 *
Richard Curnow's avatar
Richard Curnow committed
20 21 22 23 24 25 26
 **********************************************************************
 */

/* Traverse a directory tree and find maildirs, then list files in them. */

#include <sys/types.h>
#include <sys/stat.h>
27
#include <ctype.h>
Richard Curnow's avatar
Richard Curnow committed
28 29
#include <unistd.h>
#include <dirent.h>
30
#include <assert.h>
31
#include "mairix.h"
32

Richard Curnow's avatar
Richard Curnow committed
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
struct msgpath_array *new_msgpath_array(void)/*{{{*/
{
  struct msgpath_array *result;
  result = new(struct msgpath_array);
  result->paths = NULL;
  result->n = 0;
  result->max = 0;
  return result;
}
/*}}}*/
void free_msgpath_array(struct msgpath_array *x)/*{{{*/
{
  int i;
  if (x->paths) {
    for (i=0; i<x->n; i++) {
48
      switch (x->paths[i].type) {
49
        case MTY_FILE:
50
        case MTY_IMAP:
51 52 53 54 55 56 57
          free(x->paths[i].src.mpf.path);
          break;
        case MTY_MBOX:
          break;
        case MTY_DEAD:
          break;
      }
Richard Curnow's avatar
Richard Curnow committed
58 59 60 61 62 63
    }
    free(x->paths);
  }
  free(x);
}
/*}}}*/
64
static void add_file_to_list(char *x, struct msgpath_array *arr) {/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
65 66 67
  char *y = new_string(x);
  if (arr->n == arr->max) {
    arr->max += 1024;
68
    arr->paths = grow_array(struct msgpath,    arr->max, arr->paths);
Richard Curnow's avatar
Richard Curnow committed
69
  }
70
  arr->paths[arr->n].type = MTY_FILE;
71
  arr->paths[arr->n].src.mpf.path = y;
Richard Curnow's avatar
Richard Curnow committed
72 73 74 75
  ++arr->n;
  return;
}
/*}}}*/
76
static void get_maildir_message_paths(char *folder, struct msgpath_array *arr)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
77 78 79 80 81 82
{
  char *subdir, *fname;
  int i;
  static char *subdirs[] = {"new", "cur"};
  DIR *d;
  struct dirent *de;
83
  int folder_len = strlen(folder);
Richard Curnow's avatar
Richard Curnow committed
84 85

  /* FIXME : just store mdir-rooted paths in array and have common prefix elsewhere. */
Richard P. Curnow's avatar
Richard P. Curnow committed
86

87 88
  subdir = new_array(char, folder_len + 6);
  fname = new_array(char, folder_len + 8 + NAME_MAX);
Richard P. Curnow's avatar
Richard P. Curnow committed
89
  for (i=0; i<2; i++) {
90
    strcpy(subdir, folder);
Richard Curnow's avatar
Richard Curnow committed
91 92 93 94 95
    strcat(subdir, "/");
    strcat(subdir, subdirs[i]);
    d = opendir(subdir);
    if (d) {
      while ((de = readdir(d))) {
96 97
        /* TODO : Perhaps we ought to do some validation on the path here?
           i.e. check that the filename looks valid for a maildir message. */
98 99 100 101
        if (!strcmp(de->d_name, ".") ||
            !strcmp(de->d_name, "..")) {
          continue;
        }
Richard Curnow's avatar
Richard Curnow committed
102 103 104
        strcpy(fname, subdir);
        strcat(fname, "/");
        strcat(fname, de->d_name);
105
        add_file_to_list(fname, arr);
Richard Curnow's avatar
Richard Curnow committed
106 107 108 109 110 111 112 113 114
      }
      closedir(d);
    }
  }
  free(subdir);
  free(fname);
  return;
}
/*}}}*/
115
int valid_mh_filename_p(const char *x)/*{{{*/
116
{
117
  const char *p;
Richard P. Curnow's avatar
Richard P. Curnow committed
118

119 120 121
  if (!*x) return 0; /* Must not be empty */
  p = x;
  while (*p) {
122 123 124 125 126 127
    if (!isdigit(*p)) {
      /* Handle MH folders generated by Evolution, which have '.' on the ends
       * of the numerical filenames for the messages. */
      if ((p[0] == '.') && (p[1] == 0)) return 1;
      else return 0;
    }
128 129 130 131 132
    p++;
  }
  return 1;
}
/*}}}*/
133
static void get_mh_message_paths(char *folder, struct msgpath_array *arr)/*{{{*/
134
{
135
  char *fname;
136 137
  DIR *d;
  struct dirent *de;
138
  int folder_len = strlen(folder);
139

140 141
  fname = new_array(char, folder_len + 8 + NAME_MAX);
  d = opendir(folder);
142 143
  if (d) {
    while ((de = readdir(d))) {
144 145 146 147
      if (!strcmp(de->d_name, ".") ||
          !strcmp(de->d_name, "..")) {
        continue;
      }
148
      strcpy(fname, folder);
149 150
      strcat(fname, "/");
      strcat(fname, de->d_name);
151 152
      if (valid_mh_filename_p(de->d_name)) {
        add_file_to_list(fname, arr);
153 154 155 156 157 158 159 160
      }
    }
    closedir(d);
  }
  free(fname);
  return;
}
/*}}}*/
161
static int child_stat(const char *base, const char *child, struct stat *sb)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
162 163
{
  int result = 0;
164 165 166 167 168 169 170 171 172
  char *scratch;
  int len;

  len = strlen(base) + strlen(child) + 2;
  scratch = new_array(char, len);

  strcpy(scratch, base);
  strcat(scratch, "/");
  strcat(scratch, child);
Richard P. Curnow's avatar
Richard P. Curnow committed
173

174 175 176 177 178 179 180 181 182 183
  result = stat(scratch, sb);
  free(scratch);
  return result;
}
/*}}}*/
static int has_child_file(const char *base, const char *child)/*{{{*/
{
  int result = 0;
  int status;
  struct stat sb;
Richard P. Curnow's avatar
Richard P. Curnow committed
184

185 186 187
  status = child_stat(base, child, &sb);
  if ((status >= 0) && S_ISREG(sb.st_mode)) {
    result = 1;
Richard Curnow's avatar
Richard Curnow committed
188 189 190 191 192
  }

  return result;
}
/*}}}*/
193
static int has_child_dir(const char *base, const char *child)/*{{{*/
194
{
195 196 197
  int result = 0;
  int status;
  struct stat sb;
Richard P. Curnow's avatar
Richard P. Curnow committed
198

199 200 201
  status = child_stat(base, child, &sb);
  if ((status >= 0) && S_ISDIR(sb.st_mode)) {
    result = 1;
Richard Curnow's avatar
Richard Curnow committed
202 203 204 205 206
  }

  return result;
}
/*}}}*/
207
static enum traverse_check scrutinize_maildir_entry(int parent_is_maildir, const char *de_name)/*{{{*/
208 209 210
{
  if (parent_is_maildir) {
    /* Process any subdirectory that's not part of this maildir itself. */
211 212 213 214 215 216 217
    if (!strcmp(de_name, "new") ||
        !strcmp(de_name, "cur") ||
        !strcmp(de_name, "tmp")) {
      return TRAV_IGNORE;
    } else {
      return TRAV_PROCESS;
    }
218
  } else {
219
    return TRAV_PROCESS;
220 221 222
  }
}
/*}}}*/
223
static int filter_is_maildir(const char *path, const struct stat *sb)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
224
{
225 226 227 228 229
  if (S_ISDIR(sb->st_mode)) {
    if (has_child_dir(path, "new") &&
        has_child_dir(path, "tmp") &&
        has_child_dir(path, "cur")) {
      return 1;
Richard Curnow's avatar
Richard Curnow committed
230 231
    }
  }
232
  return 0;
Richard Curnow's avatar
Richard Curnow committed
233 234
}
/*}}}*/
235 236 237 238 239
struct traverse_methods maildir_traverse_methods = {/*{{{*/
  .filter = filter_is_maildir,
  .scrutinize = scrutinize_maildir_entry
};
/*}}}*/
240
static enum traverse_check scrutinize_mh_entry(int parent_is_mh, const char *de_name)/*{{{*/
241
{
242 243
  /* Have to allow sub-folders within a folder until we think of a better
   * solution.  */
244 245 246 247 248
  if (valid_mh_filename_p(de_name)) {
    return TRAV_IGNORE;
  } else {
    return TRAV_PROCESS;
  }
249 250
}
/*}}}*/
251
static int filter_is_mh(const char *path, const struct stat *sb)/*{{{*/
252
{
253
  int result = 0;
254
  if (S_ISDIR(sb->st_mode)) {
255 256 257
    /* TODO : find a way of making this more scalable?  e.g. if a folder of a
     * particular subtype is found once, try that subtype first later, since
     * the user presumably uses a consistent MH-subtype (i.e. a single MUA). */
258
    if (has_child_file(path, ".xmhcache") ||
259
        has_child_file(path, ".mh_sequences") ||
260
        /* Sylpheed */
261
        has_child_file(path, ".sylpheed_cache") ||
Richard P. Curnow's avatar
Richard P. Curnow committed
262
        has_child_file(path, ".sylpheed_mark") ||
Anand Kumria's avatar
Anand Kumria committed
263 264 265
        /* claws-mail */
        has_child_file(path, ".claws_cache") ||
        has_child_file(path, ".claws_mark") ||
266 267
        /* NNML (Gnus) */
        has_child_file(path, ".marks") ||
Richard P. Curnow's avatar
Richard P. Curnow committed
268
        has_child_file(path, ".overview") ||
269 270
        /* Evolution */
        has_child_file(path, "cmeta") ||
271 272
        has_child_file(path, "summary") ||
        /* Mew */
273 274 275
        has_child_file(path, ".mew-summary") ||
        /* ezmlm/archive */
        has_child_file(path, "index")
276
        ) {
277 278
      result = 1;
    }
279
  }
280
  return result;
281 282
}
/*}}}*/
283 284 285 286 287
struct traverse_methods mh_traverse_methods = {/*{{{*/
  .filter = filter_is_mh,
  .scrutinize = scrutinize_mh_entry
};
/*}}}*/
288
#if 0
289
static void scan_directory(char *folder_base, char *this_folder, enum folder_type ft, struct msgpath_array *arr)/*{{{*/
Richard Curnow's avatar
Richard Curnow committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303
{
  DIR *d;
  struct dirent *de;
  struct stat sb;
  char *fname, *sname;
  char *name;
  int folder_base_len = strlen(folder_base);
  int this_folder_len = strlen(this_folder);

  name = new_array(char, folder_base_len + this_folder_len + 2);
  strcpy(name, folder_base);
  strcat(name, "/");
  strcat(name, this_folder);

304 305 306 307 308 309 310 311 312 313 314
  switch (ft) {
    case FT_MAILDIR:
      if (looks_like_maildir(folder_base, this_folder)) {
        get_maildir_message_paths(folder_base, this_folder, arr);
      }
      break;
    case FT_MH:
      get_mh_message_paths(folder_base, this_folder, arr);
      break;
    default:
      break;
Richard Curnow's avatar
Richard Curnow committed
315
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
316

Richard Curnow's avatar
Richard Curnow committed
317 318 319 320
  fname = new_array(char, strlen(name) + 2 + NAME_MAX);
  sname = new_array(char, this_folder_len + 2 + NAME_MAX);

  d = opendir(name);
Richard Curnow's avatar
Richard Curnow committed
321 322 323 324 325 326
  if (d) {
    while ((de = readdir(d))) {
      if (!strcmp(de->d_name, ".") ||
          !strcmp(de->d_name, "..")) {
        continue;
      }
Richard Curnow's avatar
Richard Curnow committed
327

Richard Curnow's avatar
Richard Curnow committed
328 329 330
      strcpy(fname, name);
      strcat(fname, "/");
      strcat(fname, de->d_name);
Richard Curnow's avatar
Richard Curnow committed
331

Richard Curnow's avatar
Richard Curnow committed
332 333 334
      strcpy(sname, this_folder);
      strcat(sname, "/");
      strcat(sname, de->d_name);
Richard Curnow's avatar
Richard Curnow committed
335

Richard Curnow's avatar
Richard Curnow committed
336 337 338 339
      if (stat(fname, &sb) >= 0) {
        if (S_ISDIR(sb.st_mode)) {
          scan_directory(folder_base, sname, ft, arr);
        }
Richard Curnow's avatar
Richard Curnow committed
340 341
      }
    }
Richard Curnow's avatar
Richard Curnow committed
342
    closedir(d);
Richard Curnow's avatar
Richard Curnow committed
343 344 345 346 347 348 349 350
  }

  free(fname);
  free(sname);
  free(name);
  return;
}
/*}}}*/
351
#endif
352 353 354 355
/*{{{ void build_message_list */
void build_message_list(char *folder_base, char *folders, enum folder_type ft,
    struct msgpath_array *msgs,
    struct globber_array *omit_globs)
356 357 358 359 360 361 362
{
  char **raw_paths, **paths;
  int n_raw_paths, n_paths, i;

  split_on_colons(folders, &n_raw_paths, &raw_paths);
  switch (ft) {
    case FT_MAILDIR:
363
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &maildir_traverse_methods, omit_globs);
364 365 366 367 368
      for (i=0; i<n_paths; i++) {
        get_maildir_message_paths(paths[i], msgs);
      }
      break;
    case FT_MH:
369
      glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_paths, &mh_traverse_methods, omit_globs);
370 371 372 373 374 375 376 377
      for (i=0; i<n_paths; i++) {
        get_mh_message_paths(paths[i], msgs);
      }
      break;
    default:
      assert(0);
      break;
  }
Richard P. Curnow's avatar
Richard P. Curnow committed
378

379
  if (paths) free(paths);
Richard Curnow's avatar
Richard Curnow committed
380

381
  return;
Richard Curnow's avatar
Richard Curnow committed
382 383 384 385 386 387 388 389
}
/*}}}*/

#ifdef TEST
int main (int argc, char **argv)
{
  int i;
  struct msgpath_array *arr;
Richard P. Curnow's avatar
Richard P. Curnow committed
390

Richard Curnow's avatar
Richard Curnow committed
391 392 393 394 395 396 397
  arr = build_message_list(".");

  for (i=0; i<arr->n; i++) {
    printf("%08lx %s\n", arr->paths[i].mtime, arr->paths[i].path);
  }

  free_msgpath_array(arr);
Richard P. Curnow's avatar
Richard P. Curnow committed
398

Richard Curnow's avatar
Richard Curnow committed
399 400 401 402 403
  return 0;
}
#endif