/**
   * 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();
 }