content: /**
   * 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 org.zeus.common.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;
    }
  }
  