1 /* 2 * Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de. 3 * Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 //! The daemon's inner workings 10 11 12 #include <Application.h> 13 #include <Message.h> 14 #include <File.h> 15 #include <MessageRunner.h> 16 #include <Deskbar.h> 17 #include <Roster.h> 18 #include <Button.h> 19 #include <StringView.h> 20 #include <Mime.h> 21 #include <Beep.h> 22 #include <fs_index.h> 23 #include <fs_info.h> 24 #include <String.h> 25 #include <VolumeRoster.h> 26 #include <Query.h> 27 #include <ChainRunner.h> 28 #include <NodeMonitor.h> 29 #include <Path.h> 30 31 #include <string.h> 32 #include <stdio.h> 33 #include <errno.h> 34 #include <sys/socket.h> 35 #include <map> 36 37 #include <E-mail.h> 38 #include <MailSettings.h> 39 #include <MailMessage.h> 40 #include <status.h> 41 #include <StringList.h> 42 43 #include "DeskbarView.h" 44 #include "LEDAnimation.h" 45 46 #include <MDRLanguage.h> 47 48 49 using std::map; 50 51 typedef struct glorbal { 52 size_t bytes; 53 BStringList msgs; 54 } snuzzwut; 55 56 57 static BMailStatusWindow* sStatus; 58 59 60 class MailDaemonApp : public BApplication { 61 public: 62 MailDaemonApp(); 63 virtual ~MailDaemonApp(); 64 65 virtual void MessageReceived(BMessage* message); 66 virtual void RefsReceived(BMessage* message); 67 68 virtual void Pulse(); 69 virtual bool QuitRequested(); 70 virtual void ReadyToRun(); 71 72 void InstallDeskbarIcon(); 73 void RemoveDeskbarIcon(); 74 75 void RunChains(BList& list, BMessage* message); 76 void SendPendingMessages(BMessage* message); 77 void GetNewMessages(BMessage* message); 78 79 private: 80 void _UpdateAutoCheck(bigtime_t interval); 81 bool _IsPending(BNode& entry) const; 82 83 private: 84 BMessageRunner* fAutoCheckRunner; 85 BMailSettings fSettingsFile; 86 87 int32 fNewMessages; 88 bool fCentralBeep; 89 // TRUE to do a beep when the status window closes. This happens 90 // when all mail has been received, so you get one beep for 91 // everything rather than individual beeps for each mail 92 // account. 93 // Set to TRUE by the 'mcbp' message that the mail Notification 94 // filter sends us, cleared when the beep is done. 95 BList fFetchDoneRespondents; 96 BList fQueries; 97 98 LEDAnimation* fLEDAnimation; 99 100 BString fAlertString; 101 }; 102 103 104 MailDaemonApp::MailDaemonApp() 105 : 106 BApplication("application/x-vnd.Be-POST") 107 { 108 sStatus = new BMailStatusWindow(BRect(40, 400, 360, 400), "Mail Status", 109 fSettingsFile.ShowStatusWindow()); 110 fAutoCheckRunner = NULL; 111 } 112 113 114 MailDaemonApp::~MailDaemonApp() 115 { 116 delete fAutoCheckRunner; 117 118 for (int32 i = 0; i < fQueries.CountItems(); i++) 119 delete (BQuery*)fQueries.ItemAt(i); 120 121 delete fLEDAnimation; 122 } 123 124 125 void 126 MailDaemonApp::ReadyToRun() 127 { 128 InstallDeskbarIcon(); 129 130 _UpdateAutoCheck(fSettingsFile.AutoCheckInterval()); 131 132 BVolume volume; 133 BVolumeRoster roster; 134 135 fNewMessages = 0; 136 137 while (roster.GetNextVolume(&volume) == B_OK) { 138 //{char name[255];volume.GetName(name);printf("Volume: %s\n",name);} 139 140 BQuery* query = new BQuery; 141 142 query->SetTarget(this); 143 query->SetVolume(&volume); 144 query->PushAttr(B_MAIL_ATTR_STATUS); 145 query->PushString("New"); 146 query->PushOp(B_EQ); 147 query->PushAttr("BEOS:TYPE"); 148 query->PushString("text/x-email"); 149 query->PushOp(B_EQ); 150 query->PushAttr("BEOS:TYPE"); 151 query->PushString("text/x-partial-email"); 152 query->PushOp(B_EQ); 153 query->PushOp(B_OR); 154 query->PushOp(B_AND); 155 query->Fetch(); 156 157 BEntry entry; 158 while (query->GetNextEntry(&entry) == B_OK) 159 fNewMessages++; 160 161 fQueries.AddItem(query); 162 } 163 164 BString string; 165 MDR_DIALECT_CHOICE( 166 if (fNewMessages > 0) 167 string << fNewMessages; 168 else 169 string << "No"; 170 if (fNewMessages != 1) 171 string << " new messages."; 172 else 173 string << " new message.";, 174 if (fNewMessages > 0) 175 string << fNewMessages << " 通の未読メッセージがあります "; 176 else 177 string << "未読メッセージはありません"; 178 ); 179 fCentralBeep = false; 180 sStatus->SetDefaultMessage(string); 181 182 fLEDAnimation = new LEDAnimation; 183 SetPulseRate(1000000); 184 } 185 186 187 void 188 MailDaemonApp::RefsReceived(BMessage* message) 189 { 190 sStatus->Activate(true); 191 192 entry_ref ref; 193 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 194 BNode node(&ref); 195 if (node.InitCheck() < B_OK) 196 continue; 197 198 BString uid; 199 if (node.ReadAttrString("MAIL:unique_id", &uid) < 0) 200 continue; 201 202 int32 id; 203 if (node.ReadAttr("MAIL:chain", B_INT32_TYPE, 0, &id, sizeof(id)) < 0) 204 continue; 205 206 int32 size; 207 if (node.ReadAttr("MAIL:fullsize", B_SIZE_T_TYPE, 0, &size, 208 sizeof(size)) < 0) { 209 size = -1; 210 } 211 212 BPath path(&ref); 213 BMailChainRunner* runner = GetMailChainRunner(id, sStatus); 214 if (runner != NULL) 215 runner->GetSingleMessage(uid.String(), size, &path); 216 } 217 } 218 219 220 void 221 MailDaemonApp::_UpdateAutoCheck(bigtime_t interval) 222 { 223 if (interval > 0) { 224 if (fAutoCheckRunner != NULL) { 225 fAutoCheckRunner->SetInterval(interval); 226 fAutoCheckRunner->SetCount(-1); 227 } else 228 fAutoCheckRunner = new BMessageRunner(be_app_messenger, 229 new BMessage('moto'), interval); 230 } else { 231 delete fAutoCheckRunner; 232 fAutoCheckRunner = NULL; 233 } 234 } 235 236 237 void 238 MailDaemonApp::MessageReceived(BMessage* msg) 239 { 240 switch (msg->what) { 241 case 'moto': 242 if (fSettingsFile.CheckOnlyIfPPPUp()) { 243 // TODO: check whether internet is up and running! 244 } 245 // supposed to fall through 246 case 'mbth': // check & send messages 247 msg->what = 'msnd'; 248 PostMessage(msg); 249 // supposed to fall trough 250 case 'mnow': // check messages 251 GetNewMessages(msg); 252 break; 253 254 case 'msnd': // send messages 255 SendPendingMessages(msg); 256 break; 257 258 case 'mrrs': 259 fSettingsFile.Reload(); 260 _UpdateAutoCheck(fSettingsFile.AutoCheckInterval()); 261 sStatus->SetShowCriterion(fSettingsFile.ShowStatusWindow()); 262 break; 263 case 'shst': // when to show the status window 264 { 265 int32 mode; 266 if (msg->FindInt32("ShowStatusWindow", &mode) == B_OK) 267 sStatus->SetShowCriterion(mode); 268 break; 269 } 270 271 case 'lkch': // status window look changed 272 case 'wsch': // workspace changed 273 sStatus->PostMessage(msg); 274 break; 275 276 case 'stwg': // Status window gone 277 { 278 BMessage reply('mnuc'); 279 reply.AddInt32("num_new_messages", fNewMessages); 280 281 while ((msg = (BMessage*)fFetchDoneRespondents.RemoveItem(0L))) { 282 msg->SendReply(&reply); 283 delete msg; 284 } 285 286 if (fAlertString != B_EMPTY_STRING) { 287 fAlertString.Truncate(fAlertString.Length() - 1); 288 BAlert* alert = new BAlert(MDR_DIALECT_CHOICE("New Messages", 289 "新着メッセージ"), fAlertString.String(), "OK", NULL, NULL, 290 B_WIDTH_AS_USUAL); 291 alert->SetFeel(B_NORMAL_WINDOW_FEEL); 292 alert->Go(NULL); 293 fAlertString = B_EMPTY_STRING; 294 } 295 296 if (fCentralBeep) { 297 system_beep("New E-mail"); 298 fCentralBeep = false; 299 } 300 break; 301 } 302 303 case 'mcbp': 304 if (fNewMessages > 0) 305 fCentralBeep = true; 306 break; 307 308 case 'mnum': // Number of new messages 309 { 310 BMessage reply('mnuc'); // Mail New message Count 311 if (msg->FindBool("wait_for_fetch_done")) { 312 fFetchDoneRespondents.AddItem(DetachCurrentMessage()); 313 break; 314 } 315 316 reply.AddInt32("num_new_messages", fNewMessages); 317 msg->SendReply(&reply); 318 break; 319 } 320 321 case 'mblk': // Mail Blink 322 if (fNewMessages > 0) 323 fLEDAnimation->Start(); 324 break; 325 326 case 'enda': // End Auto Check 327 delete fAutoCheckRunner; 328 fAutoCheckRunner = NULL; 329 break; 330 331 case 'numg': 332 { 333 int32 numMessages = msg->FindInt32("num_messages"); 334 MDR_DIALECT_CHOICE( 335 fAlertString << numMessages << " new message"; 336 if (numMessages > 1) 337 fAlertString << 's'; 338 339 fAlertString << " for " << msg->FindString("chain_name") 340 << '\n';, 341 342 fAlertString << msg->FindString("chain_name") << "より\n" 343 << numMessages << " 通のメッセージが届きました "; 344 ); 345 break; 346 } 347 348 case B_QUERY_UPDATE: 349 { 350 int32 what; 351 msg->FindInt32("opcode", &what); 352 switch (what) { 353 case B_ENTRY_CREATED: 354 fNewMessages++; 355 break; 356 case B_ENTRY_REMOVED: 357 fNewMessages--; 358 break; 359 } 360 361 BString string; 362 363 MDR_DIALECT_CHOICE( 364 if (fNewMessages > 0) 365 string << fNewMessages; 366 else 367 string << "No"; 368 if (fNewMessages != 1) 369 string << " new messages."; 370 else 371 string << " new message.";, 372 373 if (fNewMessages > 0) 374 string << fNewMessages << " 通の未読メッセージがあります"; 375 else 376 string << "未読メッセージはありません"; 377 ); 378 379 sStatus->SetDefaultMessage(string.String()); 380 break; 381 } 382 383 default: 384 BApplication::MessageReceived(msg); 385 break; 386 } 387 } 388 389 390 void 391 MailDaemonApp::InstallDeskbarIcon() 392 { 393 BDeskbar deskbar; 394 395 if (!deskbar.HasItem("mail_daemon")) { 396 BRoster roster; 397 entry_ref ref; 398 399 status_t status = roster.FindApp("application/x-vnd.Be-POST", &ref); 400 if (status < B_OK) { 401 fprintf(stderr, "Can't find application to tell deskbar: %s\n", 402 strerror(status)); 403 return; 404 } 405 406 status = deskbar.AddItem(&ref); 407 if (status < B_OK) { 408 fprintf(stderr, "Can't add deskbar replicant: %s\n", strerror(status)); 409 return; 410 } 411 } 412 } 413 414 415 void 416 MailDaemonApp::RemoveDeskbarIcon() 417 { 418 BDeskbar deskbar; 419 if (deskbar.HasItem("mail_daemon")) 420 deskbar.RemoveItem("mail_daemon"); 421 } 422 423 424 bool 425 MailDaemonApp::QuitRequested() 426 { 427 RemoveDeskbarIcon(); 428 429 return true; 430 } 431 432 433 void 434 MailDaemonApp::RunChains(BList& list, BMessage* msg) 435 { 436 BMailChain* chain; 437 438 int32 index = 0, id; 439 for (; msg->FindInt32("chain", index, &id) == B_OK; index++) { 440 for (int32 i = 0; i < list.CountItems(); i++) { 441 chain = (BMailChain*)list.ItemAt(i); 442 443 if (chain->ID() == (unsigned)id) { 444 chain->RunChain(sStatus, true, false, true); 445 list.RemoveItem(i); // the chain runner deletes the chain 446 break; 447 } 448 } 449 } 450 451 if (index == 0) { 452 // invoke all chains 453 for (int32 i = 0; i < list.CountItems(); i++) { 454 chain = (BMailChain*)list.ItemAt(i); 455 456 chain->RunChain(sStatus, true, false, true); 457 } 458 } else { 459 // delete unused chains 460 for (int32 i = list.CountItems(); i-- > 0;) 461 delete (BMailChain*)list.RemoveItem(i); 462 } 463 } 464 465 466 void 467 MailDaemonApp::GetNewMessages(BMessage* msg) 468 { 469 BList list; 470 GetInboundMailChains(&list); 471 472 RunChains(list, msg); 473 } 474 475 476 void 477 MailDaemonApp::SendPendingMessages(BMessage* msg) 478 { 479 BVolumeRoster roster; 480 BVolume volume; 481 482 while (roster.GetNextVolume(&volume) == B_OK) { 483 BQuery query; 484 query.SetVolume(&volume); 485 query.PushAttr(B_MAIL_ATTR_FLAGS); 486 query.PushInt32(B_MAIL_PENDING); 487 query.PushOp(B_EQ); 488 489 query.PushAttr(B_MAIL_ATTR_FLAGS); 490 query.PushInt32(B_MAIL_PENDING | B_MAIL_SAVE); 491 query.PushOp(B_EQ); 492 493 query.PushOp(B_OR); 494 495 int32 chainID = -1; 496 497 if (msg->FindInt32("chain", &chainID) == B_OK) { 498 query.PushAttr("MAIL:chain"); 499 query.PushInt32(chainID); 500 query.PushOp(B_EQ); 501 query.PushOp(B_AND); 502 } else 503 chainID = -1; 504 505 if (!msg->HasString("message_path")) { 506 if (chainID == -1) { 507 map<int32, snuzzwut*> messages; 508 509 query.Fetch(); 510 BEntry entry; 511 BPath path; 512 BNode node; 513 int32 chain; 514 int32 defaultChain(BMailSettings().DefaultOutboundChainID()); 515 off_t size; 516 517 while (query.GetNextEntry(&entry) == B_OK) { 518 while (node.SetTo(&entry) == B_BUSY) 519 snooze(1000); 520 if (!_IsPending(node)) 521 continue; 522 523 if (node.ReadAttr("MAIL:chain", B_INT32_TYPE, 0, &chain, 4) 524 < B_OK) 525 chain = defaultChain; 526 entry.GetPath(&path); 527 node.GetSize(&size); 528 if (messages[chain] == NULL) { 529 messages[chain] = new snuzzwut; 530 messages[chain]->bytes = 0; 531 } 532 533 messages[chain]->msgs += path.Path(); 534 messages[chain]->bytes += size; 535 } 536 537 map<int32, snuzzwut*>::iterator iter = messages.begin(); 538 map<int32, snuzzwut*>::iterator end = messages.end(); 539 while (iter != end) { 540 if (iter->first > 0 && BMailChain(iter->first) 541 .ChainDirection() == outbound) { 542 BMailChainRunner* runner 543 = GetMailChainRunner(iter->first, sStatus); 544 runner->GetMessages(&messages[iter->first]->msgs, 545 messages[iter->first]->bytes); 546 delete messages[iter->first]; 547 runner->Stop(); 548 } 549 550 iter++; 551 } 552 } else { 553 BStringList ids; 554 size_t bytes = 0; 555 556 query.Fetch(); 557 BEntry entry; 558 BPath path; 559 BNode node; 560 off_t size; 561 562 while (query.GetNextEntry(&entry) == B_OK) { 563 node.SetTo(&entry); 564 if (!_IsPending(node)) 565 continue; 566 567 entry.GetPath(&path); 568 node.GetSize(&size); 569 ids += path.Path(); 570 bytes += size; 571 } 572 573 BMailChainRunner* runner 574 = GetMailChainRunner(chainID, sStatus); 575 runner->GetMessages(&ids, bytes); 576 runner->Stop(); 577 } 578 } else { 579 const char* path; 580 if (msg->FindString("message_path", &path) != B_OK) 581 return; 582 583 off_t size; 584 if (BNode(path).GetSize(&size) != B_OK) 585 return; 586 587 BStringList ids; 588 ids += path; 589 590 BMailChainRunner* runner = GetMailChainRunner(chainID, sStatus); 591 runner->GetMessages(&ids, size); 592 runner->Stop(); 593 } 594 } 595 } 596 597 598 void 599 MailDaemonApp::Pulse() 600 { 601 bigtime_t idle = idle_time(); 602 if (fLEDAnimation->IsRunning() && idle < 100000) 603 fLEDAnimation->Stop(); 604 } 605 606 607 /*! Work-around for a broken index that contains out-of-date information. 608 */ 609 bool 610 MailDaemonApp::_IsPending(BNode& node) const 611 { 612 int32 flags; 613 if (node.ReadAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32)) 614 != (ssize_t)sizeof(int32)) 615 return false; 616 617 return (flags & B_MAIL_PENDING) != 0; 618 } 619 620 621 // #pragma mark - 622 623 624 void 625 makeIndices() 626 { 627 const char* stringIndices[] = { 628 B_MAIL_ATTR_ACCOUNT, B_MAIL_ATTR_CC, B_MAIL_ATTR_FROM, B_MAIL_ATTR_NAME, 629 B_MAIL_ATTR_PRIORITY, B_MAIL_ATTR_REPLY, B_MAIL_ATTR_STATUS, 630 B_MAIL_ATTR_SUBJECT, B_MAIL_ATTR_TO, B_MAIL_ATTR_THREAD, 631 NULL 632 }; 633 634 // add mail indices for all devices capable of querying 635 636 int32 cookie = 0; 637 dev_t device; 638 while ((device = next_dev(&cookie)) >= B_OK) { 639 fs_info info; 640 if (fs_stat_dev(device, &info) < 0 641 || (info.flags & B_FS_HAS_QUERY) == 0) 642 continue; 643 644 for (int32 i = 0; stringIndices[i]; i++) 645 fs_create_index(device, stringIndices[i], B_STRING_TYPE, 0); 646 647 fs_create_index(device, "MAIL:draft", B_INT32_TYPE, 0); 648 fs_create_index(device, B_MAIL_ATTR_WHEN, B_INT32_TYPE, 0); 649 fs_create_index(device, B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0); 650 fs_create_index(device, "MAIL:chain", B_INT32_TYPE, 0); 651 fs_create_index(device, "MAIL:pending_chain", B_INT32_TYPE, 0); 652 } 653 } 654 655 656 void 657 addAttribute(BMessage& msg, const char* name, const char* publicName, 658 int32 type = B_STRING_TYPE, bool viewable = true, bool editable = false, 659 int32 width = 200) 660 { 661 msg.AddString("attr:name", name); 662 msg.AddString("attr:public_name", publicName); 663 msg.AddInt32("attr:type", type); 664 msg.AddBool("attr:viewable", viewable); 665 msg.AddBool("attr:editable", editable); 666 msg.AddInt32("attr:width", width); 667 msg.AddInt32("attr:alignment", B_ALIGN_LEFT); 668 } 669 670 671 void 672 makeMimeType(bool remakeMIMETypes) 673 { 674 // Add MIME database entries for the e-mail file types we handle. Either 675 // do a full rebuild from nothing, or just add on the new attributes that 676 // we support which the regular BeOS mail daemon didn't have. 677 678 const char* types[2] = {"text/x-email", "text/x-partial-email"}; 679 BMimeType mime; 680 BMessage info; 681 682 for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); i++) { 683 info.MakeEmpty(); 684 mime.SetTo(types[i]); 685 if (mime.InitCheck() != B_OK) { 686 fputs("could not init mime type.\n", stderr); 687 return; 688 } 689 690 if (!mime.IsInstalled() || remakeMIMETypes) { 691 // install the full mime type 692 mime.Delete (); 693 mime.Install(); 694 695 // Set up the list of e-mail related attributes that Tracker will 696 // let you display in columns for e-mail messages. 697 addAttribute(info, B_MAIL_ATTR_NAME, "Name"); 698 addAttribute(info, B_MAIL_ATTR_SUBJECT, "Subject"); 699 addAttribute(info, B_MAIL_ATTR_TO, "To"); 700 addAttribute(info, B_MAIL_ATTR_CC, "Cc"); 701 addAttribute(info, B_MAIL_ATTR_FROM, "From"); 702 addAttribute(info, B_MAIL_ATTR_REPLY, "Reply To"); 703 addAttribute(info, B_MAIL_ATTR_STATUS, "Status"); 704 addAttribute(info, B_MAIL_ATTR_PRIORITY, "Priority", B_STRING_TYPE, 705 true, true, 40); 706 addAttribute(info, B_MAIL_ATTR_WHEN, "When", B_TIME_TYPE, true, 707 false, 150); 708 addAttribute(info, B_MAIL_ATTR_THREAD, "Thread"); 709 addAttribute(info, B_MAIL_ATTR_ACCOUNT, "Account", B_STRING_TYPE, 710 true, false, 100); 711 mime.SetAttrInfo(&info); 712 713 if (i == 0) { 714 mime.SetShortDescription("E-mail"); 715 mime.SetLongDescription("Electronic Mail Message"); 716 mime.SetPreferredApp("application/x-vnd.Be-MAIL"); 717 } else { 718 mime.SetShortDescription("Partial E-mail"); 719 mime.SetLongDescription("A Partially Downloaded E-mail"); 720 mime.SetPreferredApp("application/x-vnd.Be-POST"); 721 } 722 } else { 723 // Just add the e-mail related attribute types we use to the MIME 724 // system. 725 mime.GetAttrInfo(&info); 726 bool hasAccount = false; 727 bool hasThread = false; 728 bool hasSize = false; 729 const char* result; 730 for (int32 index = 0; info.FindString("attr:name", index, &result) 731 == B_OK; index++) { 732 if (!strcmp(result, B_MAIL_ATTR_ACCOUNT)) 733 hasAccount = true; 734 if (!strcmp(result, B_MAIL_ATTR_THREAD)) 735 hasThread = true; 736 if (!strcmp(result, "MAIL:fullsize")) 737 hasSize = true; 738 } 739 740 if (!hasAccount) { 741 addAttribute(info, B_MAIL_ATTR_ACCOUNT, "Account", 742 B_STRING_TYPE, true, false, 100); 743 } 744 if (!hasThread) 745 addAttribute(info, B_MAIL_ATTR_THREAD, "Thread"); 746 /*if (!hasSize) 747 addAttribute(info,"MAIL:fullsize","Message Size",B_SIZE_T_TYPE,true,false,100);*/ 748 // TODO: Tracker can't display SIZT attributes. What a pain. 749 if (!hasAccount || !hasThread/* || !hasSize*/) 750 mime.SetAttrInfo(&info); 751 } 752 mime.Unset(); 753 } 754 } 755 756 757 int 758 main(int argc, const char** argv) 759 { 760 bool remakeMIMETypes = false; 761 762 for (int i = 1; i < argc; i++) { 763 if (strcmp(argv[i], "-E") == 0) { 764 if (!BMailSettings().DaemonAutoStarts()) 765 return 0; 766 } 767 if (strcmp(argv[i], "-M") == 0) { 768 remakeMIMETypes = true; 769 } 770 } 771 772 MailDaemonApp app; 773 774 // install MimeTypes, attributes, indices, and the 775 // system beep add startup 776 777 makeMimeType(remakeMIMETypes); 778 makeIndices(); 779 add_system_beep_event("New E-mail"); 780 781 be_app->Run(); 782 return 0; 783 } 784 785