1/* 2 * Copyright 2010-2011, Haiku Inc. All Rights Reserved. 3 * Copyright 2010 Clemens Zeidler. All rights reserved. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9#include "IMAPStorage.h" 10 11#include <stdlib.h> 12 13#include <Directory.h> 14#include <Entry.h> 15#include <NodeInfo.h> 16#include <OS.h> 17 18#include <mail_util.h> 19 20#include "IMAPMailbox.h" 21 22 23#define DEBUG_IMAP_STORAGE 24#ifdef DEBUG_IMAP_STORAGE 25# include <stdio.h> 26# define TRACE(x...) printf(x) 27#else 28# define TRACE(x...) /* nothing */ 29#endif 30 31 32status_t 33IMAPMailboxSync::Sync(IMAPStorage& storage, IMAPMailbox& mailbox) 34{ 35 const MailEntryMap& files = storage.GetFiles(); 36 const MinMessageList& messages = mailbox.GetMessageList(); 37 38 for (MailEntryMap::const_iterator it = files.begin(); it != files.end(); 39 it++) { 40 const StorageMailEntry& mailEntry = (*it).second; 41 bool found = false; 42 for (unsigned int a = 0; a < messages.size(); a++) { 43 const MinMessage& minMessage = messages[a]; 44 if (mailEntry.uid == minMessage.uid) { 45 found = true; 46 if (mailEntry.flags != minMessage.flags) 47 storage.SetFlags(mailEntry.uid, minMessage.flags); 48 break; 49 } 50 } 51 if (!found) 52 storage.DeleteMessage(mailEntry.uid); 53 } 54 55 for (unsigned int i = 0; i < messages.size(); i++) { 56 const MinMessage& minMessage = messages[i]; 57 bool found = false; 58 for (MailEntryMap::const_iterator it = files.begin(); it != files.end(); 59 it++) { 60 const StorageMailEntry& mailEntry = (*it).second; 61 62 if (mailEntry.uid == minMessage.uid) { 63 found = true; 64 break; 65 } 66 } 67 if (!found) 68 fToFetchList.push_back(i + 1); 69 } 70 71 mailbox.Listener().NewMessagesToFetch(fToFetchList.size()); 72 73 // fetch headers in big bunches if possible 74 int32 fetchCount = 0; 75 int32 start = -1; 76 int32 end = -1; 77 int32 lastId = -1; 78 for (unsigned int i = 0; i < fToFetchList.size(); i++) { 79 if (mailbox.StopNow()) 80 return B_ERROR; 81 82 int32 current = fToFetchList[i]; 83 fetchCount++; 84 85 if (start < 0) { 86 start = current; 87 lastId = current; 88 continue; 89 } 90 if (current - 1 == lastId) { 91 end = current; 92 lastId = current; 93 // limit to a fix number to make it interruptable see StopNow() 94 if (fetchCount < 250) 95 continue; 96 } 97 98 fetchCount = 0; 99 mailbox.FetchMessages(start, end); 100 start = -1; 101 end = -1; 102 } 103 if (start > 0) 104 mailbox.FetchMessages(start, end); 105 106 return B_OK; 107} 108 109 110// #pragma mark - 111 112 113IMAPStorage::IMAPStorage() 114{ 115 fLoadDatabaseLock = create_sem(1, "sync lock"); 116} 117 118 119IMAPStorage::~IMAPStorage() 120{ 121 delete_sem(fLoadDatabaseLock); 122} 123 124 125void 126IMAPStorage::SetTo(const char* dir) 127{ 128 fMailboxPath.SetTo(dir); 129} 130 131 132status_t 133IMAPStorage::StartReadDatabase() 134{ 135 status_t status = create_directory(fMailboxPath.Path(), 0755); 136 if (status != B_OK) 137 return status; 138 139 thread_id id = spawn_thread(_ReadFilesThreadFunction, "read mailbox", 140 B_LOW_PRIORITY, this); 141 if (id < 0) 142 return id; 143 144 // will be unlocked from thread 145 acquire_sem(fLoadDatabaseLock); 146 status = resume_thread(id); 147 if (status != B_OK) 148 release_sem(fLoadDatabaseLock); 149 return status; 150} 151 152 153status_t 154IMAPStorage::WaitForDatabaseRead() 155{ 156 // just wait for thread 157 if (acquire_sem(fLoadDatabaseLock) != B_OK) 158 return B_ERROR; 159 release_sem(fLoadDatabaseLock); 160 return B_OK; 161} 162 163 164status_t 165IMAPStorage::AddNewMessage(int32 uid, int32 flags, BPositionIO** file) 166{ 167 if (file != NULL) 168 *file = NULL; 169 // TODO: make sure there is not a valid mail with this name 170 BString fileName = "Downloading file... uid: "; 171 fileName << uid; 172 173 BPath filePath = fMailboxPath; 174 filePath.Append(fileName); 175 TRACE("AddNewMessage %s\n", filePath.Path()); 176 BFile* newFile = new BFile(filePath.Path(), B_READ_WRITE | B_CREATE_FILE 177 | B_ERASE_FILE); 178 if (newFile == NULL) 179 return B_NO_MEMORY; 180 181 StorageMailEntry storageEntry; 182 storageEntry.uid = uid; 183 storageEntry.flags = flags; 184 storageEntry.fileName = fileName; 185 newFile->GetNodeRef(&storageEntry.nodeRef); 186 187 if (_WriteUniqueID(*newFile, uid) != B_OK) { 188 delete newFile; 189 return B_ERROR; 190 } 191 192 status_t status = _WriteFlags(flags, *newFile); 193 if (status != B_OK) { 194 delete newFile; 195 return status; 196 } 197 198 if (file) 199 *file = newFile; 200 else 201 delete newFile; 202 fMailEntryMap[uid] = storageEntry; 203 return B_OK; 204} 205 206 207status_t 208IMAPStorage::OpenMessage(int32 uid, BPositionIO** file) 209{ 210 *file = NULL; 211 212 MailEntryMap::const_iterator it = fMailEntryMap.find(uid); 213 if (it == fMailEntryMap.end()) 214 return B_BAD_VALUE; 215 216 const StorageMailEntry& storageEntry = (*it).second; 217 BPath filePath = fMailboxPath; 218 filePath.Append(storageEntry.fileName); 219 BFile* ourFile = new BFile(filePath.Path(), B_READ_WRITE); 220 if (!ourFile) 221 return B_NO_MEMORY; 222 status_t status = ourFile->InitCheck(); 223 if (status != B_OK) { 224 delete *file; 225 return status; 226 } 227 *file = ourFile; 228 return B_OK; 229} 230 231 232status_t 233IMAPStorage::DeleteMessage(int32 uid) 234{ 235 MailEntryMap::iterator it = fMailEntryMap.find(uid); 236 if (it == fMailEntryMap.end()) 237 return B_ENTRY_NOT_FOUND; 238 const StorageMailEntry& storageEntry = (*it).second; 239 240 BPath filePath = fMailboxPath; 241 filePath.Append(storageEntry.fileName); 242 BEntry entry(filePath.Path()); 243 TRACE("IMAPStorage::DeleteMessage %s, %ld\n", filePath.Path(), uid); 244 245 status_t status = entry.Remove(); 246 if (status != B_OK) 247 return status; 248 fMailEntryMap.erase(it); 249 return B_OK; 250} 251 252 253status_t 254IMAPStorage::SetFlags(int32 uid, int32 flags) 255{ 256 MailEntryMap::iterator it = fMailEntryMap.find(uid); 257 if (it == fMailEntryMap.end()) 258 return B_ENTRY_NOT_FOUND; 259 StorageMailEntry& entry = (*it).second; 260 261 BPath filePath = fMailboxPath; 262 filePath.Append(entry.fileName); 263 BNode node(filePath.Path()); 264 265 status_t status = _WriteFlags(flags, node); 266 if (status != B_OK) 267 return status; 268 269 entry.flags = flags; 270 return B_OK; 271} 272 273 274int32 275IMAPStorage::GetFlags(int32 uid) 276{ 277 MailEntryMap::const_iterator it = fMailEntryMap.find(uid); 278 if (it == fMailEntryMap.end()) 279 return -1; 280 const StorageMailEntry& entry = (*it).second; 281 return entry.flags; 282} 283 284 285status_t 286IMAPStorage::SetFileName(int32 uid, const BString& name) 287{ 288 MailEntryMap::iterator it = fMailEntryMap.find(uid); 289 if (it == fMailEntryMap.end()) 290 return -1; 291 StorageMailEntry& storageEntry = (*it).second; 292 BPath filePath = fMailboxPath; 293 filePath.Append(storageEntry.fileName); 294 BEntry entry(filePath.Path()); 295 status_t status = entry.Rename(name); 296 if (status != B_OK) 297 return status; 298 storageEntry.fileName = name; 299 return B_OK; 300} 301 302 303status_t 304IMAPStorage::FileRenamed(const entry_ref& from, const entry_ref& to) 305{ 306 int32 uid = RefToUID(from); 307 if (uid < 0) 308 return B_BAD_VALUE; 309 fMailEntryMap[uid].fileName = to.name; 310 return B_OK; 311} 312 313 314bool 315IMAPStorage::HasFile(int32 uid) 316{ 317 MailEntryMap::const_iterator it = fMailEntryMap.find(uid); 318 if (it == fMailEntryMap.end()) 319 return false; 320 return true; 321} 322 323 324status_t 325IMAPStorage::SetCompleteMessageSize(int32 uid, int32 size) 326{ 327 MailEntryMap::iterator it = fMailEntryMap.find(uid); 328 if (it == fMailEntryMap.end()) 329 return false; 330 StorageMailEntry& storageEntry = (*it).second; 331 BPath filePath = fMailboxPath; 332 filePath.Append(storageEntry.fileName); 333 BNode node(filePath.Path()); 334 335 if (node.WriteAttr("MAIL:size", B_INT32_TYPE, 0, &size, sizeof(int32)) < 0) 336 return B_ERROR; 337 return B_OK; 338} 339 340 341StorageMailEntry* 342IMAPStorage::GetEntryForRef(const node_ref& ref) 343{ 344 for (MailEntryMap::iterator it = fMailEntryMap.begin(); 345 it != fMailEntryMap.end(); it++) { 346 StorageMailEntry& mailEntry = (*it).second; 347 if (mailEntry.nodeRef == ref) 348 return &mailEntry; 349 } 350 return NULL; 351} 352 353 354bool 355IMAPStorage::BodyFetched(int32 uid) 356{ 357 MailEntryMap::iterator it = fMailEntryMap.find(uid); 358 if (it == fMailEntryMap.end()) 359 return false; 360 StorageMailEntry& storageEntry = (*it).second; 361 BPath filePath = fMailboxPath; 362 filePath.Append(storageEntry.fileName); 363 BNode node(filePath.Path()); 364 365 char buffer[B_MIME_TYPE_LENGTH]; 366 BNodeInfo info(&node); 367 info.GetType(buffer); 368 if (strcmp(buffer, B_MAIL_TYPE) == 0) 369 return true; 370 return false; 371} 372 373 374int32 375IMAPStorage::RefToUID(const entry_ref& ref) 376{ 377 for (MailEntryMap::iterator it = fMailEntryMap.begin(); 378 it != fMailEntryMap.end(); it++) { 379 StorageMailEntry& mailEntry = (*it).second; 380 if (mailEntry.fileName == ref.name) 381 return mailEntry.uid; 382 } 383 384 // not found try to fix internal name 385 BDirectory dir(fMailboxPath.Path()); 386 if (!dir.Contains(ref.name)) 387 return -1; 388 389 BNode node(&ref); 390 int32 uid; 391 if (ReadUniqueID(node, uid) != B_OK) 392 return -1; 393 394 MailEntryMap::iterator it = fMailEntryMap.find(uid); 395 if (it == fMailEntryMap.end()) 396 return -1; 397 it->second.fileName = ref.name; 398 399 return uid; 400} 401 402 403bool 404IMAPStorage::UIDToRef(int32 uid, entry_ref& ref) 405{ 406 MailEntryMap::iterator it = fMailEntryMap.find(uid); 407 if (it == fMailEntryMap.end()) 408 return false; 409 410 StorageMailEntry& mailEntry = (*it).second; 411 BPath filePath = fMailboxPath; 412 filePath.Append(mailEntry.fileName); 413 414 BEntry entry(filePath.Path()); 415 if (entry.GetRef(&ref) == B_OK) 416 return true; 417 return false; 418} 419 420 421status_t 422IMAPStorage::_ReadFilesThreadFunction(void* data) 423{ 424 IMAPStorage* storage = (IMAPStorage*)data; 425 return storage->_ReadFiles(); 426} 427 428 429status_t 430IMAPStorage::_ReadFiles() 431{ 432 fMailEntryMap.clear(); 433 434 BDirectory directory(fMailboxPath.Path()); 435 436 entry_ref ref; 437 while (directory.GetNextRef(&ref) != B_ENTRY_NOT_FOUND) { 438 BNode node(&ref); 439 if (node.InitCheck() != B_OK || !node.IsFile()) 440 continue; 441 442 char buffer[B_MIME_TYPE_LENGTH]; 443 BNodeInfo info(&node); 444 info.GetType(buffer); 445 446 // maybe interrupted downloads ignore them 447 if (strcmp(buffer, "text/x-partial-email") != 0 448 && strcmp(buffer, B_MAIL_TYPE) != 0) 449 continue; 450 451 StorageMailEntry entry; 452 entry.fileName = ref.name; 453 if (ReadUniqueID(node, entry.uid) != B_OK) { 454 TRACE("IMAPStorage::_ReadFilesThread() failed to read uid %s\n", 455 ref.name); 456 continue; 457 } 458 459 if (node.ReadAttr("MAIL:server_flags", B_INT32_TYPE, 0, &entry.flags, 460 sizeof(int32)) != sizeof(int32)) 461 continue; 462 463 node.GetNodeRef(&entry.nodeRef); 464 465 fMailEntryMap[entry.uid] = entry; 466 } 467 468 release_sem(fLoadDatabaseLock); 469 return B_OK; 470} 471 472 473status_t 474IMAPStorage::_WriteFlags(int32 flags, BNode& node) 475{ 476 if ((flags & kSeen) != 0) 477 write_read_attr(node, B_READ); 478 else 479 write_read_attr(node, B_UNREAD); 480 481 ssize_t writen = node.WriteAttr("MAIL:server_flags", B_INT32_TYPE, 0, 482 &flags, sizeof(int32)); 483 if (writen != sizeof(int32)) 484 return writen; 485 486 return B_OK; 487} 488 489 490status_t 491IMAPStorage::ReadUniqueID(BNode& node, int32& uid) 492{ 493 const uint32 kMaxUniqueLength = 32; 494 char uidString[kMaxUniqueLength]; 495 memset(uidString, 0, kMaxUniqueLength); 496 if (node.ReadAttr("MAIL:unique_id", B_STRING_TYPE, 0, uidString, 497 kMaxUniqueLength) < 0) 498 return B_ERROR; 499 uid = atoi(uidString); 500 return B_OK; 501} 502 503 504status_t 505IMAPStorage::_WriteUniqueID(BNode& node, int32 uid) 506{ 507 BString uidString; 508 uidString << uid; 509 ssize_t written = node.WriteAttr("MAIL:unique_id", B_STRING_TYPE, 0, 510 uidString.String(), uidString.Length()); 511 if (written < 0) 512 return written; 513 return B_OK; 514} 515