1 /* 2 * Copyright 2013-2016, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "IMAPProtocol.h" 8 9 #include <Directory.h> 10 #include <Messenger.h> 11 12 #include "IMAPConnectionWorker.h" 13 #include "IMAPFolder.h" 14 #include "Utilities.h" 15 16 #include <mail_util.h> 17 18 19 IMAPProtocol::IMAPProtocol(const BMailAccountSettings& settings) 20 : 21 BInboundMailProtocol("IMAP", settings), 22 fSettings(settings.Name(), settings.InboundSettings()), 23 fWorkers(5, false) 24 { 25 BPath destination = fSettings.Destination(); 26 27 status_t status = create_directory(destination.Path(), 0755); 28 if (status != B_OK) { 29 fprintf(stderr, "IMAP: Could not create destination directory %s: %s\n", 30 destination.Path(), strerror(status)); 31 } 32 33 mutex_init(&fWorkerLock, "imap worker lock"); 34 35 PostMessage(B_READY_TO_RUN); 36 } 37 38 39 IMAPProtocol::~IMAPProtocol() 40 { 41 MutexLocker locker(fWorkerLock); 42 std::vector<thread_id> threads; 43 for (int32 i = 0; i < fWorkers.CountItems(); i++) { 44 threads.push_back(fWorkers.ItemAt(i)->Thread()); 45 fWorkers.ItemAt(i)->Quit(); 46 } 47 locker.Unlock(); 48 49 for (uint32 i = 0; i < threads.size(); i++) 50 wait_for_thread(threads[i], NULL); 51 52 FolderMap::iterator iterator = fFolders.begin(); 53 for (; iterator != fFolders.end(); iterator++) { 54 IMAPFolder* folder = iterator->second; 55 delete folder; // to stop thread 56 } 57 } 58 59 status_t 60 IMAPProtocol::CheckSubscribedFolders(IMAP::Protocol& protocol, bool idle) 61 { 62 // Get list of subscribed folders 63 64 BStringList newFolders; 65 BString separator; 66 status_t status = protocol.GetSubscribedFolders(newFolders, separator); 67 if (status != B_OK) 68 return status; 69 70 // Determine how many new mailboxes we have 71 72 for (int32 i = 0; i < newFolders.CountStrings();) { 73 if (fFolders.find(newFolders.StringAt(i)) != fFolders.end()) 74 newFolders.Remove(i); 75 else 76 i++; 77 } 78 79 int32 totalMailboxes = fFolders.size() + newFolders.CountStrings(); 80 int32 workersWanted = 1; 81 if (idle) 82 workersWanted = std::min(fSettings.MaxConnections(), totalMailboxes); 83 84 MutexLocker locker(fWorkerLock); 85 86 if (newFolders.IsEmpty() && fWorkers.CountItems() == workersWanted) { 87 // Nothing to do - we've already distributed everything 88 return _EnqueueCheckMailboxes(); 89 } 90 91 // Remove mailboxes from workers 92 for (int32 i = 0; i < fWorkers.CountItems(); i++) { 93 fWorkers.ItemAt(i)->RemoveAllMailboxes(); 94 } 95 fWorkerMap.clear(); 96 97 // Create/remove connection workers as allowed and needed 98 while (fWorkers.CountItems() < workersWanted) { 99 IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this, 100 fSettings); 101 if (!fWorkers.AddItem(worker)) { 102 delete worker; 103 break; 104 } 105 106 status = worker->Run(); 107 if (status != B_OK) { 108 fWorkers.RemoveItem(worker); 109 delete worker; 110 } 111 } 112 113 while (fWorkers.CountItems() > workersWanted) { 114 IMAPConnectionWorker* worker 115 = fWorkers.RemoveItemAt(fWorkers.CountItems() - 1); 116 worker->Quit(); 117 } 118 119 // Update known mailboxes 120 for (int32 i = 0; i < newFolders.CountStrings(); i++) { 121 const BString& mailbox = newFolders.StringAt(i); 122 IMAPFolder* folder = _CreateFolder(mailbox, separator); 123 if (folder != NULL) 124 fFolders.insert(std::make_pair(mailbox, folder)); 125 } 126 127 // Distribute the mailboxes evenly to the workers 128 FolderMap::iterator iterator = fFolders.begin(); 129 int32 index = 0; 130 for (; iterator != fFolders.end(); iterator++) { 131 IMAPConnectionWorker* worker = fWorkers.ItemAt(index); 132 IMAPFolder* folder = iterator->second; 133 worker->AddMailbox(folder); 134 fWorkerMap.insert(std::make_pair(folder, worker)); 135 136 index = (index + 1) % fWorkers.CountItems(); 137 } 138 139 // Start waiting workers 140 return _EnqueueCheckMailboxes(); 141 } 142 143 144 void 145 IMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker) 146 { 147 MutexLocker locker(fWorkerLock); 148 fWorkers.RemoveItem(worker); 149 150 WorkerMap::iterator iterator = fWorkerMap.begin(); 151 while (iterator != fWorkerMap.end()) { 152 WorkerMap::iterator removed = iterator++; 153 if (removed->second == worker) 154 fWorkerMap.erase(removed); 155 } 156 } 157 158 159 void 160 IMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref, 161 BFile& stream, uint32 fetchFlags, BMessage& attributes) 162 { 163 if ((fetchFlags & (IMAP::kFetchHeader | IMAP::kFetchBody)) 164 == (IMAP::kFetchHeader | IMAP::kFetchBody)) { 165 ProcessMessageFetched(ref, stream, attributes); 166 } else if ((fetchFlags & IMAP::kFetchHeader) != 0) { 167 ProcessHeaderFetched(ref, stream, attributes); 168 } else if ((fetchFlags & IMAP::kFetchBody) != 0) { 169 NotifyBodyFetched(ref, stream, attributes); 170 } 171 } 172 173 174 status_t 175 IMAPProtocol::UpdateMessageFlags(IMAPFolder& folder, uint32 uid, uint32 flags) 176 { 177 MutexLocker locker(fWorkerLock); 178 179 WorkerMap::const_iterator found = fWorkerMap.find(&folder); 180 if (found == fWorkerMap.end()) 181 return B_ERROR; 182 183 IMAPConnectionWorker* worker = found->second; 184 return worker->EnqueueUpdateFlags(folder, uid, flags); 185 } 186 187 188 status_t 189 IMAPProtocol::SyncMessages() 190 { 191 puts("IMAP: sync"); 192 193 MutexLocker locker(fWorkerLock); 194 if (fWorkers.IsEmpty()) { 195 // Create main (and possibly initial) connection worker 196 IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this, 197 fSettings, true); 198 if (!fWorkers.AddItem(worker)) { 199 delete worker; 200 return B_NO_MEMORY; 201 } 202 203 worker->EnqueueCheckSubscribedFolders(); 204 return worker->Run(); 205 } 206 fWorkers.ItemAt(0)->EnqueueCheckSubscribedFolders(); 207 return B_OK; 208 } 209 210 211 status_t 212 IMAPProtocol::MarkMessageAsRead(const entry_ref& ref, read_flags flags) 213 { 214 printf("IMAP: mark as read %s: %d\n", ref.name, flags); 215 return B_ERROR; 216 } 217 218 219 void 220 IMAPProtocol::MessageReceived(BMessage* message) 221 { 222 switch (message->what) { 223 case B_READY_TO_RUN: 224 ReadyToRun(); 225 break; 226 227 default: 228 BInboundMailProtocol::MessageReceived(message); 229 break; 230 } 231 } 232 233 234 status_t 235 IMAPProtocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo) 236 { 237 printf("IMAP: fetch body %s\n", ref.name); 238 MutexLocker locker(fWorkerLock); 239 240 IMAPFolder* folder = _FolderFor(ref.directory); 241 if (folder == NULL) 242 return B_ENTRY_NOT_FOUND; 243 244 uint32 uid; 245 status_t status = folder->GetMessageUID(ref, uid); 246 if (status != B_OK) 247 return status; 248 249 WorkerMap::const_iterator found = fWorkerMap.find(folder); 250 if (found == fWorkerMap.end()) 251 return B_ERROR; 252 253 IMAPConnectionWorker* worker = found->second; 254 return worker->EnqueueFetchBody(*folder, uid, replyTo); 255 } 256 257 258 void 259 IMAPProtocol::ReadyToRun() 260 { 261 puts("IMAP: ready to run!"); 262 if (fSettings.IdleMode()) 263 SyncMessages(); 264 } 265 266 267 IMAPFolder* 268 IMAPProtocol::_CreateFolder(const BString& mailbox, const BString& separator) 269 { 270 BString name = MailboxToFolderName(mailbox, separator); 271 272 BPath path(fSettings.Destination()); 273 if (path.Append(name.String()) != B_OK) { 274 fprintf(stderr, "Could not append path: %s\n", name.String()); 275 return NULL; 276 } 277 278 status_t status; 279 BNode node(path.Path()); 280 281 if (node.InitCheck() == B_OK) { 282 if (!node.IsDirectory()) { 283 fprintf(stderr, "%s already exists and is not a directory\n", 284 path.Path()); 285 return NULL; 286 } 287 } else { 288 status = create_directory(path.Path(), 0755); 289 if (status != B_OK) { 290 fprintf(stderr, "Could not create path %s: %s\n", path.Path(), 291 strerror(status)); 292 return NULL; 293 } 294 CopyMailFolderAttributes(path.Path()); 295 } 296 297 entry_ref ref; 298 status = get_ref_for_path(path.Path(), &ref); 299 if (status != B_OK) { 300 fprintf(stderr, "Could not get ref for %s: %s\n", path.Path(), 301 strerror(status)); 302 return NULL; 303 } 304 305 IMAPFolder* folder = new IMAPFolder(*this, mailbox, ref); 306 status = folder->Init(); 307 if (status != B_OK) { 308 fprintf(stderr, "Initializing folder %s failed: %s\n", path.Path(), 309 strerror(status)); 310 delete folder; 311 return NULL; 312 } 313 314 fFolderNodeMap.insert(std::make_pair(folder->NodeID(), folder)); 315 return folder; 316 } 317 318 319 IMAPFolder* 320 IMAPProtocol::_FolderFor(ino_t directory) 321 { 322 FolderNodeMap::const_iterator found = fFolderNodeMap.find(directory); 323 if (found != fFolderNodeMap.end()) 324 return found->second; 325 326 return NULL; 327 } 328 329 330 status_t 331 IMAPProtocol::_EnqueueCheckMailboxes() 332 { 333 for (int32 i = 0; i < fWorkers.CountItems(); i++) { 334 fWorkers.ItemAt(i)->EnqueueCheckMailboxes(); 335 } 336 337 return B_OK; 338 } 339 340 341 // #pragma mark - 342 343 344 extern "C" BInboundMailProtocol* 345 instantiate_inbound_protocol(const BMailAccountSettings& settings) 346 { 347 return new IMAPProtocol(settings); 348 } 349