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 fFolders.insert(std::make_pair(mailbox, 123 _CreateFolder(mailbox, separator))); 124 } 125 126 // Distribute the mailboxes evenly to the workers 127 FolderMap::iterator iterator = fFolders.begin(); 128 int32 index = 0; 129 for (; iterator != fFolders.end(); iterator++) { 130 IMAPConnectionWorker* worker = fWorkers.ItemAt(index); 131 IMAPFolder* folder = iterator->second; 132 worker->AddMailbox(folder); 133 fWorkerMap.insert(std::make_pair(folder, worker)); 134 135 index = (index + 1) % fWorkers.CountItems(); 136 } 137 138 // Start waiting workers 139 return _EnqueueCheckMailboxes(); 140 } 141 142 143 void 144 IMAPProtocol::WorkerQuit(IMAPConnectionWorker* worker) 145 { 146 MutexLocker locker(fWorkerLock); 147 fWorkers.RemoveItem(worker); 148 149 WorkerMap::iterator iterator = fWorkerMap.begin(); 150 while (iterator != fWorkerMap.end()) { 151 WorkerMap::iterator removed = iterator++; 152 if (removed->second == worker) 153 fWorkerMap.erase(removed); 154 } 155 } 156 157 158 void 159 IMAPProtocol::MessageStored(IMAPFolder& folder, entry_ref& ref, 160 BFile& stream, uint32 fetchFlags, BMessage& attributes) 161 { 162 if ((fetchFlags & (IMAP::kFetchHeader | IMAP::kFetchBody)) 163 == (IMAP::kFetchHeader | IMAP::kFetchBody)) { 164 ProcessMessageFetched(ref, stream, attributes); 165 } else if ((fetchFlags & IMAP::kFetchHeader) != 0) { 166 ProcessHeaderFetched(ref, stream, attributes); 167 } else if ((fetchFlags & IMAP::kFetchBody) != 0) { 168 NotifyBodyFetched(ref, stream, attributes); 169 } 170 } 171 172 173 status_t 174 IMAPProtocol::UpdateMessageFlags(IMAPFolder& folder, uint32 uid, uint32 flags) 175 { 176 MutexLocker locker(fWorkerLock); 177 178 WorkerMap::const_iterator found = fWorkerMap.find(&folder); 179 if (found == fWorkerMap.end()) 180 return B_ERROR; 181 182 IMAPConnectionWorker* worker = found->second; 183 return worker->EnqueueUpdateFlags(folder, uid, flags); 184 } 185 186 187 status_t 188 IMAPProtocol::SyncMessages() 189 { 190 puts("IMAP: sync"); 191 192 MutexLocker locker(fWorkerLock); 193 if (fWorkers.IsEmpty()) { 194 // Create main (and possibly initial) connection worker 195 IMAPConnectionWorker* worker = new IMAPConnectionWorker(*this, 196 fSettings, true); 197 if (!fWorkers.AddItem(worker)) { 198 delete worker; 199 return B_NO_MEMORY; 200 } 201 202 worker->EnqueueCheckSubscribedFolders(); 203 return worker->Run(); 204 } 205 fWorkers.ItemAt(0)->EnqueueCheckSubscribedFolders(); 206 return B_OK; 207 } 208 209 210 status_t 211 IMAPProtocol::MarkMessageAsRead(const entry_ref& ref, read_flags flags) 212 { 213 printf("IMAP: mark as read %s: %d\n", ref.name, flags); 214 return B_ERROR; 215 } 216 217 218 void 219 IMAPProtocol::MessageReceived(BMessage* message) 220 { 221 switch (message->what) { 222 case B_READY_TO_RUN: 223 ReadyToRun(); 224 break; 225 226 default: 227 BInboundMailProtocol::MessageReceived(message); 228 break; 229 } 230 } 231 232 233 status_t 234 IMAPProtocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo) 235 { 236 printf("IMAP: fetch body %s\n", ref.name); 237 MutexLocker locker(fWorkerLock); 238 239 IMAPFolder* folder = _FolderFor(ref.directory); 240 if (folder == NULL) 241 return B_ENTRY_NOT_FOUND; 242 243 uint32 uid; 244 status_t status = folder->GetMessageUID(ref, uid); 245 if (status != B_OK) 246 return status; 247 248 WorkerMap::const_iterator found = fWorkerMap.find(folder); 249 if (found == fWorkerMap.end()) 250 return B_ERROR; 251 252 IMAPConnectionWorker* worker = found->second; 253 return worker->EnqueueFetchBody(*folder, uid, replyTo); 254 } 255 256 257 void 258 IMAPProtocol::ReadyToRun() 259 { 260 puts("IMAP: ready to run!"); 261 if (fSettings.IdleMode()) 262 SyncMessages(); 263 } 264 265 266 IMAPFolder* 267 IMAPProtocol::_CreateFolder(const BString& mailbox, const BString& separator) 268 { 269 BString name = MailboxToFolderName(mailbox, separator); 270 271 BPath path(fSettings.Destination()); 272 if (path.Append(name.String()) != B_OK) { 273 fprintf(stderr, "Could not append path: %s\n", name.String()); 274 return NULL; 275 } 276 277 status_t status = create_directory(path.Path(), 0755); 278 if (status != B_OK) { 279 fprintf(stderr, "Could not create path %s: %s\n", path.Path(), 280 strerror(status)); 281 return NULL; 282 } 283 284 CopyMailFolderAttributes(path.Path()); 285 286 entry_ref ref; 287 status = get_ref_for_path(path.Path(), &ref); 288 if (status != B_OK) { 289 fprintf(stderr, "Could not get ref for %s: %s\n", path.Path(), 290 strerror(status)); 291 return NULL; 292 } 293 294 IMAPFolder* folder = new IMAPFolder(*this, mailbox, ref); 295 status = folder->Init(); 296 if (status != B_OK) { 297 fprintf(stderr, "Initializing folder %s failed: %s\n", path.Path(), 298 strerror(status)); 299 delete folder; 300 return NULL; 301 } 302 303 fFolderNodeMap.insert(std::make_pair(folder->NodeID(), folder)); 304 return folder; 305 } 306 307 308 IMAPFolder* 309 IMAPProtocol::_FolderFor(ino_t directory) 310 { 311 FolderNodeMap::const_iterator found = fFolderNodeMap.find(directory); 312 if (found != fFolderNodeMap.end()) 313 return found->second; 314 315 return NULL; 316 } 317 318 319 status_t 320 IMAPProtocol::_EnqueueCheckMailboxes() 321 { 322 for (int32 i = 0; i < fWorkers.CountItems(); i++) { 323 fWorkers.ItemAt(i)->EnqueueCheckMailboxes(); 324 } 325 326 return B_OK; 327 } 328 329 330 // #pragma mark - 331 332 333 extern "C" BInboundMailProtocol* 334 instantiate_inbound_protocol(const BMailAccountSettings& settings) 335 { 336 return new IMAPProtocol(settings); 337 } 338