/** This is synchronized to ensure that we process the queue serially. */ private synchronized void complete(String message) { // This is a weird problem with writing stuff while idling. Need to investigate it more, but // for now just ignore it. if (MESSAGE_COULDNT_BE_FETCHED_REGEX.matcher(message).matches()) { log.warn( "Some messages in the batch could not be fetched for {}\n" + "---cmd---\n{}\n---wire---\n{}\n---end---\n", new Object[] {config.getUsername(), getCommandTrace(), getWireTrace()}); errorStack.push(new Error(completions.peek(), message, wireTrace.list())); throw new RuntimeException( "Some messages in the batch could not be fetched for user " + config.getUsername()); } CommandCompletion completion = completions.peek(); if (completion == null) { if ("+ idling".equalsIgnoreCase(message)) { synchronized (idleMutex) { idler.idleStart(); log.trace("IDLE entered."); idleAcknowledged.set(true); } } else { log.error("Could not find the completion for message {} (Was it ever issued?)", message); errorStack.push(new Error(null, "No completion found!", wireTrace.list())); } return; } if (completion.complete(message)) { completions.poll(); } }
private void disconnectAbnormally(String message) { try { halt(); // Disconnect abnormally. The user code should reconnect using the mail client. errorStack.push(new Error(completions.poll(), message, wireTrace.list())); idler.disconnect(); } finally { disconnected(); } }
private void processMessage(String message) throws Exception { Boolean toStdOut = logAllMessagesForUsers.get(config.getUsername()); if (toStdOut != null) { if (toStdOut) System.out.println("IMAPrcv[" + config.getUsername() + "]: " + message); else log.info("IMAPrcv[{}]: {}", config.getUsername(), message); } wireTrace.add(message); log.trace(message); if (SYSTEM_ERROR_REGEX.matcher(message).matches() || ". NO [ALERT] Account exceeded command or bandwidth limits. (Failure)" .equalsIgnoreCase(message.trim())) { log.warn( "{} disconnected by IMAP Server due to system error: {}", config.getUsername(), message); disconnectAbnormally(message); return; } try { if (halt) { log.error( "Mail client for {} is halted but continues to receive messages, ignoring!", config.getUsername()); return; } if (loginSuccess.getCount() > 0) { if (message.startsWith(CAPABILITY_PREFIX)) { this.capabilities = Arrays.asList(message.substring(CAPABILITY_PREFIX.length() + 1).split("[ ]+")); return; } else if (AUTH_SUCCESS_REGEX.matcher(message).matches()) { log.info("Authentication success for user {}", config.getUsername()); loginSuccess.countDown(); } else { Matcher matcher = COMMAND_FAILED_REGEX.matcher(message); if (matcher.find()) { // WARNING: DO NOT COUNTDOWN THE LOGIN LATCH ON FAILURE!!! log.warn("Authentication failed for {} due to: {}", config.getUsername(), message); errorStack.push( new Error( null /* logins have no completion */, extractError(matcher), wireTrace.list())); disconnectAbnormally(message); } } return; } // Copy to local var as the value can change underneath us. FolderObserver observer = this.observer; if (idleRequested.get() || idleAcknowledged.get()) { synchronized (idleMutex) { if (IDLE_ENDED_REGEX.matcher(message).matches()) { idleRequested.compareAndSet(true, false); idleAcknowledged.set(false); // Now fire the events. PushedData data = pushedData; pushedData = null; idler.idleEnd(); observer.changed( data.pushAdds.isEmpty() ? null : data.pushAdds, data.pushRemoves.isEmpty() ? null : data.pushRemoves); return; } // Queue up any push notifications to publish to the client in a second. Matcher existsMatcher = IDLE_EXISTS_REGEX.matcher(message); boolean matched = false; if (existsMatcher.matches()) { int number = Integer.parseInt(existsMatcher.group(1)); pushedData.pushAdds.add(number); pushedData.pushRemoves.remove(number); matched = true; } else { Matcher expungeMatcher = IDLE_EXPUNGE_REGEX.matcher(message); if (expungeMatcher.matches()) { int number = Integer.parseInt(expungeMatcher.group(1)); pushedData.pushRemoves.add(number); pushedData.pushAdds.remove(number); matched = true; } } // Stop idling, when we get the idle ended message (next cycle) we can publish what's been // gathered. if (matched) { if (!pushedData.idleExitSent) { idler.done(); pushedData.idleExitSent = true; } return; } } } complete(message); } catch (Exception ex) { CommandCompletion completion = completions.poll(); if (completion != null) completion.error(message, ex); else { log.error( "Strange exception during mail processing (no completions available!): {}", message, ex); errorStack.push(new Error(null, "No completions available!", wireTrace.list())); } throw ex; } }