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