/** * Logs out of the current IMAP session and releases all resources, including executor services. */ @Override public synchronized void disconnect() { try { // If there is an error with the handler, dont bother logging out. if (!mailClientHandler.isHalted()) { if (mailClientHandler.idleRequested.get()) { log.warn("Disconnect called while IDLE, leaving idle and logging out."); done(); } // Log out of the IMAP Server. channel.write(". logout\n"); } currentFolder = null; } catch (Exception e) { // swallow any exceptions. } finally { // Shut down all channels and exit (leave threadpools as is--for reconnects). // The Netty channel close listener will fire a disconnect event to our client, // automatically. See connect() for details. try { channel.close().awaitUninterruptibly(config.getTimeout(), TimeUnit.MILLISECONDS); } catch (Exception e) { // swallow any exceptions. } finally { mailClientHandler.idleAcknowledged.set(false); mailClientHandler.disconnected(); if (disconnectListener != null) disconnectListener.disconnected(); } } }
private boolean login() { try { channel.write(". CAPABILITY\r\n"); if (config.getPassword() != null) channel.write(". login " + config.getUsername() + " " + config.getPassword() + "\r\n"); else { // Use xoauth login instead. OAuthConfig oauth = config.getOAuthConfig(); Preconditions.checkArgument( oauth != null, "Must specify a valid oauth config if not using password auth"); //noinspection ConstantConditions String oauthString = new XoauthSasl(config.getUsername(), oauth.clientId, oauth.clientSecret) .build(Protocol.IMAP, oauth.accessToken, oauth.tokenSecret); channel.write(". AUTHENTICATE XOAUTH " + oauthString + "\r\n"); } return mailClientHandler.awaitLogin(); } catch (Exception e) { // Capture the wire trace and log it for some extra context here. StringBuilder trace = new StringBuilder(); for (String line : mailClientHandler.getWireTrace()) { trace.append(line).append("\n"); } log.warn( "Could not oauth or login for {}. Partial trace follows:\n" + "----begin wiretrace----\n{}\n----end wiretrace----", new Object[] {config.getUsername(), trace.toString(), e}); } return false; }
@Override public synchronized void watch(Folder folder, FolderObserver observer) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); checkCurrentFolder(folder); Preconditions.checkState( mailClientHandler.idleRequested.compareAndSet(false, true), "Already idling..."); // This MUST happen in the following order, otherwise send() may trigger a new mail event // before we've registered the folder observer. mailClientHandler.observe(observer); channel.write(sequence.incrementAndGet() + " idle\r\n"); }
<D> ChannelFuture send(Command command, String args, SettableFuture<D> valueFuture) { Long seq = sequence.incrementAndGet(); String commandString = seq + " " + command.toString() + (null == args ? "" : " " + args) + "\r\n"; // Log the command but clip the \r\n log.debug("Sending {} to server...", commandString.substring(0, commandString.length() - 2)); Boolean toStdOut = logAllMessagesForUsers.get(config.getUsername()); if (toStdOut != null) { if (toStdOut) System.out.println( "IMAPsnd[" + config.getUsername() + "]: " + commandString.substring(0, commandString.length() - 2)); else log.info( "IMAPsnd[{}]: {}", config.getUsername(), commandString.substring(0, commandString.length() - 2)); } // Enqueue command. mailClientHandler.enqueue(new CommandCompletion(command, seq, valueFuture, commandString)); return channel.write(commandString); }
@Override public ListenableFuture<List<MessageStatus>> listUidThin( Folder folder, List<Sequence> sequences) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); SettableFuture<List<MessageStatus>> valueFuture = SettableFuture.create(); // -ve end range means get everything (*). String extensions = config.useGmailExtensions() ? " X-GM-MSGID X-GM-THRID X-GM-LABELS UID" : ""; StringBuilder argsBuilder = new StringBuilder(); // Emit ranges. for (int i = 0, sequencesSize = sequences.size(); i < sequencesSize; i++) { Sequence seq = sequences.get(i); argsBuilder.append(toUpperBound(seq.start)); if (seq.end != 0) argsBuilder.append(':').append(toUpperBound(seq.end)); if (i < sequencesSize - 1) argsBuilder.append(','); } argsBuilder.append(" (FLAGS" + extensions + ")"); send(Command.FETCH_THIN_HEADERS_UID, argsBuilder.toString(), valueFuture); return valueFuture; }
@Override public ListenableFuture<List<MessageStatus>> list(Folder folder, int start, int end) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); checkRange(start, end); Preconditions.checkArgument( start > 0, "Start must be greater than zero (IMAP uses 1-based " + "indexing)"); SettableFuture<List<MessageStatus>> valueFuture = SettableFuture.create(); // -ve end range means get everything (*). String extensions = config.useGmailExtensions() ? " X-GM-MSGID X-GM-THRID X-GM-LABELS UID" : ""; String args = start + ":" + toUpperBound(end) + " (RFC822.SIZE INTERNALDATE FLAGS ENVELOPE" + extensions + ")"; send(Command.FETCH_HEADERS, args, valueFuture); return valueFuture; }
@Override // @Stateless public ListenableFuture<FolderStatus> statusOf(String folder) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); SettableFuture<FolderStatus> valueFuture = SettableFuture.create(); String args = '"' + folder + "\" (UIDNEXT RECENT MESSAGES UNSEEN)"; send(Command.FOLDER_STATUS, args, valueFuture); return valueFuture; }
@Override public ListenableFuture<Boolean> copy(Folder folder, int imapUid, String toFolder) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); SettableFuture<Boolean> valueFuture = SettableFuture.create(); String args = imapUid + " " + toFolder; send(Command.COPY, args, valueFuture); return valueFuture; }
private void reset() { Preconditions.checkState( !isConnected(), "Cannot reset while mail client is still connected (call disconnect() first)."); // Just to be on the safe side. if (mailClientHandler != null) { mailClientHandler.halt(); mailClientHandler.disconnected(); } this.mailClientHandler = new MailClientHandler(this, config); MailClientPipelineFactory pipelineFactory = new MailClientPipelineFactory(mailClientHandler, config); this.bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(bossPool, workerPool)); this.bootstrap.setPipelineFactory(pipelineFactory); // Reset state (helps if this is a reconnect). this.currentFolder = null; this.sequence.set(0L); mailClientHandler.idleRequested.set(false); }
@Override public ListenableFuture<Set<Flag>> addOrRemoveFlags( Folder folder, int imapUid, Set<Flag> flags, boolean add) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); SettableFuture<Set<Flag>> valueFuture = SettableFuture.create(); String args = imapUid + " " + (add ? "+" : "-") + Flag.toImap(flags); send(Command.STORE_FLAGS, args, valueFuture); return valueFuture; }
@Override // @Stateless public ListenableFuture<List<String>> listFolders() { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); SettableFuture<List<String>> valueFuture = SettableFuture.create(); send(Command.LIST_FOLDERS, "\"\" \"*\"", valueFuture); return valueFuture; }
@Override public ListenableFuture<Message> fetchUid(Folder folder, int uid) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); Preconditions.checkArgument(uid > 0, "UID must be greater than zero"); SettableFuture<Message> valueFuture = SettableFuture.create(); String args = uid + " (uid body[])"; send(Command.FETCH_BODY_UID, args, valueFuture); return valueFuture; }
@Override public ListenableFuture<List<Message>> fetch(Folder folder, int start, int end) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); checkRange(start, end); Preconditions.checkArgument( start > 0, "Start must be greater than zero (IMAP uses 1-based " + "indexing)"); SettableFuture<List<Message>> valueFuture = SettableFuture.create(); String args = start + ":" + toUpperBound(end) + " (uid body[])"; send(Command.FETCH_BODY, args, valueFuture); return valueFuture; }
@Override public ListenableFuture<Set<String>> setGmailLabels( Folder folder, int imapUid, Set<String> labels) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); SettableFuture<Set<String>> valueFuture = SettableFuture.create(); StringBuilder args = new StringBuilder(); args.append(imapUid); args.append(" X-GM-LABELS ("); Iterator<String> it = labels.iterator(); while (it.hasNext()) { args.append(it.next()); if (it.hasNext()) args.append(" "); else args.append(")"); } send(Command.STORE_LABELS, args.toString(), valueFuture); return valueFuture; }
@Override public ListenableFuture<List<Integer>> searchUid(Folder folder, String query, Date since) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); SettableFuture<List<Integer>> valueFuture = SettableFuture.create(); StringBuilder argsBuilder = new StringBuilder(); if (config.useGmailExtensions()) { argsBuilder.append("X-GM-RAW \"").append(query).append('"'); } else argsBuilder.append(query); if (since != null) argsBuilder.append(" since ").append(SINCE_FORMAT.format(since)); send(Command.SEARCH_RAW_UID, argsBuilder.toString(), valueFuture); return valueFuture; }
@Override // @Stateless public ListenableFuture<Folder> open(String folder, boolean readWrite) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); final SettableFuture<Folder> valueFuture = SettableFuture.create(); final SettableFuture<Folder> externalFuture = SettableFuture.create(); valueFuture.addListener( new Runnable() { @Override public void run() { try { // We do this to enforce a happens-before ordering between the time a folder is // saved to currentFolder and a listener registered by the user may fire in a parallel // executor service. currentFolder = valueFuture.get(); externalFuture.set(currentFolder); } catch (InterruptedException e) { log.error("Interrupted while attempting to open a folder", e); } catch (ExecutionException e) { log.error("Execution exception while attempting to open a folder", e); externalFuture.setException(e); } } }, workerPool); String args = '"' + folder + "\""; send(readWrite ? Command.FOLDER_OPEN : Command.FOLDER_EXAMINE, args, valueFuture); return externalFuture; }
@Override public ListenableFuture<List<Integer>> exists(Folder folder, Collection<Integer> uids) { Preconditions.checkState( mailClientHandler.isLoggedIn(), "Can't execute command because client is not logged in"); Preconditions.checkState( !mailClientHandler.idleRequested.get(), "Can't execute command while idling (are you watching a folder?)"); checkCurrentFolder(folder); SettableFuture<List<Integer>> valueFuture = SettableFuture.create(); StringBuilder argsBuilder = new StringBuilder("uid "); Iterator<Integer> iterator = uids.iterator(); for (int i = 0, uidsSize = uids.size(); i < uidsSize; i++) { argsBuilder.append(iterator.next()); if (i < uidsSize - 1) argsBuilder.append(","); } send(Command.SEARCH_UID_ONLY, argsBuilder.toString(), valueFuture); return valueFuture; }
public boolean isConnected() { return channel != null && channel.isConnected() && channel.isOpen() && mailClientHandler.isLoggedIn(); }
@Override public List<String> getCommandTrace() { return mailClientHandler.getCommandTrace(); }
@Override public WireError lastError() { return mailClientHandler.lastError(); }
@Override public List<String> capabilities() { return mailClientHandler.getCapabilities(); }