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