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