/**
 * Copyright (C) 2008 Happy Fish / YuQing
 * <p>
 * FastDFS Java Client may be copied only under the terms of the GNU Lesser
 * General Public License (LGPL).
 * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
 */

package com.kmall.common.fileserver.fastdfs;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;

/**
 * Tracker client
 *
 * @author Happy Fish / YuQing
 * @version Version 1.19
 */
public class TrackerClient {
  protected TrackerGroup tracker_group;
  protected byte errno;

  /**
   * constructor with global tracker group
   */
  public TrackerClient() {
    this.tracker_group = ClientGlobal.g_tracker_group;
  }

  /**
   * constructor with specified tracker group
   *
   * @param tracker_group the tracker group object
   */
  public TrackerClient(TrackerGroup tracker_group) {
    this.tracker_group = tracker_group;
  }

  /**
   * get the error code of last call
   *
   * @return the error code of last call
   */
  public byte getErrorCode() {
    return this.errno;
  }

  /**
   * get a connection to tracker server
   *
   * @return tracker server Socket object, return null if fail
   */
  public TrackerServer getConnection() throws IOException {
    return this.tracker_group.getConnection();
  }

  /**
   * query storage server to upload file
   *
   * @param trackerServer the tracker server
   * @return storage server Socket object, return null if fail
   */
  public StorageServer getStoreStorage(TrackerServer trackerServer) throws IOException {
    final String groupName = null;
    return this.getStoreStorage(trackerServer, groupName);
  }

  /**
   * query storage server to upload file
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name to upload file to, can be empty
   * @return storage server object, return null if fail
   */
  public StorageServer getStoreStorage(TrackerServer trackerServer, String groupName) throws IOException {
    byte[] header;
    String ip_addr;
    int port;
    byte cmd;
    int out_len;
    boolean bNewConnection;
    byte store_path;
    Socket trackerSocket;

    if (trackerServer == null) {
      trackerServer = getConnection();
      if (trackerServer == null) {
        return null;
      }
      bNewConnection = true;
    } else {
      bNewConnection = false;
    }

    trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    try {
      if (groupName == null || groupName.length() == 0) {
        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE;
        out_len = 0;
      } else {
        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE;
        out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
      }
      header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
      out.write(header);

      if (groupName != null && groupName.length() > 0) {
        byte[] bGroupName;
        byte[] bs;
        int group_len;

        bs = groupName.getBytes(ClientGlobal.g_charset);
        bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

        if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
          group_len = bs.length;
        } else {
          group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
        }
        Arrays.fill(bGroupName, (byte) 0);
        System.arraycopy(bs, 0, bGroupName, 0, group_len);
        out.write(bGroupName);
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
        ProtoCommon.TRACKER_PROTO_CMD_RESP,
        ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN);
      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0) {
        return null;
      }

      ip_addr = new String(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();

      port = (int) ProtoCommon.buff2long(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN
        + ProtoCommon.FDFS_IPADDR_SIZE - 1);
      store_path = pkgInfo.body[ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN - 1];

      return new StorageServer(ip_addr, port, store_path);
    } catch (IOException ex) {
      if (!bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }

      throw ex;
    } finally {
      if (bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }
  }

  /**
   * query storage servers to upload file
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name to upload file to, can be empty
   * @return storage servers, return null if fail
   */
  public StorageServer[] getStoreStorages(TrackerServer trackerServer, String groupName) throws IOException {
    byte[] header;
    String ip_addr;
    int port;
    byte cmd;
    int out_len;
    boolean bNewConnection;
    Socket trackerSocket;

    if (trackerServer == null) {
      trackerServer = getConnection();
      if (trackerServer == null) {
        return null;
      }
      bNewConnection = true;
    } else {
      bNewConnection = false;
    }

    trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    try {
      if (groupName == null || groupName.length() == 0) {
        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL;
        out_len = 0;
      } else {
        cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL;
        out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
      }
      header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
      out.write(header);

      if (groupName != null && groupName.length() > 0) {
        byte[] bGroupName;
        byte[] bs;
        int group_len;

        bs = groupName.getBytes(ClientGlobal.g_charset);
        bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

        if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
          group_len = bs.length;
        } else {
          group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
        }
        Arrays.fill(bGroupName, (byte) 0);
        System.arraycopy(bs, 0, bGroupName, 0, group_len);
        out.write(bGroupName);
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0) {
        return null;
      }

      if (pkgInfo.body.length < ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN) {
        this.errno = ProtoCommon.ERR_NO_EINVAL;
        return null;
      }

      int ipPortLen = pkgInfo.body.length - (ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1);
      final int recordLength = ProtoCommon.FDFS_IPADDR_SIZE - 1 + ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;

      if (ipPortLen % recordLength != 0) {
        this.errno = ProtoCommon.ERR_NO_EINVAL;
        return null;
      }

      int serverCount = ipPortLen / recordLength;
      if (serverCount > 16) {
        this.errno = ProtoCommon.ERR_NO_ENOSPC;
        return null;
      }

      StorageServer[] results = new StorageServer[serverCount];
      byte store_path = pkgInfo.body[pkgInfo.body.length - 1];
      int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;

      for (int i = 0; i < serverCount; i++) {
        ip_addr = new String(pkgInfo.body, offset, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
        offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;

        port = (int) ProtoCommon.buff2long(pkgInfo.body, offset);
        offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;

        results[i] = new StorageServer(ip_addr, port, store_path);
      }

      return results;
    } catch (IOException ex) {
      if (!bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }

      throw ex;
    } finally {
      if (bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }
  }

  /**
   * query storage server to download file
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name of storage server
   * @param filename      filename on storage server
   * @return storage server Socket object, return null if fail
   */
  public StorageServer getFetchStorage(TrackerServer trackerServer,
                                       String groupName, String filename) throws IOException {
    ServerInfo[] servers = this.getStorages(trackerServer, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE,
      groupName, filename);
    if (servers == null) {
      return null;
    } else {
      return new StorageServer(servers[0].getIpAddr(), servers[0].getPort(), 0);
    }
  }

  /**
   * query storage server to update file (delete file or set meta data)
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name of storage server
   * @param filename      filename on storage server
   * @return storage server Socket object, return null if fail
   */
  public StorageServer getUpdateStorage(TrackerServer trackerServer,
                                        String groupName, String filename) throws IOException {
    ServerInfo[] servers = this.getStorages(trackerServer, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE,
      groupName, filename);
    if (servers == null) {
      return null;
    } else {
      return new StorageServer(servers[0].getIpAddr(), servers[0].getPort(), 0);
    }
  }

  /**
   * get storage servers to download file
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name of storage server
   * @param filename      filename on storage server
   * @return storage servers, return null if fail
   */
  public ServerInfo[] getFetchStorages(TrackerServer trackerServer,
                                       String groupName, String filename) throws IOException {
    return this.getStorages(trackerServer, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL,
      groupName, filename);
  }

  /**
   * query storage server to download file
   *
   * @param trackerServer the tracker server
   * @param cmd           command code, ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE or
   *                      ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE
   * @param groupName     the group name of storage server
   * @param filename      filename on storage server
   * @return storage server Socket object, return null if fail
   */
  protected ServerInfo[] getStorages(TrackerServer trackerServer,
                                     byte cmd, String groupName, String filename) throws IOException {
    byte[] header;
    byte[] bFileName;
    byte[] bGroupName;
    byte[] bs;
    int len;
    String ip_addr;
    int port;
    boolean bNewConnection;
    Socket trackerSocket;

    if (trackerServer == null) {
      trackerServer = getConnection();
      if (trackerServer == null) {
        return null;
      }
      bNewConnection = true;
    } else {
      bNewConnection = false;
    }
    trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    try {
      bs = groupName.getBytes(ClientGlobal.g_charset);
      bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
      bFileName = filename.getBytes(ClientGlobal.g_charset);

      if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
        len = bs.length;
      } else {
        len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
      }
      Arrays.fill(bGroupName, (byte) 0);
      System.arraycopy(bs, 0, bGroupName, 0, len);

      header = ProtoCommon.packHeader(cmd, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + bFileName.length, (byte) 0);
      byte[] wholePkg = new byte[header.length + bGroupName.length + bFileName.length];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
      System.arraycopy(bFileName, 0, wholePkg, header.length + bGroupName.length, bFileName.length);
      out.write(wholePkg);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0) {
        return null;
      }

      if (pkgInfo.body.length < ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) {
        throw new IOException("Invalid body length: " + pkgInfo.body.length);
      }

      if ((pkgInfo.body.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) % (ProtoCommon.FDFS_IPADDR_SIZE - 1) != 0) {
        throw new IOException("Invalid body length: " + pkgInfo.body.length);
      }

      int server_count = 1 + (pkgInfo.body.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) / (ProtoCommon.FDFS_IPADDR_SIZE - 1);

      ip_addr = new String(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
      int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ProtoCommon.FDFS_IPADDR_SIZE - 1;

      port = (int) ProtoCommon.buff2long(pkgInfo.body, offset);
      offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;

      ServerInfo[] servers = new ServerInfo[server_count];
      servers[0] = new ServerInfo(ip_addr, port);
      for (int i = 1; i < server_count; i++) {
        servers[i] = new ServerInfo(new String(pkgInfo.body, offset, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim(), port);
        offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;
      }

      return servers;
    } catch (IOException ex) {
      if (!bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }

      throw ex;
    } finally {
      if (bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }
  }

  /**
   * query storage server to download file
   *
   * @param trackerServer the tracker server
   * @param file_id       the file id(including group name and filename)
   * @return storage server Socket object, return null if fail
   */
  public StorageServer getFetchStorage1(TrackerServer trackerServer, String file_id) throws IOException {
    String[] parts = new String[2];
    this.errno = StorageClient1.split_file_id(file_id, parts);
    if (this.errno != 0) {
      return null;
    }

    return this.getFetchStorage(trackerServer, parts[0], parts[1]);
  }

  /**
   * get storage servers to download file
   *
   * @param trackerServer the tracker server
   * @param file_id       the file id(including group name and filename)
   * @return storage servers, return null if fail
   */
  public ServerInfo[] getFetchStorages1(TrackerServer trackerServer, String file_id) throws IOException {
    String[] parts = new String[2];
    this.errno = StorageClient1.split_file_id(file_id, parts);
    if (this.errno != 0) {
      return null;
    }

    return this.getFetchStorages(trackerServer, parts[0], parts[1]);
  }

  /**
   * list groups
   *
   * @param trackerServer the tracker server
   * @return group stat array, return null if fail
   */
  public StructGroupStat[] listGroups(TrackerServer trackerServer) throws IOException {
    byte[] header;
    String ip_addr;
    int port;
    byte cmd;
    int out_len;
    boolean bNewConnection;
    byte store_path;
    Socket trackerSocket;

    if (trackerServer == null) {
      trackerServer = getConnection();
      if (trackerServer == null) {
        return null;
      }
      bNewConnection = true;
    } else {
      bNewConnection = false;
    }

    trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    try {
      header = ProtoCommon.packHeader(ProtoCommon.TRACKER_PROTO_CMD_SERVER_LIST_GROUP, 0, (byte) 0);
      out.write(header);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0) {
        return null;
      }

      ProtoStructDecoder<StructGroupStat> decoder = new ProtoStructDecoder<StructGroupStat>();
      return decoder.decode(pkgInfo.body, StructGroupStat.class, StructGroupStat.getFieldsTotalSize());
    } catch (IOException ex) {
      if (!bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }

      throw ex;
    } catch (Exception ex) {
      ex.printStackTrace();
      this.errno = ProtoCommon.ERR_NO_EINVAL;
      return null;
    } finally {
      if (bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }
  }

  /**
   * query storage server stat info of the group
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name of storage server
   * @return storage server stat array, return null if fail
   */
  public StructStorageStat[] listStorages(TrackerServer trackerServer, String groupName) throws IOException {
    final String storageIpAddr = null;
    return this.listStorages(trackerServer, groupName, storageIpAddr);
  }

  /**
   * query storage server stat info of the group
   *
   * @param trackerServer the tracker server
   * @param groupName     the group name of storage server
   * @param storageIpAddr the storage server ip address, can be null or empty
   * @return storage server stat array, return null if fail
   */
  public StructStorageStat[] listStorages(TrackerServer trackerServer,
                                          String groupName, String storageIpAddr) throws IOException {
    byte[] header;
    byte[] bGroupName;
    byte[] bs;
    int len;
    boolean bNewConnection;
    Socket trackerSocket;

    if (trackerServer == null) {
      trackerServer = getConnection();
      if (trackerServer == null) {
        return null;
      }
      bNewConnection = true;
    } else {
      bNewConnection = false;
    }
    trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    try {
      bs = groupName.getBytes(ClientGlobal.g_charset);
      bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

      if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
        len = bs.length;
      } else {
        len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
      }
      Arrays.fill(bGroupName, (byte) 0);
      System.arraycopy(bs, 0, bGroupName, 0, len);

      int ipAddrLen;
      byte[] bIpAddr;
      if (storageIpAddr != null && storageIpAddr.length() > 0) {
        bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
        if (bIpAddr.length < ProtoCommon.FDFS_IPADDR_SIZE) {
          ipAddrLen = bIpAddr.length;
        } else {
          ipAddrLen = ProtoCommon.FDFS_IPADDR_SIZE - 1;
        }
      } else {
        bIpAddr = null;
        ipAddrLen = 0;
      }

      header = ProtoCommon.packHeader(ProtoCommon.TRACKER_PROTO_CMD_SERVER_LIST_STORAGE, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ipAddrLen, (byte) 0);
      byte[] wholePkg = new byte[header.length + bGroupName.length + ipAddrLen];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
      if (ipAddrLen > 0) {
        System.arraycopy(bIpAddr, 0, wholePkg, header.length + bGroupName.length, ipAddrLen);
      }
      out.write(wholePkg);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
        ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0) {
        return null;
      }

      ProtoStructDecoder<StructStorageStat> decoder = new ProtoStructDecoder<StructStorageStat>();
      return decoder.decode(pkgInfo.body, StructStorageStat.class, StructStorageStat.getFieldsTotalSize());
    } catch (IOException ex) {
      if (!bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }

      throw ex;
    } catch (Exception ex) {
      ex.printStackTrace();
      this.errno = ProtoCommon.ERR_NO_EINVAL;
      return null;
    } finally {
      if (bNewConnection) {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }
  }

  /**
   * delete a storage server from the tracker server
   *
   * @param trackerServer the connected tracker server
   * @param groupName     the group name of storage server
   * @param storageIpAddr the storage server ip address
   * @return true for success, false for fail
   */
  private boolean deleteStorage(TrackerServer trackerServer,
                                String groupName, String storageIpAddr) throws IOException {
    byte[] header;
    byte[] bGroupName;
    byte[] bs;
    int len;
    Socket trackerSocket;

    trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    bs = groupName.getBytes(ClientGlobal.g_charset);
    bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

    if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
      len = bs.length;
    } else {
      len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
    }
    Arrays.fill(bGroupName, (byte) 0);
    System.arraycopy(bs, 0, bGroupName, 0, len);

    int ipAddrLen;
    byte[] bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
    if (bIpAddr.length < ProtoCommon.FDFS_IPADDR_SIZE) {
      ipAddrLen = bIpAddr.length;
    } else {
      ipAddrLen = ProtoCommon.FDFS_IPADDR_SIZE - 1;
    }

    header = ProtoCommon.packHeader(ProtoCommon.TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ipAddrLen, (byte) 0);
    byte[] wholePkg = new byte[header.length + bGroupName.length + ipAddrLen];
    System.arraycopy(header, 0, wholePkg, 0, header.length);
    System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
    System.arraycopy(bIpAddr, 0, wholePkg, header.length + bGroupName.length, ipAddrLen);
    out.write(wholePkg);

    ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(),
      ProtoCommon.TRACKER_PROTO_CMD_RESP, 0);
    this.errno = pkgInfo.errno;
    return pkgInfo.errno == 0;
  }

  /**
   * delete a storage server from the global FastDFS cluster
   *
   * @param groupName     the group name of storage server
   * @param storageIpAddr the storage server ip address
   * @return true for success, false for fail
   */
  public boolean deleteStorage(String groupName, String storageIpAddr) throws IOException {
    return this.deleteStorage(ClientGlobal.g_tracker_group, groupName, storageIpAddr);
  }

  /**
   * delete a storage server from the FastDFS cluster
   *
   * @param trackerGroup  the tracker server group
   * @param groupName     the group name of storage server
   * @param storageIpAddr the storage server ip address
   * @return true for success, false for fail
   */
  public boolean deleteStorage(TrackerGroup trackerGroup,
                               String groupName, String storageIpAddr) throws IOException {
    int serverIndex;
    int notFoundCount;
    TrackerServer trackerServer;

    notFoundCount = 0;
    for (serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++) {
      try {
        trackerServer = trackerGroup.getConnection(serverIndex);
      } catch (IOException ex) {
        ex.printStackTrace(System.err);
        this.errno = ProtoCommon.ECONNREFUSED;
        return false;
      }

      try {
        StructStorageStat[] storageStats = listStorages(trackerServer, groupName, storageIpAddr);
        if (storageStats == null) {
          if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
            notFoundCount++;
          } else {
            return false;
          }
        } else if (storageStats.length == 0) {
          notFoundCount++;
        } else if (storageStats[0].getStatus() == ProtoCommon.FDFS_STORAGE_STATUS_ONLINE ||
          storageStats[0].getStatus() == ProtoCommon.FDFS_STORAGE_STATUS_ACTIVE) {
          this.errno = ProtoCommon.ERR_NO_EBUSY;
          return false;
        }
      } finally {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }

    if (notFoundCount == trackerGroup.tracker_servers.length) {
      this.errno = ProtoCommon.ERR_NO_ENOENT;
      return false;
    }

    notFoundCount = 0;
    for (serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++) {
      try {
        trackerServer = trackerGroup.getConnection(serverIndex);
      } catch (IOException ex) {
        System.err.println("connect to server " + trackerGroup.tracker_servers[serverIndex].getAddress().getHostAddress() + ":" + trackerGroup.tracker_servers[serverIndex].getPort() + " fail");
        ex.printStackTrace(System.err);
        this.errno = ProtoCommon.ECONNREFUSED;
        return false;
      }

      try {
        if (!this.deleteStorage(trackerServer, groupName, storageIpAddr)) {
          if (this.errno != 0) {
            if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
              notFoundCount++;
            } else if (this.errno != ProtoCommon.ERR_NO_EALREADY) {
              return false;
            }
          }
        }
      } finally {
        try {
          trackerServer.close();
        } catch (IOException ex1) {
          ex1.printStackTrace();
        }
      }
    }

    if (notFoundCount == trackerGroup.tracker_servers.length) {
      this.errno = ProtoCommon.ERR_NO_ENOENT;
      return false;
    }

    if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
      this.errno = 0;
    }

    return this.errno == 0;
  }
}
