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 org.zeus.common.fastdfs.common.Base64;
  import org.zeus.common.fastdfs.common.MyException;
  import org.zeus.common.fastdfs.common.NameValuePair;
  
  import java.io.*;
  import java.net.Socket;
  import java.util.Arrays;
  
  /**
   * Storage client for 2 fields file id: group name and filename
   *
   * @author Happy Fish / YuQing
   * @version Version 1.24
   */
  public class StorageClient {
    public final static Base64 base64 = new Base64('-', '_', '.', 0);
    protected TrackerServer trackerServer;
    protected StorageServer storageServer;
    protected byte errno;
  
    /**
     * constructor using global settings in class ClientGlobal
     */
    public StorageClient() {
      this.trackerServer = null;
      this.storageServer = null;
    }
  
    /**
     * constructor with tracker server and storage server
     *
     * @param trackerServer the tracker server, can be null
     * @param storageServer the storage server, can be null
     */
    public StorageClient(TrackerServer trackerServer, StorageServer storageServer) {
      this.trackerServer = trackerServer;
      this.storageServer = storageServer;
    }
  
    /**
     * get the error code of last call
     *
     * @return the error code of last call
     */
    public byte getErrorCode() {
      return this.errno;
    }
  
    /**
     * upload file to storage server (by file name)
     *
     * @param local_filename local filename to upload
     * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
     * @param meta_list      meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file </li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String local_filename, String file_ext_name,
                                NameValuePair[] meta_list) throws IOException, MyException {
      final String group_name = null;
      return this.upload_file(group_name, local_filename, file_ext_name, meta_list);
    }
  
    /**
     * upload file to storage server (by file name)
     *
     * @param group_name     the group name to upload file to, can be empty
     * @param local_filename local filename to upload
     * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
     * @param meta_list      meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file </li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    protected String[] upload_file(String group_name, String local_filename, String file_ext_name,
                                   NameValuePair[] meta_list) throws IOException, MyException {
      final byte cmd = ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE;
      return this.upload_file(cmd, group_name, local_filename, file_ext_name, meta_list);
    }
  
    /**
     * upload file to storage server (by file name)
     *
     * @param cmd            the command
     * @param group_name     the group name to upload file to, can be empty
     * @param local_filename local filename to upload
     * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
     * @param meta_list      meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file </li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    protected String[] upload_file(byte cmd, String group_name, String local_filename, String file_ext_name,
                                   NameValuePair[] meta_list) throws IOException, MyException {
      File f = new File(local_filename);
      FileInputStream fis = new FileInputStream(f);
  
      if (file_ext_name == null) {
        int nPos = local_filename.lastIndexOf('.');
        if (nPos > 0 && local_filename.length() - nPos <= ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
          file_ext_name = local_filename.substring(nPos + 1);
        }
      }
  
      try {
        return this.do_upload_file(cmd, group_name, null, null, file_ext_name,
          f.length(), new UploadStream(fis, f.length()), meta_list);
      } finally {
        fis.close();
      }
    }
  
    /**
     * upload file to storage server (by file buff)
     *
     * @param file_buff     file content/buff
     * @param offset        start offset of the buff
     * @param length        the length of buff to upload
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(byte[] file_buff, int offset, int length, String file_ext_name,
                                NameValuePair[] meta_list) throws IOException, MyException {
      final String group_name = null;
      return this.upload_file(group_name, file_buff, offset, length, file_ext_name, meta_list);
    }
  
    /**
     * upload file to storage server (by file buff)
     *
     * @param group_name    the group name to upload file to, can be empty
     * @param file_buff     file content/buff
     * @param offset        start offset of the buff
     * @param length        the length of buff to upload
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, byte[] file_buff, int offset, int length,
                                String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name,
        length, new UploadBuff(file_buff, offset, length), meta_list);
    }
  
    /**
     * upload file to storage server (by file buff)
     *
     * @param file_buff     file content/buff
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(byte[] file_buff, String file_ext_name,
                                NameValuePair[] meta_list) throws IOException, MyException {
      final String group_name = null;
      return this.upload_file(group_name, file_buff, 0, file_buff.length, file_ext_name, meta_list);
    }
  
    /**
     * upload file to storage server (by file buff)
     *
     * @param group_name    the group name to upload file to, can be empty
     * @param file_buff     file content/buff
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, byte[] file_buff,
                                String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, null, null, file_ext_name,
        file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
    }
  
    /**
     * upload file to storage server (by callback)
     *
     * @param group_name    the group name to upload file to, can be empty
     * @param file_size     the file size
     * @param callback      the write data callback object
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, long file_size, UploadCallback callback,
                                String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      final String master_filename = null;
      final String prefix_name = null;
  
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_FILE, group_name, master_filename, prefix_name,
        file_ext_name, file_size, callback, meta_list);
    }
  
    /**
     * upload file to storage server (by file name, slave file mode)
     *
     * @param group_name      the group name of master file
     * @param master_filename the master file name to generate the slave file
     * @param prefix_name     the prefix name to generate the slave file
     * @param local_filename  local filename to upload
     * @param file_ext_name   file ext name, do not include dot(.), null to extract ext name from the local filename
     * @param meta_list       meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file </li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, String master_filename, String prefix_name,
                                String local_filename, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      if ((group_name == null || group_name.length() == 0) ||
        (master_filename == null || master_filename.length() == 0) ||
        (prefix_name == null)) {
        throw new MyException("invalid arguement");
      }
  
      File f = new File(local_filename);
      FileInputStream fis = new FileInputStream(f);
  
      if (file_ext_name == null) {
        int nPos = local_filename.lastIndexOf('.');
        if (nPos > 0 && local_filename.length() - nPos <= ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
          file_ext_name = local_filename.substring(nPos + 1);
        }
      }
  
      try {
        return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
          file_ext_name, f.length(), new UploadStream(fis, f.length()), meta_list);
      } finally {
        fis.close();
      }
    }
  
    /**
     * upload file to storage server (by file buff, slave file mode)
     *
     * @param group_name      the group name of master file
     * @param master_filename the master file name to generate the slave file
     * @param prefix_name     the prefix name to generate the slave file
     * @param file_buff       file content/buff
     * @param file_ext_name   file ext name, do not include dot(.)
     * @param meta_list       meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, String master_filename, String prefix_name,
                                byte[] file_buff, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      if ((group_name == null || group_name.length() == 0) ||
        (master_filename == null || master_filename.length() == 0) ||
        (prefix_name == null)) {
        throw new MyException("invalid arguement");
      }
  
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
        file_ext_name, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
    }
  
    /**
     * upload file to storage server (by file buff, slave file mode)
     *
     * @param group_name      the group name of master file
     * @param master_filename the master file name to generate the slave file
     * @param prefix_name     the prefix name to generate the slave file
     * @param file_buff       file content/buff
     * @param offset          start offset of the buff
     * @param length          the length of buff to upload
     * @param file_ext_name   file ext name, do not include dot(.)
     * @param meta_list       meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, String master_filename, String prefix_name,
                                byte[] file_buff, int offset, int length, String file_ext_name,
                                NameValuePair[] meta_list) throws IOException, MyException {
      if ((group_name == null || group_name.length() == 0) ||
        (master_filename == null || master_filename.length() == 0) ||
        (prefix_name == null)) {
        throw new MyException("invalid arguement");
      }
  
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
        file_ext_name, length, new UploadBuff(file_buff, offset, length), meta_list);
    }
  
    /**
     * upload file to storage server (by callback, slave file mode)
     *
     * @param group_name      the group name to upload file to, can be empty
     * @param master_filename the master file name to generate the slave file
     * @param prefix_name     the prefix name to generate the slave file
     * @param file_size       the file size
     * @param callback        the write data callback object
     * @param file_ext_name   file ext name, do not include dot(.)
     * @param meta_list       meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_file(String group_name, String master_filename,
                                String prefix_name, long file_size, UploadCallback callback,
                                String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, group_name, master_filename, prefix_name,
        file_ext_name, file_size, callback, meta_list);
    }
  
    /**
     * upload appender file to storage server (by file name)
     *
     * @param local_filename local filename to upload
     * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
     * @param meta_list      meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file </li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_appender_file(String local_filename, String file_ext_name,
                                         NameValuePair[] meta_list) throws IOException, MyException {
      final String group_name = null;
      return this.upload_appender_file(group_name, local_filename, file_ext_name, meta_list);
    }
  
    /**
     * upload appender file to storage server (by file name)
     *
     * @param group_name     the group name to upload file to, can be empty
     * @param local_filename local filename to upload
     * @param file_ext_name  file ext name, do not include dot(.), null to extract ext name from the local filename
     * @param meta_list      meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file </li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    protected String[] upload_appender_file(String group_name, String local_filename, String file_ext_name,
                                            NameValuePair[] meta_list) throws IOException, MyException {
      final byte cmd = ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE;
      return this.upload_file(cmd, group_name, local_filename, file_ext_name, meta_list);
    }
  
    /**
     * upload appender file to storage server (by file buff)
     *
     * @param file_buff     file content/buff
     * @param offset        start offset of the buff
     * @param length        the length of buff to upload
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_appender_file(byte[] file_buff, int offset, int length, String file_ext_name,
                                         NameValuePair[] meta_list) throws IOException, MyException {
      final String group_name = null;
      return this.upload_appender_file(group_name, file_buff, offset, length, file_ext_name, meta_list);
    }
  
    /**
     * upload appender file to storage server (by file buff)
     *
     * @param group_name    the group name to upload file to, can be empty
     * @param file_buff     file content/buff
     * @param offset        start offset of the buff
     * @param length        the length of buff to upload
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_appender_file(String group_name, byte[] file_buff, int offset, int length,
                                         String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null, file_ext_name,
        length, new UploadBuff(file_buff, offset, length), meta_list);
    }
  
    /**
     * upload appender file to storage server (by file buff)
     *
     * @param file_buff     file content/buff
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_appender_file(byte[] file_buff, String file_ext_name,
                                         NameValuePair[] meta_list) throws IOException, MyException {
      final String group_name = null;
      return this.upload_appender_file(group_name, file_buff, 0, file_buff.length, file_ext_name, meta_list);
    }
  
    /**
     * upload appender file to storage server (by file buff)
     *
     * @param group_name    the group name to upload file to, can be empty
     * @param file_buff     file content/buff
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_appender_file(String group_name, byte[] file_buff,
                                         String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, null, null, file_ext_name,
        file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
    }
  
    /**
     * upload appender file to storage server (by callback)
     *
     * @param group_name    the group name to upload file to, can be empty
     * @param file_size     the file size
     * @param callback      the write data callback object
     * @param file_ext_name file ext name, do not include dot(.)
     * @param meta_list     meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li>results[0]: the group name to store the file</li></ul>
     * <ul><li>results[1]: the new created filename</li></ul>
     * return null if fail
     */
    public String[] upload_appender_file(String group_name, long file_size, UploadCallback callback,
                                         String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException {
      final String master_filename = null;
      final String prefix_name = null;
  
      return this.do_upload_file(ProtoCommon.STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE, group_name, master_filename, prefix_name,
        file_ext_name, file_size, callback, meta_list);
    }
  
    /**
     * append file to storage server (by file name)
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param local_filename    local filename to append
     * @return 0 for success, != 0 for error (error no)
     */
    public int append_file(String group_name, String appender_filename, String local_filename) throws IOException, MyException {
      File f = new File(local_filename);
      FileInputStream fis = new FileInputStream(f);
  
      try {
        return this.do_append_file(group_name, appender_filename, f.length(), new UploadStream(fis, f.length()));
      } finally {
        fis.close();
      }
    }
  
    /**
     * append file to storage server (by file buff)
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_buff         file content/buff
     * @return 0 for success, != 0 for error (error no)
     */
    public int append_file(String group_name, String appender_filename, byte[] file_buff) throws IOException, MyException {
      return this.do_append_file(group_name, appender_filename, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length));
    }
  
    /**
     * append file to storage server (by file buff)
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_buff         file content/buff
     * @param offset            start offset of the buff
     * @param length            the length of buff to append
     * @return 0 for success, != 0 for error (error no)
     */
    public int append_file(String group_name, String appender_filename,
                           byte[] file_buff, int offset, int length) throws IOException, MyException {
      return this.do_append_file(group_name, appender_filename, length, new UploadBuff(file_buff, offset, length));
    }
  
    /**
     * append file to storage server (by callback)
     *
     * @param group_name        the group name to append file to
     * @param appender_filename the appender filename
     * @param file_size         the file size
     * @param callback          the write data callback object
     * @return 0 for success, != 0 for error (error no)
     */
    public int append_file(String group_name, String appender_filename,
                           long file_size, UploadCallback callback) throws IOException, MyException {
      return this.do_append_file(group_name, appender_filename, file_size, callback);
    }
  
    /**
     * modify appender file to storage server (by file name)
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_offset       the offset of appender file
     * @param local_filename    local filename to append
     * @return 0 for success, != 0 for error (error no)
     */
    public int modify_file(String group_name, String appender_filename,
                           long file_offset, String local_filename) throws IOException, MyException {
      File f = new File(local_filename);
      FileInputStream fis = new FileInputStream(f);
  
      try {
        return this.do_modify_file(group_name, appender_filename, file_offset,
          f.length(), new UploadStream(fis, f.length()));
      } finally {
        fis.close();
      }
    }
  
    /**
     * modify appender file to storage server (by file buff)
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_offset       the offset of appender file
     * @param file_buff         file content/buff
     * @return 0 for success, != 0 for error (error no)
     */
    public int modify_file(String group_name, String appender_filename,
                           long file_offset, byte[] file_buff) throws IOException, MyException {
      return this.do_modify_file(group_name, appender_filename, file_offset,
        file_buff.length, new UploadBuff(file_buff, 0, file_buff.length));
    }
  
    /**
     * modify appender file to storage server (by file buff)
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_offset       the offset of appender file
     * @param file_buff         file content/buff
     * @param buffer_offset     start offset of the buff
     * @param buffer_length     the length of buff to modify
     * @return 0 for success, != 0 for error (error no)
     */
    public int modify_file(String group_name, String appender_filename,
                           long file_offset, byte[] file_buff, int buffer_offset, int buffer_length) throws IOException, MyException {
      return this.do_modify_file(group_name, appender_filename, file_offset,
        buffer_length, new UploadBuff(file_buff, buffer_offset, buffer_length));
    }
  
    /**
     * modify appender file to storage server (by callback)
     *
     * @param group_name        the group name to modify file to
     * @param appender_filename the appender filename
     * @param file_offset       the offset of appender file
     * @param modify_size       the modify size
     * @param callback          the write data callback object
     * @return 0 for success, != 0 for error (error no)
     */
    public int modify_file(String group_name, String appender_filename,
                           long file_offset, long modify_size, UploadCallback callback) throws IOException, MyException {
      return this.do_modify_file(group_name, appender_filename, file_offset,
        modify_size, callback);
    }
  
    /**
     * upload file to storage server
     *
     * @param cmd             the command code
     * @param group_name      the group name to upload file to, can be empty
     * @param master_filename the master file name to generate the slave file
     * @param prefix_name     the prefix name to generate the slave file
     * @param file_ext_name   file ext name, do not include dot(.)
     * @param file_size       the file size
     * @param callback        the write data callback object
     * @param meta_list       meta info array
     * @return 2 elements string array if success:<br>
     * <ul><li> results[0]: the group name to store the file</li></ul>
     * <ul><li> results[1]: the new created filename</li></ul>
     * return null if fail
     */
    protected String[] do_upload_file(byte cmd, String group_name, String master_filename,
                                      String prefix_name, String file_ext_name, long file_size, UploadCallback callback,
                                      NameValuePair[] meta_list) throws IOException, MyException {
      byte[] header;
      byte[] ext_name_bs;
      String new_group_name;
      String remote_filename;
      boolean bNewConnection;
      Socket storageSocket;
      byte[] sizeBytes;
      byte[] hexLenBytes;
      byte[] masterFilenameBytes;
      boolean bUploadSlave;
      int offset;
      long body_len;
  
      bUploadSlave = ((group_name != null && group_name.length() > 0) &&
        (master_filename != null && master_filename.length() > 0) &&
        (prefix_name != null));
      if (bUploadSlave) {
        bNewConnection = this.newUpdatableStorageConnection(group_name, master_filename);
      } else {
        bNewConnection = this.newWritableStorageConnection(group_name);
      }
  
      try {
        storageSocket = this.storageServer.getSocket();
  
        ext_name_bs = new byte[ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN];
        Arrays.fill(ext_name_bs, (byte) 0);
        if (file_ext_name != null && file_ext_name.length() > 0) {
          byte[] bs = file_ext_name.getBytes(ClientGlobal.g_charset);
          int ext_name_len = bs.length;
          if (ext_name_len > ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN) {
            ext_name_len = ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN;
          }
          System.arraycopy(bs, 0, ext_name_bs, 0, ext_name_len);
        }
  
        if (bUploadSlave) {
          masterFilenameBytes = master_filename.getBytes(ClientGlobal.g_charset);
  
          sizeBytes = new byte[2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
          body_len = sizeBytes.length + ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN
            + masterFilenameBytes.length + file_size;
  
          hexLenBytes = ProtoCommon.long2buff(master_filename.length());
          System.arraycopy(hexLenBytes, 0, sizeBytes, 0, hexLenBytes.length);
          offset = hexLenBytes.length;
        } else {
          masterFilenameBytes = null;
          sizeBytes = new byte[1 + 1 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
          body_len = sizeBytes.length + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + file_size;
  
          sizeBytes[0] = (byte) this.storageServer.getStorePathIndex();
          offset = 1;
        }
  
        hexLenBytes = ProtoCommon.long2buff(file_size);
        System.arraycopy(hexLenBytes, 0, sizeBytes, offset, hexLenBytes.length);
  
        OutputStream out = storageSocket.getOutputStream();
        header = ProtoCommon.packHeader(cmd, body_len, (byte) 0);
        byte[] wholePkg = new byte[(int) (header.length + body_len - file_size)];
        System.arraycopy(header, 0, wholePkg, 0, header.length);
        System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
        offset = header.length + sizeBytes.length;
        if (bUploadSlave) {
          byte[] prefix_name_bs = new byte[ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN];
          byte[] bs = prefix_name.getBytes(ClientGlobal.g_charset);
          int prefix_name_len = bs.length;
          Arrays.fill(prefix_name_bs, (byte) 0);
          if (prefix_name_len > ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN) {
            prefix_name_len = ProtoCommon.FDFS_FILE_PREFIX_MAX_LEN;
          }
          if (prefix_name_len > 0) {
            System.arraycopy(bs, 0, prefix_name_bs, 0, prefix_name_len);
          }
  
          System.arraycopy(prefix_name_bs, 0, wholePkg, offset, prefix_name_bs.length);
          offset += prefix_name_bs.length;
        }
  
        System.arraycopy(ext_name_bs, 0, wholePkg, offset, ext_name_bs.length);
        offset += ext_name_bs.length;
  
        if (bUploadSlave) {
          System.arraycopy(masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.length);
          offset += masterFilenameBytes.length;
        }
  
        out.write(wholePkg);
  
        if ((this.errno = (byte) callback.send(out)) != 0) {
          return null;
        }
  
        ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
          return null;
        }
  
        if (pkgInfo.body.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
          throw new MyException("body length: " + pkgInfo.body.length + " <= " + ProtoCommon.FDFS_GROUP_NAME_MAX_LEN);
        }
  
        new_group_name = new String(pkgInfo.body, 0, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN).trim();
        remote_filename = new String(pkgInfo.body, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, pkgInfo.body.length - ProtoCommon.FDFS_GROUP_NAME_MAX_LEN);
        String[] results = new String[2];
        results[0] = new_group_name;
        results[1] = remote_filename;
  
        if (meta_list == null || meta_list.length == 0) {
          return results;
        }
  
        int result = 0;
        try {
          result = this.set_metadata(new_group_name, remote_filename,
            meta_list, ProtoCommon.STORAGE_SET_METADATA_FLAG_OVERWRITE);
        } catch (IOException ex) {
          result = 5;
          throw ex;
        } finally {
          if (result != 0) {
            this.errno = (byte) result;
            this.delete_file(new_group_name, remote_filename);
            return null;
          }
        }
  
        return results;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * append file to storage server
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_size         the file size
     * @param callback          the write data callback object
     * @return return true for success, false for fail
     */
    protected int do_append_file(String group_name, String appender_filename,
                                 long file_size, UploadCallback callback) throws IOException, MyException {
      byte[] header;
      boolean bNewConnection;
      Socket storageSocket;
      byte[] hexLenBytes;
      byte[] appenderFilenameBytes;
      int offset;
      long body_len;
  
      if ((group_name == null || group_name.length() == 0) ||
        (appender_filename == null || appender_filename.length() == 0)) {
        this.errno = ProtoCommon.ERR_NO_EINVAL;
        return this.errno;
      }
  
      bNewConnection = this.newUpdatableStorageConnection(group_name, appender_filename);
  
      try {
        storageSocket = this.storageServer.getSocket();
  
        appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
        body_len = 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length + file_size;
  
        header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_APPEND_FILE, body_len, (byte) 0);
        byte[] wholePkg = new byte[(int) (header.length + body_len - file_size)];
        System.arraycopy(header, 0, wholePkg, 0, header.length);
        offset = header.length;
  
        hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        hexLenBytes = ProtoCommon.long2buff(file_size);
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        OutputStream out = storageSocket.getOutputStream();
  
        System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
        offset += appenderFilenameBytes.length;
  
        out.write(wholePkg);
        if ((this.errno = (byte) callback.send(out)) != 0) {
          return this.errno;
        }
  
        ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
          return this.errno;
        }
  
        return 0;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * modify appender file to storage server
     *
     * @param group_name        the group name of appender file
     * @param appender_filename the appender filename
     * @param file_offset       the offset of appender file
     * @param modify_size       the modify size
     * @param callback          the write data callback object
     * @return return true for success, false for fail
     */
    protected int do_modify_file(String group_name, String appender_filename,
                                 long file_offset, long modify_size, UploadCallback callback) throws IOException, MyException {
      byte[] header;
      boolean bNewConnection;
      Socket storageSocket;
      byte[] hexLenBytes;
      byte[] appenderFilenameBytes;
      int offset;
      long body_len;
  
      if ((group_name == null || group_name.length() == 0) ||
        (appender_filename == null || appender_filename.length() == 0)) {
        this.errno = ProtoCommon.ERR_NO_EINVAL;
        return this.errno;
      }
  
      bNewConnection = this.newUpdatableStorageConnection(group_name, appender_filename);
  
      try {
        storageSocket = this.storageServer.getSocket();
  
        appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
        body_len = 3 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length + modify_size;
  
        header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_MODIFY_FILE, body_len, (byte) 0);
        byte[] wholePkg = new byte[(int) (header.length + body_len - modify_size)];
        System.arraycopy(header, 0, wholePkg, 0, header.length);
        offset = header.length;
  
        hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        hexLenBytes = ProtoCommon.long2buff(file_offset);
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        hexLenBytes = ProtoCommon.long2buff(modify_size);
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        OutputStream out = storageSocket.getOutputStream();
  
        System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
        offset += appenderFilenameBytes.length;
  
        out.write(wholePkg);
        if ((this.errno = (byte) callback.send(out)) != 0) {
          return this.errno;
        }
  
        ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
          return this.errno;
        }
  
        return 0;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * delete file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return 0 for success, none zero for fail (error code)
     */
    public int delete_file(String group_name, String remote_filename) throws IOException, MyException {
      boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
  
      try {
        this.send_package(ProtoCommon.STORAGE_PROTO_CMD_DELETE_FILE, group_name, remote_filename);
        ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
  
        this.errno = pkgInfo.errno;
        return pkgInfo.errno;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * truncate appender file to size 0 from storage server
     *
     * @param group_name        the group name of storage server
     * @param appender_filename the appender filename
     * @return 0 for success, none zero for fail (error code)
     */
    public int truncate_file(String group_name, String appender_filename) throws IOException, MyException {
      final long truncated_file_size = 0;
      return this.truncate_file(group_name, appender_filename, truncated_file_size);
    }
  
    /**
     * truncate appender file from storage server
     *
     * @param group_name          the group name of storage server
     * @param appender_filename   the appender filename
     * @param truncated_file_size truncated file size
     * @return 0 for success, none zero for fail (error code)
     */
    public int truncate_file(String group_name, String appender_filename,
                             long truncated_file_size) throws IOException, MyException {
      byte[] header;
      boolean bNewConnection;
      Socket storageSocket;
      byte[] hexLenBytes;
      byte[] appenderFilenameBytes;
      int offset;
      int body_len;
  
      if ((group_name == null || group_name.length() == 0) ||
        (appender_filename == null || appender_filename.length() == 0)) {
        this.errno = ProtoCommon.ERR_NO_EINVAL;
        return this.errno;
      }
  
      bNewConnection = this.newUpdatableStorageConnection(group_name, appender_filename);
  
      try {
        storageSocket = this.storageServer.getSocket();
  
        appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
        body_len = 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + appenderFilenameBytes.length;
  
        header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_TRUNCATE_FILE, body_len, (byte) 0);
        byte[] wholePkg = new byte[header.length + body_len];
        System.arraycopy(header, 0, wholePkg, 0, header.length);
        offset = header.length;
  
        hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        hexLenBytes = ProtoCommon.long2buff(truncated_file_size);
        System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
        offset += hexLenBytes.length;
  
        OutputStream out = storageSocket.getOutputStream();
  
        System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
        offset += appenderFilenameBytes.length;
  
        out.write(wholePkg);
        ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
        this.errno = pkgInfo.errno;
        return pkgInfo.errno;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * download file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return file content/buff, return null if fail
     */
    public byte[] download_file(String group_name, String remote_filename) throws IOException, MyException {
      final long file_offset = 0;
      final long download_bytes = 0;
  
      return this.download_file(group_name, remote_filename, file_offset, download_bytes);
    }
  
    /**
     * download file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param file_offset     the start offset of the file
     * @param download_bytes  download bytes, 0 for remain bytes from offset
     * @return file content/buff, return null if fail
     */
    public byte[] download_file(String group_name, String remote_filename, long file_offset, long download_bytes) throws IOException, MyException {
      boolean bNewConnection = this.newReadableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
  
      try {
        ProtoCommon.RecvPackageInfo pkgInfo;
  
        this.send_download_package(group_name, remote_filename, file_offset, download_bytes);
        pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
  
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
          return null;
        }
  
        return pkgInfo.body;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * download file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param local_filename  filename on local
     * @return 0 success, return none zero errno if fail
     */
    public int download_file(String group_name, String remote_filename,
                             String local_filename) throws IOException, MyException {
      final long file_offset = 0;
      final long download_bytes = 0;
      return this.download_file(group_name, remote_filename,
        file_offset, download_bytes, local_filename);
    }
  
    /**
     * download file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param file_offset     the start offset of the file
     * @param download_bytes  download bytes, 0 for remain bytes from offset
     * @param local_filename  filename on local
     * @return 0 success, return none zero errno if fail
     */
    public int download_file(String group_name, String remote_filename,
                             long file_offset, long download_bytes,
                             String local_filename) throws IOException, MyException {
      boolean bNewConnection = this.newReadableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
      try {
        ProtoCommon.RecvHeaderInfo header;
        FileOutputStream out = new FileOutputStream(local_filename);
        try {
          this.errno = 0;
          this.send_download_package(group_name, remote_filename, file_offset, download_bytes);
  
          InputStream in = storageSocket.getInputStream();
          header = ProtoCommon.recvHeader(in, ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
          this.errno = header.errno;
          if (header.errno != 0) {
            return header.errno;
          }
  
          byte[] buff = new byte[256 * 1024];
          long remainBytes = header.body_len;
          int bytes;
  
          //System.out.println("expect_body_len=" + header.body_len);
  
          while (remainBytes > 0) {
            if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int) remainBytes)) < 0) {
              throw new IOException("recv package size " + (header.body_len - remainBytes) + " != " + header.body_len);
            }
  
            out.write(buff, 0, bytes);
            remainBytes -= bytes;
  
            //System.out.println("totalBytes=" + (header.body_len - remainBytes));
          }
  
          return 0;
        } catch (IOException ex) {
          if (this.errno == 0) {
            this.errno = ProtoCommon.ERR_NO_EIO;
          }
  
          throw ex;
        } finally {
          out.close();
          if (this.errno != 0) {
            (new File(local_filename)).delete();
          }
        }
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * download file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param callback        call callback.recv() when data arrive
     * @return 0 success, return none zero errno if fail
     */
    public int download_file(String group_name, String remote_filename,
                             DownloadCallback callback) throws IOException, MyException {
      final long file_offset = 0;
      final long download_bytes = 0;
      return this.download_file(group_name, remote_filename,
        file_offset, download_bytes, callback);
    }
  
    /**
     * download file from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param file_offset     the start offset of the file
     * @param download_bytes  download bytes, 0 for remain bytes from offset
     * @param callback        call callback.recv() when data arrive
     * @return 0 success, return none zero errno if fail
     */
    public int download_file(String group_name, String remote_filename,
                             long file_offset, long download_bytes,
                             DownloadCallback callback) throws IOException, MyException {
      int result;
      boolean bNewConnection = this.newReadableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
  
      try {
        ProtoCommon.RecvHeaderInfo header;
        this.send_download_package(group_name, remote_filename, file_offset, download_bytes);
  
        InputStream in = storageSocket.getInputStream();
        header = ProtoCommon.recvHeader(in, ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
        this.errno = header.errno;
        if (header.errno != 0) {
          return header.errno;
        }
  
        byte[] buff = new byte[2 * 1024];
        long remainBytes = header.body_len;
        int bytes;
  
        //System.out.println("expect_body_len=" + header.body_len);
  
        while (remainBytes > 0) {
          if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int) remainBytes)) < 0) {
            throw new IOException("recv package size " + (header.body_len - remainBytes) + " != " + header.body_len);
          }
  
          if ((result = callback.recv(header.body_len, buff, bytes)) != 0) {
            this.errno = (byte) result;
            return result;
          }
  
          remainBytes -= bytes;
          //System.out.println("totalBytes=" + (header.body_len - remainBytes));
        }
  
        return 0;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * get all metadata items from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return meta info array, return null if fail
     */
    public NameValuePair[] get_metadata(String group_name, String remote_filename) throws IOException, MyException {
      boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
  
      try {
        ProtoCommon.RecvPackageInfo pkgInfo;
  
        this.send_package(ProtoCommon.STORAGE_PROTO_CMD_GET_METADATA, group_name, remote_filename);
        pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, -1);
  
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
          return null;
        }
  
        return ProtoCommon.split_metadata(new String(pkgInfo.body, ClientGlobal.g_charset));
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * set metadata items to storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param meta_list       meta item array
     * @param op_flag         flag, can be one of following values: <br>
     *                        <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_OVERWRITE: overwrite all old
     *                        metadata items</li></ul>
     *                        <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_MERGE: merge, insert when
     *                        the metadata item not exist, otherwise update it</li></ul>
     * @return 0 for success, !=0 fail (error code)
     */
    public int set_metadata(String group_name, String remote_filename,
                            NameValuePair[] meta_list, byte op_flag) throws IOException, MyException {
      boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
  
      try {
        byte[] header;
        byte[] groupBytes;
        byte[] filenameBytes;
        byte[] meta_buff;
        byte[] bs;
        int groupLen;
        byte[] sizeBytes;
        ProtoCommon.RecvPackageInfo pkgInfo;
  
        if (meta_list == null) {
          meta_buff = new byte[0];
        } else {
          meta_buff = ProtoCommon.pack_metadata(meta_list).getBytes(ClientGlobal.g_charset);
        }
  
        filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
        sizeBytes = new byte[2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE];
        Arrays.fill(sizeBytes, (byte) 0);
  
        bs = ProtoCommon.long2buff(filenameBytes.length);
        System.arraycopy(bs, 0, sizeBytes, 0, bs.length);
        bs = ProtoCommon.long2buff(meta_buff.length);
        System.arraycopy(bs, 0, sizeBytes, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE, bs.length);
  
        groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
        bs = group_name.getBytes(ClientGlobal.g_charset);
  
        Arrays.fill(groupBytes, (byte) 0);
        if (bs.length <= groupBytes.length) {
          groupLen = bs.length;
        } else {
          groupLen = groupBytes.length;
        }
        System.arraycopy(bs, 0, groupBytes, 0, groupLen);
  
        header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_SET_METADATA,
          2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE + 1 + groupBytes.length
            + filenameBytes.length + meta_buff.length, (byte) 0);
        OutputStream out = storageSocket.getOutputStream();
        byte[] wholePkg = new byte[header.length + sizeBytes.length + 1 + groupBytes.length + filenameBytes.length];
        System.arraycopy(header, 0, wholePkg, 0, header.length);
        System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
        wholePkg[header.length + sizeBytes.length] = op_flag;
        System.arraycopy(groupBytes, 0, wholePkg, header.length + sizeBytes.length + 1, groupBytes.length);
        System.arraycopy(filenameBytes, 0, wholePkg, header.length + sizeBytes.length + 1 + groupBytes.length, filenameBytes.length);
        out.write(wholePkg);
        if (meta_buff.length > 0) {
          out.write(meta_buff);
        }
  
        pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP, 0);
  
        this.errno = pkgInfo.errno;
        return pkgInfo.errno;
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * get file info decoded from the filename, fetch from the storage if necessary
     *
     * @param group_name      the group name
     * @param remote_filename the filename
     * @return FileInfo object for success, return null for fail
     */
    public FileInfo get_file_info(String group_name, String remote_filename) throws IOException, MyException {
      if (remote_filename.length() < ProtoCommon.FDFS_FILE_PATH_LEN + ProtoCommon.FDFS_FILENAME_BASE64_LENGTH
        + ProtoCommon.FDFS_FILE_EXT_NAME_MAX_LEN + 1) {
        this.errno = ProtoCommon.ERR_NO_EINVAL;
        return null;
      }
  
      byte[] buff = base64.decodeAuto(remote_filename.substring(ProtoCommon.FDFS_FILE_PATH_LEN,
        ProtoCommon.FDFS_FILE_PATH_LEN + ProtoCommon.FDFS_FILENAME_BASE64_LENGTH));
  
      long file_size = ProtoCommon.buff2long(buff, 4 * 2);
      if (((remote_filename.length() > ProtoCommon.TRUNK_LOGIC_FILENAME_LENGTH) ||
        ((remote_filename.length() > ProtoCommon.NORMAL_LOGIC_FILENAME_LENGTH) && ((file_size & ProtoCommon.TRUNK_FILE_MARK_SIZE) == 0))) ||
        ((file_size & ProtoCommon.APPENDER_FILE_SIZE) != 0)) { //slave file or appender file
        FileInfo fi = this.query_file_info(group_name, remote_filename);
        if (fi == null) {
          return null;
        }
        return fi;
      }
  
      FileInfo fileInfo = new FileInfo(file_size, 0, 0, ProtoCommon.getIpAddress(buff, 0));
      fileInfo.setCreateTimestamp(ProtoCommon.buff2int(buff, 4));
      if ((file_size >> 63) != 0) {
        file_size &= 0xFFFFFFFFL;  //low 32 bits is file size
        fileInfo.setFileSize(file_size);
      }
      fileInfo.setCrc32(ProtoCommon.buff2int(buff, 4 * 4));
  
      return fileInfo;
    }
  
    /**
     * get file info from storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return FileInfo object for success, return null for fail
     */
    public FileInfo query_file_info(String group_name, String remote_filename) throws IOException, MyException {
      boolean bNewConnection = this.newUpdatableStorageConnection(group_name, remote_filename);
      Socket storageSocket = this.storageServer.getSocket();
  
      try {
        byte[] header;
        byte[] groupBytes;
        byte[] filenameBytes;
        byte[] bs;
        int groupLen;
        ProtoCommon.RecvPackageInfo pkgInfo;
  
        filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
        groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
        bs = group_name.getBytes(ClientGlobal.g_charset);
  
        Arrays.fill(groupBytes, (byte) 0);
        if (bs.length <= groupBytes.length) {
          groupLen = bs.length;
        } else {
          groupLen = groupBytes.length;
        }
        System.arraycopy(bs, 0, groupBytes, 0, groupLen);
  
        header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_QUERY_FILE_INFO,
          +groupBytes.length + filenameBytes.length, (byte) 0);
        OutputStream out = storageSocket.getOutputStream();
        byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
        System.arraycopy(header, 0, wholePkg, 0, header.length);
        System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
        System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
        out.write(wholePkg);
  
        pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(),
          ProtoCommon.STORAGE_PROTO_CMD_RESP,
          3 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE +
            ProtoCommon.FDFS_IPADDR_SIZE);
  
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
          return null;
        }
  
        long file_size = ProtoCommon.buff2long(pkgInfo.body, 0);
        int create_timestamp = (int) ProtoCommon.buff2long(pkgInfo.body, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
        int crc32 = (int) ProtoCommon.buff2long(pkgInfo.body, 2 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
        String source_ip_addr = (new String(pkgInfo.body, 3 * ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE, ProtoCommon.FDFS_IPADDR_SIZE)).trim();
        return new FileInfo(file_size, create_timestamp, crc32, source_ip_addr);
      } catch (IOException ex) {
        if (!bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
  
        throw ex;
      } finally {
        if (bNewConnection) {
          try {
            this.storageServer.close();
          } catch (IOException ex1) {
            ex1.printStackTrace();
          } finally {
            this.storageServer = null;
          }
        }
      }
    }
  
    /**
     * check storage socket, if null create a new connection
     *
     * @param group_name the group name to upload file to, can be empty
     * @return true if create a new connection
     */
    protected boolean newWritableStorageConnection(String group_name) throws IOException, MyException {
      if (this.storageServer != null) {
        return false;
      } else {
        TrackerClient tracker = new TrackerClient();
        this.storageServer = tracker.getStoreStorage(this.trackerServer, group_name);
        if (this.storageServer == null) {
          throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
        }
        return true;
      }
    }
  
    /**
     * check storage socket, if null create a new connection
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return true if create a new connection
     */
    protected boolean newReadableStorageConnection(String group_name, String remote_filename) throws IOException, MyException {
      if (this.storageServer != null) {
        return false;
      } else {
        TrackerClient tracker = new TrackerClient();
        this.storageServer = tracker.getFetchStorage(this.trackerServer, group_name, remote_filename);
        if (this.storageServer == null) {
          throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
        }
        return true;
      }
    }
  
    /**
     * check storage socket, if null create a new connection
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @return true if create a new connection
     */
    protected boolean newUpdatableStorageConnection(String group_name, String remote_filename) throws IOException, MyException {
      if (this.storageServer != null) {
        return false;
      } else {
        TrackerClient tracker = new TrackerClient();
        this.storageServer = tracker.getUpdateStorage(this.trackerServer, group_name, remote_filename);
        if (this.storageServer == null) {
          throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
        }
        return true;
      }
    }
  
    /**
     * send package to storage server
     *
     * @param cmd             which command to send
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     */
    protected void send_package(byte cmd, String group_name, String remote_filename) throws IOException {
      byte[] header;
      byte[] groupBytes;
      byte[] filenameBytes;
      byte[] bs;
      int groupLen;
  
      groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
      bs = group_name.getBytes(ClientGlobal.g_charset);
      filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
  
      Arrays.fill(groupBytes, (byte) 0);
      if (bs.length <= groupBytes.length) {
        groupLen = bs.length;
      } else {
        groupLen = groupBytes.length;
      }
      System.arraycopy(bs, 0, groupBytes, 0, groupLen);
  
      header = ProtoCommon.packHeader(cmd, groupBytes.length + filenameBytes.length, (byte) 0);
      byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
      System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
      this.storageServer.getSocket().getOutputStream().write(wholePkg);
    }
  
    /**
     * send package to storage server
     *
     * @param group_name      the group name of storage server
     * @param remote_filename filename on storage server
     * @param file_offset     the start offset of the file
     * @param download_bytes  download bytes
     */
    protected void send_download_package(String group_name, String remote_filename, long file_offset, long download_bytes) throws IOException {
      byte[] header;
      byte[] bsOffset;
      byte[] bsDownBytes;
      byte[] groupBytes;
      byte[] filenameBytes;
      byte[] bs;
      int groupLen;
  
      bsOffset = ProtoCommon.long2buff(file_offset);
      bsDownBytes = ProtoCommon.long2buff(download_bytes);
      groupBytes = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
      bs = group_name.getBytes(ClientGlobal.g_charset);
      filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
  
      Arrays.fill(groupBytes, (byte) 0);
      if (bs.length <= groupBytes.length) {
        groupLen = bs.length;
      } else {
        groupLen = groupBytes.length;
      }
      System.arraycopy(bs, 0, groupBytes, 0, groupLen);
  
      header = ProtoCommon.packHeader(ProtoCommon.STORAGE_PROTO_CMD_DOWNLOAD_FILE,
        bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length, (byte) 0);
      byte[] wholePkg = new byte[header.length + bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(bsOffset, 0, wholePkg, header.length, bsOffset.length);
      System.arraycopy(bsDownBytes, 0, wholePkg, header.length + bsOffset.length, bsDownBytes.length);
      System.arraycopy(groupBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length, groupBytes.length);
      System.arraycopy(filenameBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length + groupBytes.length, filenameBytes.length);
      this.storageServer.getSocket().getOutputStream().write(wholePkg);
    }
  
    /**
     * Upload file by file buff
     *
     * @author Happy Fish / YuQing
     * @version Version 1.12
     */
    public static class UploadBuff implements UploadCallback {
      private byte[] fileBuff;
      private int offset;
      private int length;
  
      /**
       * constructor
       *
       * @param fileBuff the file buff for uploading
       */
      public UploadBuff(byte[] fileBuff, int offset, int length) {
        super();
        this.fileBuff = fileBuff;
        this.offset = offset;
        this.length = length;
      }
  
      /**
       * send file content callback function, be called only once when the file uploaded
       *
       * @param out output stream for writing file content
       * @return 0 success, return none zero(errno) if fail
       */
      public int send(OutputStream out) throws IOException {
        out.write(this.fileBuff, this.offset, this.length);
  
        return 0;
      }
    }
  }
  