mh.c (30853B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2025 the Claws Mail team and Hiroyuki Yamamoto 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #include "defs.h" 20 21 #include <glib.h> 22 #include <glib/gi18n.h> 23 #include <sys/stat.h> 24 #include <unistd.h> 25 #include <string.h> 26 #include <err.h> 27 #include <errno.h> 28 #include <time.h> 29 30 #include "folder.h" 31 #include "folder_item_prefs.h" 32 #include "mh.h" 33 #include "procmsg.h" 34 #include "procheader.h" 35 #include "utils.h" 36 #include "codeconv.h" 37 #include "statusbar.h" 38 #include "gtkutils.h" 39 #include "msgcache.h" 40 #include "file-utils.h" 41 #include "prefs_common.h" 42 43 static void mh_folder_init (Folder *folder, 44 const gchar *name, 45 const gchar *path); 46 47 static Folder *mh_folder_new (const gchar *name, 48 const gchar *path); 49 static void mh_folder_destroy (Folder *folder); 50 static gchar *mh_fetch_msg (Folder *folder, 51 FolderItem *item, 52 gint num); 53 static MsgInfo *mh_get_msginfo (Folder *folder, 54 FolderItem *item, 55 gint num); 56 static gint mh_add_msg (Folder *folder, 57 FolderItem *dest, 58 const gchar *file, 59 MsgFlags *flags); 60 static gint mh_add_msgs (Folder *folder, 61 FolderItem *dest, 62 GSList *file_list, 63 GHashTable *relation); 64 static gint mh_copy_msg (Folder *folder, 65 FolderItem *dest, 66 MsgInfo *msginfo); 67 static gint mh_copy_msgs (Folder *folder, 68 FolderItem *dest, 69 MsgInfoList *msglist, 70 GHashTable *relation); 71 static gint mh_remove_msg (Folder *folder, 72 FolderItem *item, 73 gint num); 74 static gint mh_remove_msgs (Folder *folder, 75 FolderItem *item, 76 MsgInfoList *msglist, 77 GHashTable *relation); 78 static gint mh_remove_all_msg (Folder *folder, 79 FolderItem *item); 80 static gboolean mh_is_msg_changed (Folder *folder, 81 FolderItem *item, 82 MsgInfo *msginfo); 83 84 static gint mh_get_num_list (Folder *folder, 85 FolderItem *item, 86 GSList **list, 87 gboolean *old_uids_valid); 88 static gint mh_scan_tree (Folder *folder); 89 90 static gint mh_create_tree (Folder *folder); 91 static FolderItem *mh_create_folder (Folder *folder, 92 FolderItem *parent, 93 const gchar *name); 94 static gint mh_rename_folder (Folder *folder, 95 FolderItem *item, 96 const gchar *name); 97 static gint mh_remove_folder (Folder *folder, 98 FolderItem *item); 99 100 static gchar *mh_get_new_msg_filename (FolderItem *dest); 101 102 static MsgInfo *mh_parse_msg (const gchar *file, 103 FolderItem *item); 104 static void mh_remove_missing_folder_items (Folder *folder); 105 static gchar *mh_filename_from_utf8 (const gchar *path); 106 static gchar *mh_filename_to_utf8 (const gchar *path); 107 static void mh_scan_tree_recursive (FolderItem *item); 108 109 static gboolean mh_rename_folder_func (GNode *node, 110 gpointer data); 111 static gchar *mh_item_get_path (Folder *folder, 112 FolderItem *item); 113 114 static gboolean mh_scan_required (Folder *folder, 115 FolderItem *item); 116 static void mh_set_mtime (Folder *folder, 117 FolderItem *item); 118 static int mh_item_close (Folder *folder, 119 FolderItem *item); 120 #if 0 121 static gint mh_get_flags (Folder *folder, FolderItem *item, 122 MsgInfoList *msginfo_list, GHashTable *msgflags); 123 #endif 124 125 static FolderClass mh_class; 126 127 FolderClass *mh_get_class(void) 128 { 129 if (mh_class.idstr == NULL) { 130 mh_class.type = F_MH; 131 mh_class.idstr = "mh"; 132 mh_class.uistr = "MH"; 133 134 /* Folder functions */ 135 mh_class.new_folder = mh_folder_new; 136 mh_class.destroy_folder = mh_folder_destroy; 137 mh_class.set_xml = folder_local_set_xml; 138 mh_class.get_xml = folder_local_get_xml; 139 mh_class.scan_tree = mh_scan_tree; 140 mh_class.create_tree = mh_create_tree; 141 142 /* FolderItem functions */ 143 mh_class.item_get_path = mh_item_get_path; 144 mh_class.create_folder = mh_create_folder; 145 mh_class.rename_folder = mh_rename_folder; 146 mh_class.remove_folder = mh_remove_folder; 147 mh_class.get_num_list = mh_get_num_list; 148 mh_class.scan_required = mh_scan_required; 149 mh_class.set_mtime = mh_set_mtime; 150 mh_class.close = mh_item_close; 151 mh_class.get_flags = NULL; /*mh_get_flags */; 152 153 /* Message functions */ 154 mh_class.get_msginfo = mh_get_msginfo; 155 mh_class.fetch_msg = mh_fetch_msg; 156 mh_class.add_msg = mh_add_msg; 157 mh_class.add_msgs = mh_add_msgs; 158 mh_class.copy_msg = mh_copy_msg; 159 mh_class.copy_msgs = mh_copy_msgs; 160 mh_class.remove_msg = mh_remove_msg; 161 mh_class.remove_msgs = mh_remove_msgs; 162 mh_class.remove_all_msg = mh_remove_all_msg; 163 mh_class.is_msg_changed = mh_is_msg_changed; 164 } 165 166 return &mh_class; 167 } 168 169 static Folder *mh_folder_new(const gchar *name, const gchar *path) 170 { 171 Folder *folder; 172 173 folder = (Folder *)g_new0(MHFolder, 1); 174 folder->klass = &mh_class; 175 mh_folder_init(folder, name, path); 176 177 return folder; 178 } 179 180 static void mh_folder_destroy(Folder *folder) 181 { 182 free(LOCAL_FOLDER(folder)->rootpath); 183 } 184 185 static void mh_folder_init(Folder *folder, const gchar *name, const gchar *path) 186 { 187 folder_init(folder, name); 188 LOCAL_FOLDER(folder)->rootpath = strdup(path); 189 } 190 191 gboolean mh_scan_required(Folder *folder, FolderItem *item) 192 { 193 char *path = folder_item_get_path(item); 194 if (path == NULL) 195 return FALSE; 196 struct stat sb; 197 if (stat(path, &sb) < 0) { 198 warn("stat %s", path); 199 free(path); 200 return FALSE; 201 } 202 free(path); 203 204 if ((sb.st_mtime > item->mtime) && (sb.st_mtime - 3600 != item->mtime)) 205 return TRUE; 206 return FALSE; 207 } 208 209 static void mh_get_last_num(Folder *folder, FolderItem *item) 210 { 211 gchar *path, *fullpath; 212 GDir *dp; 213 const gchar *d; 214 GError *error = NULL; 215 gint max = 0; 216 gint num; 217 218 cm_return_if_fail(item != NULL); 219 220 debug_print("mh_get_last_num(): Scanning %s ...\n", item->path?item->path:"(null)"); 221 222 path = folder_item_get_path(item); 223 cm_return_if_fail(path != NULL); 224 225 if ((dp = g_dir_open(path, 0, &error)) == NULL) { 226 g_warning("couldn't open directory '%s': %s (%d)", 227 path, error->message, error->code); 228 g_error_free(error); 229 g_free(path); 230 return; 231 } 232 233 while ((d = g_dir_read_name(dp)) != NULL) { 234 fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, d, NULL); 235 if ((num = atoi(d)) > 0 && 236 g_file_test(fullpath, G_FILE_TEST_IS_REGULAR)) { 237 if (max < num) 238 max = num; 239 } 240 g_free(fullpath); 241 242 if (num % 2000 == 0) 243 GTK_EVENTS_FLUSH(); 244 } 245 g_dir_close(dp); 246 g_free(path); 247 248 debug_print("Last number in dir %s = %d\n", item->path?item->path:"(null)", max); 249 item->last_num = max; 250 } 251 252 gint mh_get_num_list(Folder *folder, FolderItem *item, GSList **list, gboolean *old_uids_valid) 253 { 254 255 gchar *path; 256 GDir *dp; 257 const gchar *d; 258 GError *error = NULL; 259 gint num, nummsgs = 0; 260 261 cm_return_val_if_fail(item != NULL, -1); 262 263 debug_print("mh_get_num_list(): Scanning %s ...\n", item->path?item->path:"(null)"); 264 265 *old_uids_valid = TRUE; 266 267 path = folder_item_get_path(item); 268 cm_return_val_if_fail(path != NULL, -1); 269 270 if ((dp = g_dir_open(path, 0, &error)) == NULL) { 271 g_message("Couldn't open current directory: %s (%d).\n", 272 error->message, error->code); 273 g_error_free(error); 274 g_free(path); 275 return -1; 276 } 277 g_free(path); 278 279 while ((d = g_dir_read_name(dp)) != NULL) { 280 if ((num = atoi(d)) > 0) { 281 *list = g_slist_prepend(*list, GINT_TO_POINTER(num)); 282 nummsgs++; 283 } 284 } 285 g_dir_close(dp); 286 287 mh_set_mtime(folder, item); 288 return nummsgs; 289 } 290 291 static gchar *mh_fetch_msg(Folder *folder, FolderItem *item, int num) 292 { 293 if (item == NULL) { 294 warn("fetch message %d: NULL folder item", num); 295 return NULL; 296 } else if (num <= 0) { 297 warn("fetch message %d: message number must be positive"); 298 return NULL; 299 } 300 301 char *path = folder_item_get_path(item); 302 char *file = malloc(PATH_MAX); 303 snprintf(file, sizeof(file), "%s/%d", path, num); 304 return file; 305 } 306 307 static MsgInfo *mh_get_msginfo(Folder *folder, FolderItem *item, gint num) 308 { 309 MsgInfo *msginfo; 310 gchar *file; 311 312 cm_return_val_if_fail(item != NULL, NULL); 313 if (num <= 0) 314 return NULL; 315 316 file = mh_fetch_msg(folder, item, num); 317 if (!file) return NULL; 318 319 msginfo = mh_parse_msg(file, item); 320 if (msginfo) 321 msginfo->msgnum = num; 322 323 g_free(file); 324 325 return msginfo; 326 } 327 328 static gchar *mh_get_new_msg_filename(FolderItem *dest) 329 { 330 gchar *destfile; 331 gchar *destpath; 332 333 destpath = folder_item_get_path(dest); 334 cm_return_val_if_fail(destpath != NULL, NULL); 335 336 if (dest->last_num < 0) { 337 mh_get_last_num(dest->folder, dest); 338 if (dest->last_num < 0) return NULL; 339 } 340 341 if (!is_dir_exist(destpath)) 342 make_dir_hier(destpath); 343 344 for (;;) { 345 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR, 346 dest->last_num + 1); 347 if (is_file_entry_exist(destfile)) { 348 dest->last_num++; 349 g_free(destfile); 350 } else 351 break; 352 } 353 354 g_free(destpath); 355 356 return destfile; 357 } 358 359 static gint mh_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags) 360 { 361 gint ret; 362 GSList file_list; 363 MsgFileInfo fileinfo; 364 365 cm_return_val_if_fail(file != NULL, -1); 366 367 fileinfo.msginfo = NULL; 368 fileinfo.file = (gchar *)file; 369 fileinfo.flags = flags; 370 file_list.data = &fileinfo; 371 file_list.next = NULL; 372 373 ret = mh_add_msgs(folder, dest, &file_list, NULL); 374 return ret; 375 } 376 377 static gint mh_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, 378 GHashTable *relation) 379 { 380 gchar *destfile; 381 GSList *cur; 382 MsgFileInfo *fileinfo; 383 FolderItemPrefs *prefs; 384 385 cm_return_val_if_fail(dest != NULL, -1); 386 cm_return_val_if_fail(file_list != NULL, -1); 387 388 if (dest->last_num < 0) { 389 mh_get_last_num(folder, dest); 390 if (dest->last_num < 0) return -1; 391 } 392 393 prefs = dest->prefs; 394 395 for (cur = file_list; cur != NULL; cur = cur->next) { 396 fileinfo = (MsgFileInfo *)cur->data; 397 398 destfile = mh_get_new_msg_filename(dest); 399 if (destfile == NULL) return -1; 400 401 #ifdef G_OS_UNIX 402 if (link(fileinfo->file, destfile) < 0) { 403 #endif 404 if (copy_file(fileinfo->file, destfile, TRUE) < 0) { 405 g_warning("can't copy message %s to %s", 406 fileinfo->file, destfile); 407 g_free(destfile); 408 return -1; 409 } 410 #ifdef G_OS_UNIX 411 } 412 #endif 413 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) { 414 if (chmod(destfile, prefs->folder_chmod) < 0) 415 FILE_OP_ERROR(destfile, "chmod"); 416 } 417 418 if (relation != NULL) 419 g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(dest->last_num + 1)); 420 g_free(destfile); 421 dest->last_num++; 422 } 423 424 return dest->last_num; 425 } 426 427 static gint mh_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) 428 { 429 GSList msglist; 430 431 cm_return_val_if_fail(msginfo != NULL, -1); 432 433 msglist.data = msginfo; 434 msglist.next = NULL; 435 436 return mh_copy_msgs(folder, dest, &msglist, NULL); 437 } 438 439 static gint mh_copy_msgs(Folder *folder, FolderItem *dest, MsgInfoList *msglist, 440 GHashTable *relation) 441 { 442 gboolean dest_need_scan = FALSE; 443 gboolean src_need_scan = FALSE; 444 FolderItem *src = NULL; 445 gchar *srcfile; 446 gchar *destfile; 447 FolderItemPrefs *prefs; 448 MsgInfo *msginfo = NULL; 449 MsgInfoList *cur = NULL; 450 gint curnum = 0, total = 0; 451 gchar *srcpath = NULL; 452 gboolean full_fetch = FALSE; 453 time_t last_dest_mtime = (time_t)0; 454 time_t last_src_mtime = (time_t)0; 455 456 cm_return_val_if_fail(dest != NULL, -1); 457 cm_return_val_if_fail(msglist != NULL, -1); 458 459 msginfo = (MsgInfo *)msglist->data; 460 461 cm_return_val_if_fail(msginfo != NULL, -1); 462 463 if (msginfo->folder == dest) { 464 g_warning("the src folder is identical to the dest"); 465 return -1; 466 } 467 468 if (msginfo->folder->folder != dest->folder) 469 full_fetch = TRUE; 470 471 if (FOLDER_TYPE(msginfo->folder->folder) == F_MH) { 472 src = msginfo->folder; 473 } 474 475 if (dest->last_num < 0) { 476 mh_get_last_num(folder, dest); 477 if (dest->last_num < 0) return -1; 478 } 479 480 prefs = dest->prefs; 481 482 srcpath = folder_item_get_path(msginfo->folder); 483 484 dest_need_scan = mh_scan_required(dest->folder, dest); 485 last_dest_mtime = dest->mtime; 486 487 if (src) { 488 src_need_scan = mh_scan_required(src->folder, src); 489 last_src_mtime = src->mtime; 490 } 491 492 total = g_slist_length(msglist); 493 if (total > 100) { 494 if (MSG_IS_MOVE(msginfo->flags)) 495 statusbar_print_all(_("Moving messages...")); 496 else 497 statusbar_print_all(_("Copying messages...")); 498 } 499 for (cur = msglist; cur; cur = cur->next) { 500 msginfo = (MsgInfo *)cur->data; 501 if (!msginfo) { 502 goto err_reset_status; 503 } 504 if (!full_fetch) { 505 srcfile = g_strconcat(srcpath, 506 G_DIR_SEPARATOR_S, 507 itos(msginfo->msgnum), NULL); 508 } else { 509 srcfile = procmsg_get_message_file(msginfo); 510 } 511 if (!srcfile) { 512 goto err_reset_status; 513 } 514 destfile = mh_get_new_msg_filename(dest); 515 if (!destfile) { 516 g_free(srcfile); 517 goto err_reset_status; 518 } 519 520 if (total > 100) { 521 statusbar_progress_all(curnum, total, 100); 522 if (curnum % 100 == 0) 523 GTK_EVENTS_FLUSH(); 524 curnum++; 525 } 526 527 debug_print("Copying message %s%c%d to %s ...\n", 528 msginfo->folder->path, G_DIR_SEPARATOR, 529 msginfo->msgnum, dest->path); 530 531 532 if (MSG_IS_MOVE(msginfo->flags)) { 533 msginfo->flags.tmp_flags &= ~MSG_MOVE_DONE; 534 if (rename(srcfile, destfile) < 0) { 535 warn("rename %s to %s", srcfile, destfile); 536 if (copy_file(srcfile, destfile, TRUE) < 0) { 537 FILE_OP_ERROR(srcfile, "copy"); 538 g_free(srcfile); 539 g_free(destfile); 540 goto err_reset_status; 541 } 542 } else { 543 /* say unlinking's not necessary */ 544 msginfo->flags.tmp_flags |= MSG_MOVE_DONE; 545 } 546 } else if (copy_file(srcfile, destfile, TRUE) < 0) { 547 FILE_OP_ERROR(srcfile, "copy"); 548 g_free(srcfile); 549 g_free(destfile); 550 goto err_reset_status; 551 } 552 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) { 553 if (chmod(destfile, prefs->folder_chmod) < 0) 554 FILE_OP_ERROR(destfile, "chmod"); 555 } 556 if (relation) { 557 if (g_hash_table_lookup(relation, msginfo) != NULL) 558 g_warning("already in: %p", msginfo); 559 560 g_hash_table_insert(relation, msginfo, GINT_TO_POINTER(dest->last_num+1)); 561 } 562 g_free(srcfile); 563 g_free(destfile); 564 dest->last_num++; 565 } 566 567 g_free(srcpath); 568 569 if (dest->mtime == last_dest_mtime && !dest_need_scan) { 570 mh_set_mtime(folder, dest); 571 } 572 573 if (src && src->mtime == last_src_mtime && !src_need_scan) { 574 mh_set_mtime(folder, src); 575 } 576 577 if (total > 100) { 578 statusbar_progress_all(0,0,0); 579 statusbar_pop_all(); 580 } 581 return dest->last_num; 582 err_reset_status: 583 g_free(srcpath); 584 if (total > 100) { 585 statusbar_progress_all(0,0,0); 586 statusbar_pop_all(); 587 } 588 return -1; 589 590 } 591 592 static gint mh_remove_msg(Folder *folder, FolderItem *item, gint num) 593 { 594 gboolean need_scan = FALSE; 595 time_t last_mtime = (time_t)0; 596 gchar *file; 597 598 cm_return_val_if_fail(item != NULL, -1); 599 600 file = mh_fetch_msg(folder, item, num); 601 cm_return_val_if_fail(file != NULL, -1); 602 603 need_scan = mh_scan_required(folder, item); 604 last_mtime = item->mtime; 605 606 if (unlink(file) < 0) { 607 FILE_OP_ERROR(file, "unlink"); 608 g_free(file); 609 return -1; 610 } 611 612 if (item->mtime == last_mtime && !need_scan) { 613 mh_set_mtime(folder, item); 614 } 615 g_free(file); 616 return 0; 617 } 618 619 static gint mh_remove_msgs(Folder *folder, FolderItem *item, 620 MsgInfoList *msglist, GHashTable *relation) 621 { 622 gboolean need_scan = FALSE; 623 gchar *path, *file; 624 time_t last_mtime = (time_t)0; 625 MsgInfoList *cur; 626 gint total = 0, curnum = 0; 627 628 cm_return_val_if_fail(item != NULL, -1); 629 630 path = folder_item_get_path(item); 631 632 need_scan = mh_scan_required(folder, item); 633 last_mtime = item->mtime; 634 635 total = g_slist_length(msglist); 636 if (total > 100) { 637 statusbar_print_all(_("Deleting messages...")); 638 } 639 640 for (cur = msglist; cur; cur = cur->next) { 641 MsgInfo *msginfo = (MsgInfo *)cur->data; 642 if (msginfo == NULL) 643 continue; 644 if (MSG_IS_MOVE(msginfo->flags) && MSG_IS_MOVE_DONE(msginfo->flags)) { 645 msginfo->flags.tmp_flags &= ~MSG_MOVE_DONE; 646 continue; 647 } 648 if (total > 100) { 649 statusbar_progress_all(curnum, total, 100); 650 if (curnum % 100 == 0) 651 GTK_EVENTS_FLUSH(); 652 curnum++; 653 } 654 655 file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(msginfo->msgnum), NULL); 656 if (file == NULL) 657 continue; 658 659 if (unlink(file) < 0) { 660 g_free(file); 661 continue; 662 } 663 664 g_free(file); 665 } 666 667 if (total > 100) { 668 statusbar_progress_all(0,0,0); 669 statusbar_pop_all(); 670 } 671 if (item->mtime == last_mtime && !need_scan) { 672 mh_set_mtime(folder, item); 673 } 674 675 g_free(path); 676 return 0; 677 } 678 679 static gint mh_remove_all_msg(Folder *folder, FolderItem *item) 680 { 681 gchar *path; 682 gint val; 683 684 cm_return_val_if_fail(item != NULL, -1); 685 686 path = folder_item_get_path(item); 687 cm_return_val_if_fail(path != NULL, -1); 688 val = remove_all_numbered_files(path); 689 g_free(path); 690 return val; 691 } 692 693 static gboolean mh_is_msg_changed(Folder *folder, FolderItem *item, 694 MsgInfo *msginfo) 695 { 696 GStatBuf s; 697 int r; 698 gchar *path; 699 gchar *parent_path; 700 701 parent_path = folder_item_get_path(item); 702 path = g_strdup_printf("%s%c%d", parent_path, 703 G_DIR_SEPARATOR, msginfo->msgnum); 704 g_free(parent_path); 705 706 r = g_stat(path, &s); 707 g_free(path); 708 if (r < 0 || 709 msginfo->size != s.st_size || ( 710 (msginfo->mtime - s.st_mtime != 0) && 711 (msginfo->mtime - s.st_mtime != 3600) && 712 (msginfo->mtime - s.st_mtime != -3600))) { 713 return TRUE; 714 } 715 return FALSE; 716 } 717 718 static gint mh_scan_tree(Folder *folder) 719 { 720 FolderItem *item; 721 722 cm_return_val_if_fail(folder != NULL, -1); 723 724 if (!folder->node) { 725 item = folder_item_new(folder, folder->name, NULL); 726 item->folder = folder; 727 folder->node = item->node = g_node_new(item); 728 } else 729 item = FOLDER_ITEM(folder->node->data); 730 731 mh_create_tree(folder); 732 mh_remove_missing_folder_items(folder); 733 mh_scan_tree_recursive(item); 734 735 return 0; 736 } 737 738 #define MAKE_DIR_IF_NOT_EXIST(dir) \ 739 { \ 740 if (!is_dir_exist(dir)) { \ 741 if (is_file_exist(dir)) { \ 742 g_warning("file '%s' already exists, " \ 743 "can't create folder", dir); \ 744 if (rootpath) \ 745 g_free(rootpath); \ 746 if (path) \ 747 g_free(path); \ 748 return -1; \ 749 } \ 750 if (make_dir_hier(dir) < 0) { \ 751 if (rootpath) \ 752 g_free(rootpath); \ 753 if (path) \ 754 g_free(path); \ 755 return -1; \ 756 } \ 757 debug_print("Created dir '%s'\n", dir); \ 758 } \ 759 } 760 761 static gint mh_create_tree(Folder *folder) 762 { 763 gchar *rootpath, *f, *path = NULL; 764 765 cm_return_val_if_fail(folder != NULL, -1); 766 767 rootpath = LOCAL_FOLDER(folder)->rootpath; 768 if (*rootpath == '/') { 769 /* Folder path is absolute. */ 770 rootpath = g_strdup(rootpath); 771 } else { 772 /* Folder path is relative, using mail base dir. */ 773 rootpath = g_strconcat(get_mail_base_dir(), G_DIR_SEPARATOR_S, 774 rootpath, NULL); 775 } 776 777 MAKE_DIR_IF_NOT_EXIST(rootpath); 778 779 /* Create special directories as needed */ 780 if (folder->inbox != NULL && 781 folder->inbox->path != NULL) 782 f = folder->inbox->path; 783 else 784 f = INBOX_DIR; 785 path = g_strconcat(rootpath, G_DIR_SEPARATOR_S, f, NULL); 786 MAKE_DIR_IF_NOT_EXIST(path); 787 g_free(path); 788 789 if (folder->outbox != NULL && 790 folder->outbox->path != NULL) 791 f = folder->outbox->path; 792 else 793 f = OUTBOX_DIR; 794 path = g_strconcat(rootpath, G_DIR_SEPARATOR_S, f, NULL); 795 MAKE_DIR_IF_NOT_EXIST(path); 796 g_free(path); 797 798 if (folder->draft != NULL && 799 folder->draft->path != NULL) 800 f = folder->draft->path; 801 else 802 f = DRAFT_DIR; 803 path = g_strconcat(rootpath, G_DIR_SEPARATOR_S, f, NULL); 804 MAKE_DIR_IF_NOT_EXIST(path); 805 g_free(path); 806 807 if (folder->queue != NULL && 808 folder->queue->path != NULL) 809 f = folder->queue->path; 810 else 811 f = QUEUE_DIR; 812 path = g_strconcat(rootpath, G_DIR_SEPARATOR_S, f, NULL); 813 MAKE_DIR_IF_NOT_EXIST(path); 814 g_free(path); 815 816 if (folder->trash != NULL && 817 folder->trash->path != NULL) 818 f = folder->trash->path; 819 else 820 f = TRASH_DIR; 821 path = g_strconcat(rootpath, G_DIR_SEPARATOR_S, f, NULL); 822 MAKE_DIR_IF_NOT_EXIST(path); 823 g_free(path); 824 825 g_free(rootpath); 826 return 0; 827 } 828 829 #undef MAKE_DIR_IF_NOT_EXIST 830 831 static gchar *mh_item_get_path(Folder *folder, FolderItem *item) 832 { 833 gchar *folder_path, *path; 834 gchar *real_path; 835 cm_return_val_if_fail(folder != NULL, NULL); 836 cm_return_val_if_fail(item != NULL, NULL); 837 838 folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath); 839 cm_return_val_if_fail(folder_path != NULL, NULL); 840 841 if ( !is_relative_filename (folder_path) ) { 842 if (item->path) 843 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S, 844 item->path, NULL); 845 else 846 path = g_strdup(folder_path); 847 } else { 848 if (item->path) 849 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, 850 folder_path, G_DIR_SEPARATOR_S, 851 item->path, NULL); 852 else 853 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, 854 folder_path, NULL); 855 } 856 g_free(folder_path); 857 real_path = mh_filename_from_utf8(path); 858 if (!is_dir_exist(real_path) && is_dir_exist(path)) { 859 /* mmh, older version did put utf8 filenames instead of 860 * the correct encoding */ 861 if (g_rename(path, real_path) == 0) 862 folder_item_scan(item); 863 } 864 865 g_free(path); 866 return real_path; 867 } 868 869 static gboolean mh_renumber_msg(MsgInfo *info) 870 { 871 gchar *src, *dest; 872 gboolean result = FALSE; 873 guint num; 874 cm_return_val_if_fail(info != NULL, FALSE); 875 876 src = folder_item_fetch_msg(info->folder, info->msgnum); 877 dest = mh_get_new_msg_filename(info->folder); 878 num = info->folder->last_num + 1; 879 880 // only renumber the file if dest doesn't already exist. 881 // TODO(otl): why? 882 struct stat sb; 883 if (stat(dest, &sb) && errno == ENOENT) { 884 if (rename(src, dest) < 0) { 885 warn("rename %s to %s", src, dest); 886 free(src); 887 free(dest); 888 return FALSE; 889 } 890 msgcache_remove_msg(info->folder->cache, info->msgnum); 891 info->msgnum = num; 892 msgcache_add_msg(info->folder->cache, info); 893 result = TRUE; 894 } 895 896 g_free(src); 897 g_free(dest); 898 899 return result; 900 } 901 902 static FolderItem *mh_create_folder(Folder *folder, FolderItem *parent, 903 const gchar *name) 904 { 905 gchar *path, *real_name; 906 gchar *fullpath; 907 FolderItem *new_item; 908 909 cm_return_val_if_fail(folder != NULL, NULL); 910 cm_return_val_if_fail(parent != NULL, NULL); 911 cm_return_val_if_fail(name != NULL, NULL); 912 913 path = folder_item_get_path(parent); 914 if (!is_dir_exist(path)) 915 if (make_dir_hier(path) != 0) 916 return NULL; 917 918 real_name = mh_filename_from_utf8(name); 919 fullpath = g_strconcat(path, G_DIR_SEPARATOR_S, real_name, NULL); 920 g_free(real_name); 921 g_free(path); 922 923 if (atoi(name) > 0) { 924 MsgInfo *info = folder_item_get_msginfo(parent, atoi(name)); 925 if (info != NULL) { 926 gboolean ok = mh_renumber_msg(info); 927 procmsg_msginfo_free(&info); 928 if (!ok) { 929 g_free(fullpath); 930 return NULL; 931 } 932 } 933 } 934 935 if (mkdir(fullpath, 0700) < 0) { 936 g_free(fullpath); 937 return NULL; 938 } 939 940 g_free(fullpath); 941 942 if (parent->path) 943 path = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name, 944 NULL); 945 else 946 path = g_strdup(name); 947 new_item = folder_item_new(folder, name, path); 948 folder_item_append(parent, new_item); 949 950 g_free(path); 951 952 return new_item; 953 } 954 955 static gint mh_rename_folder(Folder *folder, FolderItem *item, 956 const gchar *name) 957 { 958 gchar *real_name; 959 gchar *oldpath; 960 gchar *dirname; 961 gchar *newpath, *utf8newpath; 962 gchar *paths[2]; 963 964 cm_return_val_if_fail(folder != NULL, -1); 965 cm_return_val_if_fail(item != NULL, -1); 966 cm_return_val_if_fail(item->path != NULL, -1); 967 cm_return_val_if_fail(name != NULL, -1); 968 969 oldpath = folder_item_get_path(item); 970 if (!is_dir_exist(oldpath)) 971 make_dir_hier(oldpath); 972 973 dirname = g_path_get_dirname(oldpath); 974 real_name = mh_filename_from_utf8(name); 975 newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, real_name, NULL); 976 g_free(real_name); 977 g_free(dirname); 978 979 if (g_rename(oldpath, newpath) < 0) { 980 FILE_OP_ERROR(oldpath, "rename"); 981 g_free(oldpath); 982 g_free(newpath); 983 return -1; 984 } 985 986 g_free(oldpath); 987 g_free(newpath); 988 989 if (strchr(item->path, G_DIR_SEPARATOR) != NULL) { 990 dirname = g_path_get_dirname(item->path); 991 utf8newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, 992 name, NULL); 993 g_free(dirname); 994 } else 995 utf8newpath = g_strdup(name); 996 997 g_free(item->name); 998 item->name = g_strdup(name); 999 1000 paths[0] = g_strdup(item->path); 1001 paths[1] = utf8newpath; 1002 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, 1003 mh_rename_folder_func, paths); 1004 1005 g_free(paths[0]); 1006 g_free(paths[1]); 1007 return 0; 1008 } 1009 1010 static gint mh_remove_folder(Folder *folder, FolderItem *item) 1011 { 1012 gchar *path; 1013 gint ret; 1014 1015 cm_return_val_if_fail(folder != NULL, -1); 1016 cm_return_val_if_fail(item != NULL, -1); 1017 cm_return_val_if_fail(item->path != NULL, -1); 1018 1019 path = folder_item_get_path(item); 1020 if ((ret = remove_dir_recursive(path)) < 0) { 1021 g_warning("can't remove directory '%s'", path); 1022 g_free(path); 1023 return ret; 1024 } 1025 1026 g_free(path); 1027 folder_item_remove(item); 1028 return 0; 1029 } 1030 1031 static MsgInfo *mh_parse_msg(const gchar *file, FolderItem *item) 1032 { 1033 MsgInfo *msginfo; 1034 MsgFlags flags; 1035 1036 cm_return_val_if_fail(item != NULL, NULL); 1037 cm_return_val_if_fail(file != NULL, NULL); 1038 1039 flags.perm_flags = MSG_NEW|MSG_UNREAD; 1040 flags.tmp_flags = 0; 1041 1042 if (folder_has_parent_of_type(item, F_QUEUE)) { 1043 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED); 1044 } else if (folder_has_parent_of_type(item, F_DRAFT)) { 1045 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT); 1046 } 1047 1048 msginfo = procheader_parse_file(file, flags, FALSE, FALSE); 1049 if (!msginfo) return NULL; 1050 1051 msginfo->msgnum = atoi(file); 1052 msginfo->folder = item; 1053 1054 return msginfo; 1055 } 1056 1057 static gboolean mh_remove_missing_folder_items_func(GNode *node, gpointer data) 1058 { 1059 FolderItem *item; 1060 gchar *path; 1061 1062 cm_return_val_if_fail(node->data != NULL, FALSE); 1063 1064 if (G_NODE_IS_ROOT(node)) 1065 return FALSE; 1066 1067 item = FOLDER_ITEM(node->data); 1068 1069 path = folder_item_get_path(item); 1070 if (!is_dir_exist(path)) { 1071 debug_print("folder '%s' not found. removing...\n", path?path:"(null)"); 1072 folder_item_remove(item); 1073 } 1074 g_free(path); 1075 1076 return FALSE; 1077 } 1078 1079 static void mh_remove_missing_folder_items(Folder *folder) 1080 { 1081 cm_return_if_fail(folder != NULL); 1082 1083 debug_print("searching missing folders...\n"); 1084 1085 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, 1086 mh_remove_missing_folder_items_func, folder); 1087 } 1088 1089 static void mh_scan_tree_recursive(FolderItem *item) 1090 { 1091 Folder *folder; 1092 GDir *dir; 1093 const gchar *dir_name; 1094 gchar *entry, *utf8entry, *utf8name, *path; 1095 GError *error = NULL; 1096 1097 cm_return_if_fail(item != NULL); 1098 cm_return_if_fail(item->folder != NULL); 1099 1100 folder = item->folder; 1101 1102 path = folder_item_get_path(item); 1103 debug_print("mh_scan_tree_recursive() opening '%s'\n", path); 1104 dir = g_dir_open(path, 0, &error); 1105 if (!dir) { 1106 g_warning("failed to open directory '%s': %s (%d)", 1107 path, error->message, error->code); 1108 g_error_free(error); 1109 g_free(path); 1110 return; 1111 } 1112 1113 debug_print("scanning %s ...\n", 1114 item->path ? item->path 1115 : LOCAL_FOLDER(item->folder)->rootpath); 1116 if (folder->ui_func) 1117 folder->ui_func(folder, item, folder->ui_func_data); 1118 1119 while ((dir_name = g_dir_read_name(dir)) != NULL) { 1120 if (dir_name[0] == '.') continue; 1121 1122 entry = g_strconcat(path, G_DIR_SEPARATOR_S, dir_name, NULL); 1123 1124 utf8name = mh_filename_to_utf8(dir_name); 1125 if (item->path) 1126 utf8entry = g_strconcat(item->path, G_DIR_SEPARATOR_S, 1127 utf8name, NULL); 1128 else 1129 utf8entry = g_strdup(utf8name); 1130 1131 if (g_file_test(entry, G_FILE_TEST_IS_DIR)) { 1132 FolderItem *new_item = NULL; 1133 GNode *node; 1134 1135 node = item->node; 1136 for (node = node->children; node != NULL; node = node->next) { 1137 FolderItem *cur_item = FOLDER_ITEM(node->data); 1138 gchar *curpath = folder_item_get_path(cur_item); 1139 if (!g_strcmp0(curpath, entry)) { 1140 new_item = cur_item; 1141 g_free(curpath); 1142 break; 1143 } 1144 g_free(curpath); 1145 } 1146 if (!new_item) { 1147 debug_print("new folder '%s' found.\n", entry); 1148 new_item = folder_item_new(folder, utf8name, utf8entry); 1149 folder_item_append(item, new_item); 1150 } 1151 1152 if (!item->path) { 1153 if (!folder->inbox && 1154 !strcmp(dir_name, INBOX_DIR)) { 1155 new_item->stype = F_INBOX; 1156 folder->inbox = new_item; 1157 } else if (!folder->outbox && 1158 !strcmp(dir_name, OUTBOX_DIR)) { 1159 new_item->stype = F_OUTBOX; 1160 folder->outbox = new_item; 1161 } else if (!folder->draft && 1162 !strcmp(dir_name, DRAFT_DIR)) { 1163 new_item->stype = F_DRAFT; 1164 folder->draft = new_item; 1165 } else if (!folder->queue && 1166 !strcmp(dir_name, QUEUE_DIR)) { 1167 new_item->stype = F_QUEUE; 1168 folder->queue = new_item; 1169 } else if (!folder->trash && 1170 !strcmp(dir_name, TRASH_DIR)) { 1171 new_item->stype = F_TRASH; 1172 folder->trash = new_item; 1173 } 1174 } 1175 1176 mh_scan_tree_recursive(new_item); 1177 } 1178 1179 g_free(entry); 1180 g_free(utf8entry); 1181 g_free(utf8name); 1182 } 1183 1184 g_dir_close(dir); 1185 g_free(path); 1186 1187 mh_set_mtime(folder, item); 1188 } 1189 1190 static gboolean mh_rename_folder_func(GNode *node, gpointer data) 1191 { 1192 FolderItem *item = node->data; 1193 gchar **paths = data; 1194 const gchar *oldpath = paths[0]; 1195 const gchar *newpath = paths[1]; 1196 gchar *base; 1197 gchar *new_itempath; 1198 gint oldpathlen; 1199 1200 oldpathlen = strlen(oldpath); 1201 if (strncmp(oldpath, item->path, oldpathlen) != 0) { 1202 g_warning("path doesn't match: %s, %s", oldpath, item->path); 1203 return TRUE; 1204 } 1205 1206 base = item->path + oldpathlen; 1207 while (*base == G_DIR_SEPARATOR) base++; 1208 if (*base == '\0') 1209 new_itempath = g_strdup(newpath); 1210 else 1211 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base, 1212 NULL); 1213 g_free(item->path); 1214 item->path = new_itempath; 1215 1216 return FALSE; 1217 } 1218 1219 static gchar *mh_filename_from_utf8(const gchar *path) 1220 { 1221 gchar *real_path = g_filename_from_utf8(path, -1, NULL, NULL, NULL); 1222 1223 if (!real_path) { 1224 g_warning("mh_filename_from_utf8: failed to convert character set"); 1225 real_path = g_strdup(path); 1226 } 1227 1228 return real_path; 1229 } 1230 1231 static gchar *mh_filename_to_utf8(const gchar *path) 1232 { 1233 gchar *utf8path = g_filename_to_utf8(path, -1, NULL, NULL, NULL); 1234 if (!utf8path) { 1235 g_warning("mh_filename_to_utf8: failed to convert character set"); 1236 utf8path = g_strdup(path); 1237 } 1238 1239 return utf8path; 1240 } 1241 1242 static int mh_item_close(Folder *folder, FolderItem *item) 1243 { 1244 time_t last_mtime = (time_t)0; 1245 gboolean need_scan = mh_scan_required(item->folder, item); 1246 last_mtime = item->mtime; 1247 1248 if (item->mtime == last_mtime && !need_scan) { 1249 mh_set_mtime(folder, item); 1250 } 1251 1252 return 0; 1253 } 1254 1255 static void mh_set_mtime(Folder *folder, FolderItem *item) 1256 { 1257 GStatBuf s; 1258 gchar *path = folder_item_get_path(item); 1259 1260 cm_return_if_fail(path != NULL); 1261 1262 if (g_stat(path, &s) < 0) { 1263 FILE_OP_ERROR(path, "stat"); 1264 g_free(path); 1265 return; 1266 } 1267 1268 item->mtime = s.st_mtime; 1269 debug_print("MH: forced mtime of %s to %lld\n", item->name?item->name:"(null)", item->mtime); 1270 g_free(path); 1271 }