Procházet zdrojové kódy

新增fastdfs支持

rogetfan před 7 roky
rodič
revize
c7146be7b4
26 změnil soubory, kde provedl 7394 přidání a 2 odebrání
  1. 84 0
      src/main/java/io/renren/config/FastDFSConfig.java
  2. 472 0
      src/main/java/org/csource/common/Base64.java
  3. 190 0
      src/main/java/org/csource/common/IniFileReader.java
  4. 26 0
      src/main/java/org/csource/common/MyException.java
  5. 55 0
      src/main/java/org/csource/common/NameValuePair.java
  6. 201 0
      src/main/java/org/csource/fastdfs/ClientGlobal.java
  7. 26 0
      src/main/java/org/csource/fastdfs/DownloadCallback.java
  8. 49 0
      src/main/java/org/csource/fastdfs/DownloadStream.java
  9. 125 0
      src/main/java/org/csource/fastdfs/FileInfo.java
  10. 554 0
      src/main/java/org/csource/fastdfs/ProtoCommon.java
  11. 52 0
      src/main/java/org/csource/fastdfs/ProtoStructDecoder.java
  12. 66 0
      src/main/java/org/csource/fastdfs/ServerInfo.java
  13. 2076 0
      src/main/java/org/csource/fastdfs/StorageClient.java
  14. 799 0
      src/main/java/org/csource/fastdfs/StorageClient1.java
  15. 61 0
      src/main/java/org/csource/fastdfs/StorageServer.java
  16. 79 0
      src/main/java/org/csource/fastdfs/StructBase.java
  17. 226 0
      src/main/java/org/csource/fastdfs/StructGroupStat.java
  18. 934 0
      src/main/java/org/csource/fastdfs/StructStorageStat.java
  19. 990 0
      src/main/java/org/csource/fastdfs/TrackerClient.java
  20. 120 0
      src/main/java/org/csource/fastdfs/TrackerGroup.java
  21. 90 0
      src/main/java/org/csource/fastdfs/TrackerServer.java
  22. 27 0
      src/main/java/org/csource/fastdfs/UploadCallback.java
  23. 60 0
      src/main/java/org/csource/fastdfs/UploadStream.java
  24. 10 0
      src/main/resources/application-dev.yml
  25. 11 1
      src/main/resources/application-pro.yml
  26. 11 1
      src/main/resources/application-test.yml

+ 84 - 0
src/main/java/io/renren/config/FastDFSConfig.java

@@ -0,0 +1,84 @@
+package io.renren.config;
+
+
+import org.csource.common.MyException;
+import org.csource.fastdfs.ClientGlobal;
+import org.csource.fastdfs.TrackerClient;
+import org.csource.fastdfs.TrackerGroup;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+/**
+ * Created by Glenn on 2017/5/18 0018.
+ */
+
+@Configuration("FastDFSConfig")
+@EnableCaching//启用缓存的意思
+public class FastDFSConfig {
+    /***
+     * Example
+     @Autowired
+     private TrackerClient client;
+
+     上传至FastDFS
+     StorageClient storageClient = new StorageClient(client.getConnection(), null);
+     String[] ret = storageClient.upload_file(fileContent,type.getPureSuffix(), null);
+     下载从FastDFS
+
+     StorageClient storageClient = new StorageClient(client.getConnection(), null);
+     FileInfo fi = storageClient.get_file_info(entity.getGroupName(), entity.getFileUri());
+     if (fi == null) {
+     throw new Exception("File information from FastDFS is null");
+     }
+     byte[] fileContent = storageClient.download_file(entity.getGroupName(), entity.getFileUri());
+     if (fileContent == null) {
+     throw new Exception("File entity from FastDFS is null");
+     }
+     * */
+
+
+    @Value("${fdfs.networkTimeout:#{null}}")
+    private Integer networkTimeout;
+    @Value("${fdfs.connectTimeout:#{null}}")
+    private Integer connectTimeout;
+    @Value("${fdfs.trackerServer:#{null}}")
+    List<String> trackerServer;
+    @Value("${fdfs.charset:#{null}}")
+    private String charset;
+    @Value("${fdfs.trackerHttpPort:#{null}}")
+    private Integer trackerHttpPort;
+    @Value("${fdfs.antiStealToken:#{null}}")
+    private Boolean antiStealToken;
+    @Value("${fdfs.secretKey:#{null}}")
+    private String secretKey;
+
+    @Bean
+    public TrackerClient getTrackerClient() throws MyException {
+        String[] parts;
+        ClientGlobal.setG_anti_steal_token(antiStealToken);
+        ClientGlobal.setG_charset(charset);
+        ClientGlobal.setG_connect_timeout(connectTimeout*1000);
+        ClientGlobal.setG_network_timeout(networkTimeout*1000);
+        ClientGlobal.setG_secret_key(secretKey);
+        InetSocketAddress[] tracker_servers = new InetSocketAddress[trackerServer.size()];
+        for (int i=0; i<trackerServer.size(); i++)
+        {
+            parts =trackerServer.get(i).split("\\:", 2);
+            if (parts.length != 2)
+            {
+                throw new MyException("the value of item \"tracker_server\" is invalid, the correct format is host:port");
+            }
+
+            tracker_servers[i] = new InetSocketAddress(parts[0].trim(), Integer.parseInt(parts[1].trim()));
+        }
+        ClientGlobal.setG_tracker_group(new TrackerGroup(tracker_servers));
+        ClientGlobal.setG_tracker_http_port(trackerHttpPort);
+        TrackerClient trackerClient = new TrackerClient();
+        return trackerClient;
+    }
+}

+ 472 - 0
src/main/java/org/csource/common/Base64.java

@@ -0,0 +1,472 @@
+package org.csource.common;
+
+import java.io.IOException;
+
+/**
+ * Freeware from:
+ * Roedy Green
+ * Canadian Mind Products
+ * #327 - 964 Heywood Avenue
+ * Victoria, BC Canada V8V 2Y5
+ * tel:(250) 361-9093
+ * mailto:roedy@mindprod.com
+ */
+
+/**
+ * Encode arbitrary binary into printable ASCII using BASE64 encoding.
+ * very loosely based on the Base64 Reader by
+ * Dr. Mark Thornton
+ * Optrak Distribution Software Ltd.
+ * http://www.optrak.co.uk
+ * and Kevin Kelley's  http://www.ruralnet.net/~kelley/java/Base64.java
+ *
+ * Base64 is a way of encoding 8-bit characters using only ASCII printable
+ * characters similar to UUENCODE.  UUENCODE includes a filename where BASE64 does not.
+ * The spec is described in RFC 2045.  Base64 is a scheme where
+ * 3 bytes are concatenated, then split to form 4 groups of 6-bits each; and
+ * each 6-bits gets translated to an encoded printable ASCII character, via a
+ * table lookup.  An encoded string is therefore longer than the original by
+ * about 1/3.  The "=" character is used to pad the end.  Base64 is used,
+ * among other things, to encode the user:password string in an
+ * Authorization: header for HTTP.  Don't confuse Base64 with
+ * x-www-form-urlencoded which is handled by
+ * Java.net.URLEncoder.encode/decode
+ * If you don't like this code, there is another implementation at http://www.ruffboy.com/download.htm
+ * Sun has an undocumented method called sun.misc.Base64Encoder.encode.
+ * You could use hex, simpler to code, but not as compact.
+ *
+ * If you wanted to encode a giant file, you could do it in large chunks that
+ * are even multiples of 3 bytes, except for the last chunk, and append the outputs.
+ *
+ * To encode a string, rather than binary data java.net.URLEncoder may be better. See
+ * printable characters in the Java glossary for a discussion of the differences.
+ *
+ * version 1.4 2002 February 15  -- correct bugs with uneven line lengths,
+ *                                  allow you to configure line separator.
+ *                                  now need Base64 object and instance methods.
+ *                                  new mailing address.
+ * version 1.3 2000 September 12 -- fix problems with estimating output length in encode
+ * version 1.2 2000 September 09 -- now handles decode as well.
+ * version 1.1 1999 December 04 -- more symmetrical encoding algorithm.
+ *                                 more accurate StringBuffer allocation size.
+ * version 1.0 1999 December 03 -- posted in comp.lang.java.programmer.
+ * Futures Streams or files.
+ */
+
+public class Base64
+{
+
+   /**
+    * how we separate lines, e.g. \n, \r\n, \r etc.
+    */
+   private String lineSeparator = System.getProperty( "line.separator" );
+
+   /**
+    * max chars per line, excluding lineSeparator.  A multiple of 4.
+    */
+   private int lineLength = 72;
+
+   private char[] valueToChar = new char[64];
+
+   /**
+    * binary value encoded by a given letter of the alphabet 0..63
+    */
+   private int[] charToValue = new int[256];
+   
+   private int[] charToPad = new int[4];
+   
+   /* constructor */
+   public Base64()
+   {
+   	  this.init('+', '/', '=');
+   }
+   
+   /* constructor */
+   public Base64(char chPlus, char chSplash, char chPad, int lineLength)
+   {
+   	  this.init(chPlus, chSplash, chPad);
+   	  this.lineLength = lineLength;
+   }
+
+   public Base64(int lineLength)
+   {
+       this.lineLength = lineLength;
+   }
+
+   /* initialise defaultValueToChar and defaultCharToValue tables */
+   private void init(char chPlus, char chSplash, char chPad)
+   {
+   		int index = 0;
+      // build translate this.valueToChar table only once.
+      // 0..25 -> 'A'..'Z'
+      for ( int i='A'; i<='Z'; i++) {
+         this.valueToChar[index++] = (char)i;
+      }
+      
+      // 26..51 -> 'a'..'z'
+      for ( int i='a'; i<='z'; i++ ) {
+         this.valueToChar[index++] = (char)i;
+      }
+      
+      // 52..61 -> '0'..'9'
+      for ( int i='0'; i<='9'; i++) {
+         this.valueToChar[index++] = (char)i;
+      }
+      
+      this.valueToChar[index++] = chPlus;
+      this.valueToChar[index++] = chSplash;
+
+      // build translate defaultCharToValue table only once.
+      for ( int i=0; i<256; i++ )
+      {
+         this.charToValue[i] = IGNORE;  // default is to ignore
+      }
+
+      for ( int i=0; i<64; i++ )
+      {
+         this.charToValue[this.valueToChar[i]] = i;
+      }
+
+      this.charToValue[chPad] = PAD;
+      java.util.Arrays.fill(this.charToPad, chPad);
+   }
+   
+   /**
+    * Encode an arbitrary array of bytes as Base64 printable ASCII.
+    * It will be broken into lines of 72 chars each.  The last line is not
+    * terminated with a line separator.
+    * The output will always have an even multiple of data characters,
+    * exclusive of \n.  It is padded out with =.
+    */
+   public String encode(byte[] b) throws IOException
+      {
+      // Each group or partial group of 3 bytes becomes four chars
+      // covered quotient
+      int outputLength = ((b.length + 2) / 3) * 4;
+
+      // account for trailing newlines, on all but the very last line
+      if ( lineLength != 0 )
+         {
+          int lines =  ( outputLength + lineLength -1 ) / lineLength - 1;
+          if ( lines > 0 )
+            {
+             outputLength += lines  * lineSeparator.length();
+            }
+         }
+
+      // must be local for recursion to work.
+      StringBuffer sb = new StringBuffer( outputLength );
+
+      // must be local for recursion to work.
+      int linePos = 0;
+
+      // first deal with even multiples of 3 bytes.
+      int len = (b.length / 3) * 3;
+      int leftover = b.length - len;
+      for ( int i=0; i<len; i+=3 )
+         {
+         // Start a new line if next 4 chars won't fit on the current line
+         // We can't encapsulete the following code since the variable need to
+         // be local to this incarnation of encode.
+         linePos += 4;
+         if ( linePos > lineLength )
+            {
+            if ( lineLength != 0 )
+               {
+               sb.append(lineSeparator);
+               }
+            linePos = 4;
+            }
+
+         // get next three bytes in unsigned form lined up,
+         // in big-endian order
+         int combined = b[i+0] & 0xff;
+         combined <<= 8;
+         combined |= b[i+1] & 0xff;
+         combined <<= 8;
+         combined |= b[i+2] & 0xff;
+
+         // break those 24 bits into a 4 groups of 6 bits,
+         // working LSB to MSB.
+         int c3 = combined & 0x3f;
+         combined >>>= 6;
+         int c2 = combined & 0x3f;
+         combined >>>= 6;
+         int c1 = combined & 0x3f;
+         combined >>>= 6;
+         int c0 = combined & 0x3f;
+
+         // Translate into the equivalent alpha character
+         // emitting them in big-endian order.
+         sb.append( valueToChar[c0]);
+         sb.append( valueToChar[c1]);
+         sb.append( valueToChar[c2]);
+         sb.append( valueToChar[c3]);
+         }
+
+      // deal with leftover bytes
+      switch ( leftover )
+         {
+         case 0:
+         default:
+            // nothing to do
+            break;
+
+         case 1:
+            // One leftover byte generates xx==
+            // Start a new line if next 4 chars won't fit on the current line
+            linePos += 4;
+            if ( linePos > lineLength )
+               {
+
+               if ( lineLength != 0 )
+                  {
+                  sb.append(lineSeparator);
+                  }
+               linePos = 4;
+               }
+
+            // Handle this recursively with a faked complete triple.
+            // Throw away last two chars and replace with ==
+            sb.append(encode(new byte[] {b[len], 0, 0}
+                            ).substring(0,2));
+            sb.append("==");
+            break;
+
+         case 2:
+            // Two leftover bytes generates xxx=
+            // Start a new line if next 4 chars won't fit on the current line
+            linePos += 4;
+            if ( linePos > lineLength )
+               {
+               if ( lineLength != 0 )
+                  {
+                  sb.append(lineSeparator);
+                  }
+               linePos = 4;
+               }
+            // Handle this recursively with a faked complete triple.
+            // Throw away last char and replace with =
+            sb.append(encode(new byte[] {b[len], b[len+1], 0}
+                            ).substring(0,3));
+            sb.append("=");
+            break;
+
+         } // end switch;
+
+      if ( outputLength != sb.length() )
+         {
+         System.out.println("oops: minor program flaw: output length mis-estimated");
+         System.out.println("estimate:" + outputLength);
+         System.out.println("actual:" + sb.length());
+         }
+      return sb.toString();
+      }// end encode
+
+   /**
+    * decode a well-formed complete Base64 string back into an array of bytes.
+    * It must have an even multiple of 4 data characters (not counting \n),
+    * padded out with = as needed.
+    */
+   public byte[] decodeAuto( String s) {   	 
+   	 int nRemain = s.length() % 4;
+   	 if (nRemain == 0) {
+   	 	 return this.decode(s);
+   	 } else {
+   	   return this.decode(s + new String(this.charToPad, 0, 4 - nRemain));
+   	 }
+   }
+   
+   /**
+    * decode a well-formed complete Base64 string back into an array of bytes.
+    * It must have an even multiple of 4 data characters (not counting \n),
+    * padded out with = as needed.
+    */
+   public byte[] decode( String s)
+      {
+
+      // estimate worst case size of output array, no embedded newlines.
+      byte[] b = new byte[(s.length() / 4) * 3];
+
+      // tracks where we are in a cycle of 4 input chars.
+      int cycle = 0;
+
+      // where we combine 4 groups of 6 bits and take apart as 3 groups of 8.
+      int combined = 0;
+
+      // how many bytes we have prepared.
+      int j = 0;
+      // will be an even multiple of 4 chars, plus some embedded \n
+      int len = s.length();
+      int dummies = 0;
+      for ( int i=0; i<len; i++ )
+         {
+
+         int c = s.charAt(i);
+         int value  = (c <= 255) ? charToValue[c] : IGNORE;
+         // there are two magic values PAD (=) and IGNORE.
+         switch ( value )
+            {
+            case IGNORE:
+               // e.g. \n, just ignore it.
+               break;
+
+            case PAD:
+               value = 0;
+               dummies++;
+               // fallthrough
+            default:
+               /* regular value character */
+               switch ( cycle )
+                  {
+                  case 0:
+                     combined = value;
+                     cycle = 1;
+                     break;
+
+                  case 1:
+                     combined <<= 6;
+                     combined |= value;
+                     cycle = 2;
+                     break;
+
+                  case 2:
+                     combined <<= 6;
+                     combined |= value;
+                     cycle = 3;
+                     break;
+
+                  case 3:
+                     combined <<= 6;
+                     combined |= value;
+                     // we have just completed a cycle of 4 chars.
+                     // the four 6-bit values are in combined in big-endian order
+                     // peel them off 8 bits at a time working lsb to msb
+                     // to get our original 3 8-bit bytes back
+
+                     b[j+2] = (byte)combined;
+                     combined >>>= 8;
+                     b[j+1] = (byte)combined;
+                     combined >>>= 8;
+                     b[j] = (byte)combined;
+                     j += 3;
+                     cycle = 0;
+                     break;
+                  }
+               break;
+            }
+         } // end for
+      if ( cycle != 0 )
+         {
+         throw new ArrayIndexOutOfBoundsException ("Input to decode not an even multiple of 4 characters; pad with =.");
+         }
+      j -= dummies;
+      if ( b.length != j )
+         {
+         byte[] b2 = new byte[j];
+         System.arraycopy(b, 0, b2, 0, j);
+         b = b2;
+         }
+      return b;
+
+      }// end decode
+
+   /**
+    * determines how long the lines are that are generated by encode.
+    * Ignored by decode.
+    * @param length 0 means no newlines inserted. Must be a multiple of 4.
+    */
+   public void setLineLength(int length)
+      {
+      this.lineLength = (length/4) * 4;
+      }
+
+   /**
+    * How lines are separated.
+    * Ignored by decode.
+    * @param lineSeparator may be "" but not null.
+    * Usually contains only a combination of chars \n and \r.
+    * Could be any chars not in set A-Z a-z 0-9 + /.
+    */
+   public  void setLineSeparator(String lineSeparator)
+      {
+      this.lineSeparator = lineSeparator;
+      }
+
+   /**
+    * Marker value for chars we just ignore, e.g. \n \r high ascii
+    */
+   static final int IGNORE = -1;
+
+   /**
+    * Marker for = trailing pad
+    */
+   static final int PAD = -2;
+
+   /**
+    * used to disable test driver
+    */
+   private static final boolean debug = true;
+
+   /**
+    * debug display array
+    */
+   public static void show (byte[] b)
+   {
+      int count = 0;
+      int rows = 0;
+
+
+      for ( int i=0; i<b.length; i++ )
+      {
+         if(count == 8)
+         {
+            System.out.print("  ");
+         }
+         else if(count == 16)
+         {
+            System.out.println("");
+            count = 0;
+            continue;
+         }
+         System.out.print( Integer.toHexString(b[i] & 0xFF).toUpperCase() + " ");
+         count ++;
+
+      }
+      System.out.println();
+   }
+
+   /**
+    * debug display array
+    */
+   public static void display (byte[] b)
+      {
+      for ( int i=0; i<b.length; i++ )
+         {
+         System.out.print( (char)b[i]);
+         }
+      System.out.println();
+      }
+
+   public static void test()
+   {
+       try
+       {
+          Base64 b64 = new Base64();
+
+          //encode
+          //str64 = b64.encode(str.getBytes());
+          //System.out.println(str64);
+
+          String str64 = "CwUEFYoAAAADjQMC7ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267EI=";
+          //decode
+          byte[] theBytes = b64.decode(str64);
+          show(theBytes);
+       }
+       catch(Exception e)
+       {
+          e.printStackTrace();
+       }
+   }
+} // end Base64
+

+ 190 - 0
src/main/java/org/csource/common/IniFileReader.java

@@ -0,0 +1,190 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.common;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+/**
+* ini file reader / parser
+* @author Happy Fish / YuQing
+* @version Version 1.0
+*/
+public class IniFileReader
+{
+	private Hashtable paramTable;
+	private String conf_filename;
+	
+/**
+* @param conf_filename config filename
+*/
+	public IniFileReader(String conf_filename) throws FileNotFoundException, IOException
+	{
+		this.conf_filename = conf_filename;
+		loadFromFile(conf_filename);
+	}
+	
+/**
+* get the config filename
+* @return config filename
+*/
+	public String getConfFilename()
+	{
+		return this.conf_filename;
+	}
+	
+/**
+* get string value from config file
+* @param name item name in config file
+* @return string value
+*/
+	public String getStrValue(String name)
+	{
+		Object obj;
+		obj = this.paramTable.get(name);
+		if (obj == null)
+		{
+			return null;
+		}
+		
+		if (obj instanceof String)
+		{
+			return (String)obj;
+		}
+		
+		return (String)((ArrayList)obj).get(0);
+	}
+
+/**
+* get int value from config file
+* @param name item name in config file
+* @param default_value the default value
+* @return int value
+*/
+	public int getIntValue(String name, int default_value)
+	{
+		String szValue = this.getStrValue(name);
+		if (szValue == null)
+		{
+			return default_value;
+		}
+		
+		return Integer.parseInt(szValue);
+	}
+
+/**
+* get boolean value from config file
+* @param name item name in config file
+* @param default_value the default value
+* @return boolean value
+*/
+	public boolean getBoolValue(String name, boolean default_value)
+	{
+		String szValue = this.getStrValue(name);
+		if (szValue == null)
+		{
+			return default_value;
+		}
+		
+		return szValue.equalsIgnoreCase("yes") || szValue.equalsIgnoreCase("on") || 
+					 szValue.equalsIgnoreCase("true") || szValue.equals("1");
+	}
+	
+/**
+* get all values from config file
+* @param name item name in config file
+* @return string values (array)
+*/
+	public String[] getValues(String name)
+	{
+		Object obj;
+		String[] values;
+		
+		obj = this.paramTable.get(name);
+		if (obj == null)
+		{
+			return null;
+		}
+		
+		if (obj instanceof String)
+		{
+			values = new String[1];
+			values[0] = (String)obj;
+			return values;
+		}
+		
+		Object[] objs = ((ArrayList)obj).toArray();
+		values = new String[objs.length];
+		System.arraycopy(objs, 0, values, 0, objs.length);
+		return values;
+	}
+	
+	private void loadFromFile(String conf_filename) throws FileNotFoundException, IOException
+	{
+		FileReader fReader;
+		BufferedReader buffReader;
+		String line;
+		String[] parts;
+		String name;
+		String value;
+		Object obj;
+		ArrayList valueList;
+		
+	  fReader = new FileReader(conf_filename);
+	  buffReader = new BufferedReader(fReader);
+	  this.paramTable = new Hashtable();
+	  
+	  try
+	  {
+	  	while ((line=buffReader.readLine()) != null)
+	  	{
+	  		line = line.trim();
+	  		if (line.length() == 0 || line.charAt(0) == '#')
+	  		{
+	  			continue;
+	  		}
+	  		
+	  		parts = line.split("=", 2);
+	  		if (parts.length != 2)
+	  		{
+	  			continue;
+	  		}
+	  	
+	  		name = parts[0].trim();
+	  		value = parts[1].trim();
+	  		
+	  		obj = this.paramTable.get(name);
+	  		if (obj == null)
+	  		{
+	  			this.paramTable.put(name, value);
+	  		}
+	  		else if (obj instanceof String)
+	  		{
+	  			valueList = new ArrayList();
+	  			valueList.add(obj);
+	  			valueList.add(value);
+	  			this.paramTable.put(name, valueList);
+	  		}
+	  		else
+	  		{
+	  			valueList = (ArrayList)obj;
+	  			valueList.add(value);
+	  		}
+	  	}
+	  }
+	  finally
+	  {
+	  	fReader.close();
+	  }
+  }  
+}

+ 26 - 0
src/main/java/org/csource/common/MyException.java

@@ -0,0 +1,26 @@
+/*
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.common;
+
+/**
+* My Exception
+* @author Happy Fish / YuQing
+* @version Version 1.0
+*/
+public class MyException extends Exception
+{
+    public MyException()
+    {
+    }
+    
+    public MyException(String message)
+    {
+    		super(message);
+    }
+}

+ 55 - 0
src/main/java/org/csource/common/NameValuePair.java

@@ -0,0 +1,55 @@
+/*
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.common;
+
+/**
+* name(key) and value pair model
+* @author Happy Fish / YuQing
+* @version Version 1.0
+*/
+public class NameValuePair
+{
+    protected String name;
+    protected String value;
+
+    public NameValuePair()
+    {
+    }
+
+    public NameValuePair(String name)
+    {
+        this.name = name;
+    }
+
+    public NameValuePair(String name, String value)
+    {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName()
+    {
+        return this.name;
+    }
+
+    public String getValue()
+    {
+        return this.value;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public void setValue(String value)
+    {
+        this.value = value;
+    }
+}

+ 201 - 0
src/main/java/org/csource/fastdfs/ClientGlobal.java

@@ -0,0 +1,201 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import org.csource.common.IniFileReader;
+import org.csource.common.MyException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+* Global variables
+* @author Happy Fish / YuQing
+* @version Version 1.11
+*/
+public class ClientGlobal
+{
+	public static int g_connect_timeout; //millisecond
+	public static int g_network_timeout; //millisecond
+	public static String g_charset;
+	public static int g_tracker_http_port;
+	public static boolean g_anti_steal_token;  //if anti-steal token
+	public static String g_secret_key;   //generage token secret key
+	public static TrackerGroup g_tracker_group;
+	
+	public static final int DEFAULT_CONNECT_TIMEOUT = 5;  //second
+	public static final int DEFAULT_NETWORK_TIMEOUT = 30; //second
+	
+	private ClientGlobal()
+	{
+	}
+	
+/**
+* load global variables
+* @param conf_filename config filename
+*/
+	public static void init(String conf_filename) throws FileNotFoundException, IOException, MyException
+	{
+  		IniFileReader iniReader;
+  		String[] szTrackerServers;
+			String[] parts;
+			
+  		iniReader = new IniFileReader(conf_filename);
+
+			g_connect_timeout = iniReader.getIntValue("connect_timeout", DEFAULT_CONNECT_TIMEOUT);
+  		if (g_connect_timeout < 0)
+  		{
+  			g_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+  		}
+  		g_connect_timeout *= 1000; //millisecond
+  		
+  		g_network_timeout = iniReader.getIntValue("network_timeout", DEFAULT_NETWORK_TIMEOUT);
+  		if (g_network_timeout < 0)
+  		{
+  			g_network_timeout = DEFAULT_NETWORK_TIMEOUT;
+  		}
+  		g_network_timeout *= 1000; //millisecond
+
+  		g_charset = iniReader.getStrValue("charset");
+  		if (g_charset == null || g_charset.length() == 0)
+  		{
+  			g_charset = "ISO8859-1";
+  		}
+  		
+  		szTrackerServers = iniReader.getValues("tracker_server");
+  		if (szTrackerServers == null)
+  		{
+  			throw new MyException("item \"tracker_server\" in " + conf_filename + " not found");
+  		}
+  		
+  		InetSocketAddress[] tracker_servers = new InetSocketAddress[szTrackerServers.length];
+  		for (int i=0; i<szTrackerServers.length; i++)
+  		{
+  			parts = szTrackerServers[i].split("\\:", 2);
+  			if (parts.length != 2)
+  			{
+  				throw new MyException("the value of item \"tracker_server\" is invalid, the correct format is host:port");
+  			}
+  			
+  			tracker_servers[i] = new InetSocketAddress(parts[0].trim(), Integer.parseInt(parts[1].trim()));
+  		}
+  		g_tracker_group = new TrackerGroup(tracker_servers);
+  		
+  		g_tracker_http_port = iniReader.getIntValue("http.tracker_http_port", 80);
+  		g_anti_steal_token = iniReader.getBoolValue("http.anti_steal_token", false);
+  		if (g_anti_steal_token)
+  		{
+  			g_secret_key = iniReader.getStrValue("http.secret_key");
+  		}
+	}
+	
+/**
+* construct Socket object
+* @param ip_addr ip address or hostname
+* @param port port number
+* @return connected Socket object
+*/
+	public static Socket getSocket(String ip_addr, int port) throws IOException
+	{
+		Socket sock = new Socket();
+		sock.setSoTimeout(ClientGlobal.g_network_timeout);
+		sock.connect(new InetSocketAddress(ip_addr, port), ClientGlobal.g_connect_timeout);
+		return sock;
+	}
+	
+/**
+* construct Socket object
+* @param addr InetSocketAddress object, including ip address and port
+* @return connected Socket object
+*/
+	public static Socket getSocket(InetSocketAddress addr) throws IOException
+	{
+		Socket sock = new Socket();
+		sock.setSoTimeout(ClientGlobal.g_network_timeout);
+		sock.connect(addr, ClientGlobal.g_connect_timeout);
+		return sock;
+	}
+	
+	public static int getG_connect_timeout()
+	{
+		return g_connect_timeout;
+	}
+	
+	public static void setG_connect_timeout(int connect_timeout)
+	{
+		ClientGlobal.g_connect_timeout = connect_timeout;
+	}
+	
+	public static int getG_network_timeout()
+	{
+		return g_network_timeout;
+	}
+	
+	public static void setG_network_timeout(int network_timeout)
+	{
+		ClientGlobal.g_network_timeout = network_timeout;
+	}
+	
+	public static String getG_charset()
+	{
+		return g_charset;
+	}
+	
+	public static void setG_charset(String charset)
+	{
+		ClientGlobal.g_charset = charset;
+	}
+	
+	public static int getG_tracker_http_port()
+	{
+		return g_tracker_http_port;
+	}
+	
+	public static void setG_tracker_http_port(int tracker_http_port)
+	{
+		ClientGlobal.g_tracker_http_port = tracker_http_port;
+	}
+	
+	public static boolean getG_anti_steal_token()
+	{
+		return g_anti_steal_token;
+	}
+	
+	public static boolean isG_anti_steal_token()
+	{
+		return g_anti_steal_token;
+	}
+	
+	public static void setG_anti_steal_token(boolean anti_steal_token)
+	{
+		ClientGlobal.g_anti_steal_token = anti_steal_token;
+	}
+	
+	public static String getG_secret_key()
+	{
+		return g_secret_key;
+	}
+	
+	public static void setG_secret_key(String secret_key)
+	{
+		ClientGlobal.g_secret_key = secret_key;
+	}
+	
+	public static TrackerGroup getG_tracker_group()
+	{
+		return g_tracker_group;
+	}
+	
+	public static void setG_tracker_group(TrackerGroup tracker_group)
+	{
+		ClientGlobal.g_tracker_group = tracker_group;
+	}
+}

+ 26 - 0
src/main/java/org/csource/fastdfs/DownloadCallback.java

@@ -0,0 +1,26 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+/**
+* Download file callback interface
+* @author Happy Fish / YuQing
+* @version Version 1.4
+*/
+public interface DownloadCallback
+{
+	/**
+	* recv file content callback function, may be called more than once when the file downloaded
+	* @param file_size file size
+	*	@param data data buff
+	* @param bytes data bytes
+	* @return 0 success, return none zero(errno) if fail
+	*/
+	public int recv(long file_size, byte[] data, int bytes);
+}

+ 49 - 0
src/main/java/org/csource/fastdfs/DownloadStream.java

@@ -0,0 +1,49 @@
+package org.csource.fastdfs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+* Download file by stream (download callback class)
+* @author  zhouzezhong & Happy Fish / YuQing
+* @version Version 1.11
+*/
+public class DownloadStream implements DownloadCallback
+{
+	private OutputStream out;
+	private long currentBytes = 0;
+	
+	public DownloadStream(OutputStream out)
+	{
+		super();
+		this.out = out;
+	}
+
+	/**
+	* recv file content callback function, may be called more than once when the file downloaded
+	* @param fileSize file size
+	*	@param data data buff
+	* @param bytes data bytes
+	* @return 0 success, return none zero(errno) if fail
+	*/
+	public int recv(long fileSize, byte[] data, int bytes)
+	{
+		try
+		{
+			out.write(data, 0, bytes);
+		}
+		catch(IOException ex)
+		{
+			ex.printStackTrace(); 
+			return -1;
+		}
+		
+		currentBytes +=	bytes;
+		if (this.currentBytes == fileSize)
+		{
+			this.currentBytes = 0;
+		}
+		
+		return 0;
+	}
+}

+ 125 - 0
src/main/java/org/csource/fastdfs/FileInfo.java

@@ -0,0 +1,125 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+* Server Info
+* @author Happy Fish / YuQing
+* @version Version 1.23
+*/
+public class FileInfo
+{
+	protected String source_ip_addr;
+	protected long file_size;
+	protected Date create_timestamp;
+	protected int crc32;
+
+/**
+* Constructor
+* @param file_size the file size
+* @param create_timestamp create timestamp in seconds
+* @param crc32 the crc32 signature
+* @param source_ip_addr the source storage ip address
+*/
+	public FileInfo(long file_size, int create_timestamp, int crc32, String source_ip_addr)
+	{
+		this.file_size = file_size;
+		this.create_timestamp = new Date(create_timestamp * 1000L);
+		this.crc32 = crc32;
+		this.source_ip_addr = source_ip_addr;
+	}
+
+/**
+* set the source ip address of the file uploaded to
+* @param source_ip_addr the source ip address
+*/
+	public void setSourceIpAddr(String source_ip_addr)
+	{
+		this.source_ip_addr = source_ip_addr;
+	}
+	
+/**
+* get the source ip address of the file uploaded to
+* @return the source ip address of the file uploaded to
+*/
+	public String getSourceIpAddr()
+	{
+		return this.source_ip_addr;
+	}
+	
+/**
+* set the file size
+* @param file_size the file size
+*/
+	public void setFileSize(long file_size)
+	{
+		this.file_size = file_size;
+	}
+	
+/**
+* get the file size
+* @return the file size
+*/
+	public long getFileSize()
+	{
+		return this.file_size;
+	}
+
+/**
+* set the create timestamp of the file
+* @param create_timestamp create timestamp in seconds
+*/
+	public void setCreateTimestamp(int create_timestamp)
+	{
+		this.create_timestamp = new Date(create_timestamp * 1000L);
+	}
+	
+/**
+* get the create timestamp of the file
+* @return the create timestamp of the file
+*/
+	public Date getCreateTimestamp()
+	{
+		return this.create_timestamp;
+	}
+
+/**
+* set the create timestamp of the file
+* @param crc32 the crc32 signature
+*/
+	public void setCrc32(int crc32)
+	{
+		this.crc32 = crc32;
+	}
+	
+/**
+* get the file CRC32 signature
+* @return the file CRC32 signature
+*/
+	public long getCrc32()
+	{
+		return this.crc32;
+	}
+	
+/**
+* to string
+* @return string
+*/
+	public String toString()
+	{
+		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		return 	"source_ip_addr = " + this.source_ip_addr + ", " + 
+		        "file_size = " + this.file_size + ", " +
+		        "create_timestamp = " + df.format(this.create_timestamp) + ", " +
+		        "crc32 = " + this.crc32;
+	}
+}

+ 554 - 0
src/main/java/org/csource/fastdfs/ProtoCommon.java

@@ -0,0 +1,554 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import org.csource.common.MyException;
+import org.csource.common.NameValuePair;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.Socket;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+* protocol common functions
+* @author Happy Fish / YuQing
+* @version Version 1.18
+*/
+public class ProtoCommon
+{
+	/**
+	* receive package info
+	*/
+	public static class RecvPackageInfo
+	{
+		public byte errno;
+		public byte[] body;
+		
+		public RecvPackageInfo(byte errno, byte[] body)
+		{
+			this.errno = errno;
+			this.body = body;
+		}
+	}
+	
+	/**
+	* receive header info
+	*/
+	public static class RecvHeaderInfo
+	{
+		public byte errno;
+		public long body_len;
+		
+		public RecvHeaderInfo(byte errno, long body_len)
+		{
+			this.errno = errno;
+			this.body_len = body_len;
+		}
+	}
+	
+	public static final byte FDFS_PROTO_CMD_QUIT      = 82;
+	public static final byte TRACKER_PROTO_CMD_SERVER_LIST_GROUP     = 91;
+	public static final byte TRACKER_PROTO_CMD_SERVER_LIST_STORAGE   = 92;
+	public static final byte TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE = 93;
+
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE = 101;
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE = 102;
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE = 103;
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE = 104;
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL = 105;
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL = 106;
+	public static final byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL = 107;
+	public static final byte TRACKER_PROTO_CMD_RESP = 100;
+	public static final byte FDFS_PROTO_CMD_ACTIVE_TEST = 111;
+	public static final byte STORAGE_PROTO_CMD_UPLOAD_FILE  = 11;
+	public static final byte STORAGE_PROTO_CMD_DELETE_FILE	= 12;
+	public static final byte STORAGE_PROTO_CMD_SET_METADATA	 = 13;
+	public static final byte STORAGE_PROTO_CMD_DOWNLOAD_FILE = 14;
+	public static final byte STORAGE_PROTO_CMD_GET_METADATA	 = 15;
+	public static final byte STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE   = 21;
+	public static final byte STORAGE_PROTO_CMD_QUERY_FILE_INFO     = 22;
+	public static final byte STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE= 23;  //create appender file
+	public static final byte STORAGE_PROTO_CMD_APPEND_FILE         = 24;  //append file
+	public static final byte STORAGE_PROTO_CMD_MODIFY_FILE         = 34;  //modify appender file
+	public static final byte STORAGE_PROTO_CMD_TRUNCATE_FILE       = 36;  //truncate appender file
+
+	public static final byte STORAGE_PROTO_CMD_RESP	 = TRACKER_PROTO_CMD_RESP;
+
+	public static final byte FDFS_STORAGE_STATUS_INIT        = 0;
+	public static final byte FDFS_STORAGE_STATUS_WAIT_SYNC   = 1;
+	public static final byte FDFS_STORAGE_STATUS_SYNCING     = 2;
+	public static final byte FDFS_STORAGE_STATUS_IP_CHANGED  = 3;
+	public static final byte FDFS_STORAGE_STATUS_DELETED     = 4;
+	public static final byte FDFS_STORAGE_STATUS_OFFLINE     = 5;
+	public static final byte FDFS_STORAGE_STATUS_ONLINE      = 6;
+	public static final byte FDFS_STORAGE_STATUS_ACTIVE      = 7;
+	public static final byte FDFS_STORAGE_STATUS_NONE        = 99;
+	
+	/**
+	* for overwrite all old metadata
+	*/
+	public static final byte STORAGE_SET_METADATA_FLAG_OVERWRITE = 'O';
+	
+	/**
+	* for replace, insert when the meta item not exist, otherwise update it
+	*/
+	public static final byte STORAGE_SET_METADATA_FLAG_MERGE = 'M';
+
+	public static final int FDFS_PROTO_PKG_LEN_SIZE	= 8;
+	public static final int FDFS_PROTO_CMD_SIZE		= 1;
+	public static final int FDFS_GROUP_NAME_MAX_LEN  = 16;
+	public static final int FDFS_IPADDR_SIZE	 = 16;
+	public static final int FDFS_DOMAIN_NAME_MAX_SIZE = 128;
+	public static final int FDFS_VERSION_SIZE = 6;
+	public static final int FDFS_STORAGE_ID_MAX_SIZE = 16;
+	
+	public static final String FDFS_RECORD_SEPERATOR	= "\u0001";
+	public static final String FDFS_FIELD_SEPERATOR	  = "\u0002";
+
+	public static final int TRACKER_QUERY_STORAGE_FETCH_BODY_LEN = FDFS_GROUP_NAME_MAX_LEN
+                        + FDFS_IPADDR_SIZE - 1 + FDFS_PROTO_PKG_LEN_SIZE;
+	public static final int TRACKER_QUERY_STORAGE_STORE_BODY_LEN = FDFS_GROUP_NAME_MAX_LEN
+                        + FDFS_IPADDR_SIZE + FDFS_PROTO_PKG_LEN_SIZE;
+
+	protected static final int PROTO_HEADER_CMD_INDEX	   = FDFS_PROTO_PKG_LEN_SIZE;
+	protected static final int PROTO_HEADER_STATUS_INDEX = FDFS_PROTO_PKG_LEN_SIZE+1;
+	
+	public static final byte FDFS_FILE_EXT_NAME_MAX_LEN  = 6;
+	public static final byte FDFS_FILE_PREFIX_MAX_LEN    = 16;
+	public static final byte FDFS_FILE_PATH_LEN          = 10;
+	public static final byte FDFS_FILENAME_BASE64_LENGTH = 27;
+	public static final byte FDFS_TRUNK_FILE_INFO_LEN    = 16;
+	
+	public static final byte ERR_NO_ENOENT    = 2;
+	public static final byte ERR_NO_EIO       = 5;
+	public static final byte ERR_NO_EBUSY     = 16;
+	public static final byte ERR_NO_EINVAL    = 22;
+	public static final byte ERR_NO_ENOSPC    = 28;
+	public static final byte ECONNREFUSED     = 61;
+	public static final byte ERR_NO_EALREADY  = 114;
+	
+	public static final long INFINITE_FILE_SIZE   = 256 * 1024L * 1024 * 1024 * 1024 * 1024L;
+	public static final long APPENDER_FILE_SIZE   = INFINITE_FILE_SIZE;
+	public static final long TRUNK_FILE_MARK_SIZE = 512 * 1024L * 1024 * 1024 * 1024 * 1024L;
+	public static final long NORMAL_LOGIC_FILENAME_LENGTH = FDFS_FILE_PATH_LEN + FDFS_FILENAME_BASE64_LENGTH + FDFS_FILE_EXT_NAME_MAX_LEN + 1;
+	public static final long TRUNK_LOGIC_FILENAME_LENGTH = NORMAL_LOGIC_FILENAME_LENGTH + FDFS_TRUNK_FILE_INFO_LEN;
+	
+	private ProtoCommon()
+	{
+	}
+
+	public static String getStorageStatusCaption(byte status)
+	{
+		switch(status)
+		{
+			case FDFS_STORAGE_STATUS_INIT:
+				return "INIT";
+			case FDFS_STORAGE_STATUS_WAIT_SYNC:
+				return "WAIT_SYNC";
+			case FDFS_STORAGE_STATUS_SYNCING:
+				return "SYNCING";
+			case FDFS_STORAGE_STATUS_IP_CHANGED:
+				return "IP_CHANGED";
+			case FDFS_STORAGE_STATUS_DELETED:
+				return "DELETED";
+			case FDFS_STORAGE_STATUS_OFFLINE:
+				return "OFFLINE";
+			case FDFS_STORAGE_STATUS_ONLINE:
+				return "ONLINE";
+			case FDFS_STORAGE_STATUS_ACTIVE:
+				return "ACTIVE";
+			case FDFS_STORAGE_STATUS_NONE:
+				return "NONE";
+			default:
+				return "UNKOWN";
+	}
+}
+
+/**
+* pack header by FastDFS transfer protocol
+* @param cmd which command to send
+* @param pkg_len package body length
+* @param errno status code, should be (byte)0
+* @return packed byte buffer
+*/
+	public static byte[] packHeader(byte cmd, long pkg_len, byte errno) throws UnsupportedEncodingException
+	{
+		byte[] header;
+		byte[] hex_len;
+		
+		header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2];
+		Arrays.fill(header, (byte)0);
+		
+		hex_len = ProtoCommon.long2buff(pkg_len);
+		System.arraycopy(hex_len, 0, header, 0, hex_len.length);
+		header[PROTO_HEADER_CMD_INDEX] = cmd;
+		header[PROTO_HEADER_STATUS_INDEX] = errno;
+		return header;
+	}
+
+/**
+* receive pack header
+* @param in input stream
+* @param expect_cmd expect response command
+* @param expect_body_len expect response package body length
+* @return RecvHeaderInfo: errno and pkg body length
+*/
+	public static RecvHeaderInfo recvHeader(InputStream in, byte expect_cmd, long expect_body_len) throws IOException
+	{
+		byte[] header;
+		int bytes;
+		long pkg_len;
+		
+		header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2];
+		
+		if ((bytes=in.read(header)) != header.length)
+		{
+			throw new IOException("recv package size " + bytes + " != " + header.length);
+		}
+		
+		if (header[PROTO_HEADER_CMD_INDEX] != expect_cmd)
+		{
+			throw new IOException("recv cmd: " + header[PROTO_HEADER_CMD_INDEX] + " is not correct, expect cmd: " + expect_cmd);
+		}
+		
+		if (header[PROTO_HEADER_STATUS_INDEX] != 0)
+		{
+			return new RecvHeaderInfo(header[PROTO_HEADER_STATUS_INDEX], 0);
+		}
+		
+		pkg_len = ProtoCommon.buff2long(header, 0);
+		if (pkg_len < 0)
+		{
+			throw new IOException("recv body length: " + pkg_len + " < 0!");
+		}
+		
+		if (expect_body_len >= 0 && pkg_len != expect_body_len)
+		{
+			throw new IOException("recv body length: " + pkg_len + " is not correct, expect length: " + expect_body_len);
+		}
+		
+		return new RecvHeaderInfo((byte)0, pkg_len);
+	}
+
+/**
+* receive whole pack
+* @param in input stream
+* @param expect_cmd expect response command
+* @param expect_body_len expect response package body length
+* @return RecvPackageInfo: errno and reponse body(byte buff)
+*/
+	public static RecvPackageInfo recvPackage(InputStream in, byte expect_cmd, long expect_body_len) throws IOException
+	{
+		RecvHeaderInfo header = recvHeader(in, expect_cmd, expect_body_len);
+		if (header.errno != 0)
+		{
+			return new RecvPackageInfo(header.errno, null);
+		}
+		
+		byte[] body = new byte[(int)header.body_len];
+		int totalBytes = 0;
+		int remainBytes = (int)header.body_len;
+		int bytes;
+		
+		while (totalBytes < header.body_len)
+		{
+			if ((bytes=in.read(body, totalBytes, remainBytes)) < 0)
+			{
+				break;
+			}
+			
+			totalBytes += bytes;
+			remainBytes -= bytes;
+		}
+		
+		if (totalBytes != header.body_len)
+		{
+			throw new IOException("recv package size " + totalBytes + " != " + header.body_len);
+		}
+		
+		return new RecvPackageInfo((byte)0, body);
+	}
+
+/**
+* split metadata to name value pair array
+* @param meta_buff metadata
+* @return name value pair array
+*/
+	public static NameValuePair[] split_metadata(String meta_buff)
+	{
+		return split_metadata(meta_buff, FDFS_RECORD_SEPERATOR, FDFS_FIELD_SEPERATOR);
+	}
+
+/**
+* split metadata to name value pair array
+* @param meta_buff metadata
+* @param recordSeperator record/row seperator
+* @param filedSeperator field/column seperator
+* @return name value pair array
+*/
+	public static NameValuePair[] split_metadata(String meta_buff,
+                                                 String  recordSeperator, String  filedSeperator)
+	{
+		String[] rows;
+		String[] cols;
+		NameValuePair[] meta_list;
+	
+		rows = meta_buff.split(recordSeperator);
+		meta_list = new NameValuePair[rows.length];
+		for (int i=0; i<rows.length; i++)
+		{
+			cols = rows[i].split(filedSeperator, 2);
+			meta_list[i] = new NameValuePair(cols[0]);
+			if (cols.length == 2)
+			{
+				meta_list[i].setValue(cols[1]);
+			}
+		}
+		
+		return meta_list;
+	}
+
+/**
+* pack metadata array to string
+* @param meta_list metadata array
+* @return packed metadata
+*/
+	public static String pack_metadata(NameValuePair[] meta_list)
+	{		
+		if (meta_list.length == 0)
+		{
+			return "";
+		}
+		
+		StringBuffer sb = new StringBuffer(32 * meta_list.length);
+		sb.append(meta_list[0].getName()).append(FDFS_FIELD_SEPERATOR).append(meta_list[0].getValue());
+		for (int i=1; i<meta_list.length; i++)
+		{
+			sb.append(FDFS_RECORD_SEPERATOR);
+			sb.append(meta_list[i].getName()).append(FDFS_FIELD_SEPERATOR).append(meta_list[i].getValue());
+		}
+	
+		return sb.toString();
+	}
+
+/**
+* send quit command to server and close socket
+* @param sock the Socket object
+*/
+	public static void closeSocket(Socket sock) throws IOException
+	{
+		byte[] header;
+		header = packHeader(FDFS_PROTO_CMD_QUIT, 0, (byte)0);
+		sock.getOutputStream().write(header);
+		sock.close();
+	}
+	
+/**
+* send ACTIVE_TEST command to server, test if network is ok and the server is alive
+* @param sock the Socket object
+*/
+	public static boolean activeTest(Socket sock) throws IOException
+	{
+		byte[] header;
+		header = packHeader(FDFS_PROTO_CMD_ACTIVE_TEST, 0, (byte)0);
+		sock.getOutputStream().write(header);
+		
+		RecvHeaderInfo headerInfo = recvHeader(sock.getInputStream(), TRACKER_PROTO_CMD_RESP, 0);
+		return headerInfo.errno == 0 ? true : false;
+	}
+	
+/**
+* long convert to buff (big-endian)
+* @param n long number
+* @return 8 bytes buff
+*/
+	public static byte[] long2buff(long n)
+	{
+		byte[] bs;
+		
+		bs = new byte[8];
+		bs[0] = (byte)((n >> 56) & 0xFF);
+		bs[1] = (byte)((n >> 48) & 0xFF);
+		bs[2] = (byte)((n >> 40) & 0xFF);
+		bs[3] = (byte)((n >> 32) & 0xFF);
+		bs[4] = (byte)((n >> 24) & 0xFF);
+		bs[5] = (byte)((n >> 16) & 0xFF);
+		bs[6] = (byte)((n >> 8) & 0xFF);
+		bs[7] = (byte)(n & 0xFF);
+		
+		return bs;
+	}
+	
+/**
+* buff convert to long
+* @param bs the buffer (big-endian)
+* @param offset the start position based 0
+* @return long number
+*/
+	public static long buff2long(byte[] bs, int offset)
+	{
+		return  (((long)(bs[offset] >= 0 ? bs[offset] : 256+bs[offset])) << 56) |
+		        (((long)(bs[offset+1] >= 0 ? bs[offset+1] : 256+bs[offset+1])) << 48) | 
+		        (((long)(bs[offset+2] >= 0 ? bs[offset+2] : 256+bs[offset+2])) << 40) | 
+		        (((long)(bs[offset+3] >= 0 ? bs[offset+3] : 256+bs[offset+3])) << 32) | 
+		        (((long)(bs[offset+4] >= 0 ? bs[offset+4] : 256+bs[offset+4])) << 24) | 
+		        (((long)(bs[offset+5] >= 0 ? bs[offset+5] : 256+bs[offset+5])) << 16) | 
+		        (((long)(bs[offset+6] >= 0 ? bs[offset+6] : 256+bs[offset+6])) <<  8) |
+		         ((long)(bs[offset+7] >= 0 ? bs[offset+7] : 256+bs[offset+7]));
+	}
+
+/**
+* buff convert to int
+* @param bs the buffer (big-endian)
+* @param offset the start position based 0
+* @return int number
+*/
+	public static int buff2int(byte[] bs, int offset)
+	{
+		return  (((int)(bs[offset] >= 0 ? bs[offset] : 256+bs[offset])) << 24) | 
+		        (((int)(bs[offset+1] >= 0 ? bs[offset+1] : 256+bs[offset+1])) << 16) | 
+		        (((int)(bs[offset+2] >= 0 ? bs[offset+2] : 256+bs[offset+2])) <<  8) |
+		         ((int)(bs[offset+3] >= 0 ? bs[offset+3] : 256+bs[offset+3]));
+	}
+	
+/**
+* buff convert to ip address
+* @param bs the buffer (big-endian)
+* @param offset the start position based 0
+* @return ip address
+*/
+	public static String getIpAddress(byte[] bs, int offset)
+	{
+		if (bs[0] == 0 || bs[3] == 0) //storage server ID
+		{
+			return "";
+		}
+		
+		int n;
+		StringBuilder sbResult = new StringBuilder(16);
+		for (int i=offset; i<offset+4; i++)
+		{
+			n = (bs[i] >= 0) ? bs[i] : 256 + bs[i];
+			if (sbResult.length() > 0)
+			{
+				sbResult.append(".");
+			}
+			sbResult.append(String.valueOf(n));
+		}
+		
+		return sbResult.toString();
+	}
+	
+ /**
+* md5 function
+* @param source the input buffer
+* @return md5 string
+*/
+	public static String md5(byte[] source) throws NoSuchAlgorithmException
+	{
+  	char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',  'e', 'f'};
+    java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+    md.update(source);
+    byte tmp[] = md.digest();
+    char str[] = new char[32];
+    int k = 0;
+    for (int i = 0; i < 16; i++)
+    {
+     str[k++] = hexDigits[tmp[i] >>> 4 & 0xf];
+     str[k++] = hexDigits[tmp[i] & 0xf];
+    }
+    
+  	return new String(str);
+ }
+ 
+ /**
+* get token for file URL
+* @param remote_filename the filename return by FastDFS server
+* @param ts unix timestamp, unit: second
+* @param secret_key the secret key
+* @return token string
+*/
+ public static String getToken(String remote_filename, int ts, String secret_key) throws UnsupportedEncodingException, NoSuchAlgorithmException, MyException
+ {
+ 	byte[] bsFilename = remote_filename.getBytes(ClientGlobal.g_charset);
+ 	byte[] bsKey = secret_key.getBytes(ClientGlobal.g_charset);
+ 	byte[] bsTimestamp = (new Integer(ts)).toString().getBytes(ClientGlobal.g_charset);
+ 	
+ 	byte[] buff = new byte[bsFilename.length + bsKey.length + bsTimestamp.length];
+ 	System.arraycopy(bsFilename, 0, buff, 0, bsFilename.length);
+ 	System.arraycopy(bsKey, 0, buff, bsFilename.length, bsKey.length);
+ 	System.arraycopy(bsTimestamp, 0, buff, bsFilename.length + bsKey.length, bsTimestamp.length);
+ 	
+ 	return md5(buff);
+ }
+ 
+ /**
+* generate slave filename
+* @param master_filename the master filename to generate the slave filename
+* @param prefix_name the prefix name to generate the slave filename
+* @param ext_name the extension name of slave filename, null for same as the master extension name
+* @return slave filename string
+*/
+ public static String genSlaveFilename(String master_filename, 
+                String prefix_name, String ext_name) throws MyException
+ {
+    String true_ext_name;
+    int dotIndex;
+
+    if (master_filename.length() < 28 + FDFS_FILE_EXT_NAME_MAX_LEN)
+    {
+            throw new MyException("master filename \"" + master_filename + "\" is invalid");
+    }
+
+    dotIndex = master_filename.indexOf('.', master_filename.length() - (FDFS_FILE_EXT_NAME_MAX_LEN + 1));
+    if (ext_name != null)
+    {
+	      if (ext_name.length() == 0)
+	      {
+	              true_ext_name = "";
+	      }
+	      else if (ext_name.charAt(0) == '.')
+	      {
+	              true_ext_name = ext_name;
+	      }
+	      else
+	      {
+	              true_ext_name = "." + ext_name;
+	      }
+    }
+		else
+    {
+	      if (dotIndex < 0)
+	      {
+	              true_ext_name = "";
+	      }
+	      else
+	      {
+	              true_ext_name = master_filename.substring(dotIndex);
+	      }
+    }
+
+    if (true_ext_name.length() == 0 && prefix_name.equals("-m"))
+    {
+        throw new MyException("prefix_name \"" + prefix_name + "\" is invalid");
+    }
+
+    if (dotIndex < 0)
+    {
+        return master_filename + prefix_name + true_ext_name;
+    }
+    else
+    {
+        return master_filename.substring(0, dotIndex) + prefix_name + true_ext_name;
+    }
+	}
+}

+ 52 - 0
src/main/java/org/csource/fastdfs/ProtoStructDecoder.java

@@ -0,0 +1,52 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+
+/**
+* C struct body decoder
+* @author Happy Fish / YuQing
+* @version Version 1.17
+*/
+public class ProtoStructDecoder<T extends StructBase>
+{	
+/**
+* Constructor
+*/
+	public ProtoStructDecoder()
+	{
+	}
+	
+/**
+* decode byte buffer
+*/
+	public T[] decode(byte[] bs, Class<T> clazz, int fieldsTotalSize) throws Exception
+	{
+		if (bs.length % fieldsTotalSize != 0)
+		{
+			throw new IOException("byte array length: " + bs.length + " is invalid!");
+		}
+		
+		int count = bs.length / fieldsTotalSize;
+		int offset;
+		T[] results = (T[])Array.newInstance(clazz, count);
+		
+		offset = 0;
+		for (int i=0; i<results.length; i++)
+		{
+			results[i] = clazz.newInstance();
+			results[i].setFields(bs, offset);
+			offset += fieldsTotalSize;
+		}
+		
+		return results;
+	}
+}

+ 66 - 0
src/main/java/org/csource/fastdfs/ServerInfo.java

@@ -0,0 +1,66 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+* Server Info
+* @author Happy Fish / YuQing
+* @version Version 1.7
+*/
+public class ServerInfo
+{
+	protected String ip_addr;
+	protected int port;
+	
+/**
+* Constructor
+* @param ip_addr address of the server
+* @param port the port of the server
+*/
+	public ServerInfo(String ip_addr, int port)
+	{
+		this.ip_addr = ip_addr;
+		this.port = port;
+	}
+	
+/**
+* return the ip address
+* @return the ip address
+*/
+	public String getIpAddr()
+	{
+		return this.ip_addr;
+	}
+	
+/**
+* return the port of the server
+* @return the port of the server
+*/
+	public int getPort()
+	{
+		return this.port;
+	}
+	
+/**
+* connect to server
+* @return connected Socket object
+*/
+	public Socket connect() throws IOException
+	{
+		Socket sock = new Socket();
+		sock.setReuseAddress(true);
+		sock.setSoTimeout(ClientGlobal.g_network_timeout);
+		sock.connect(new InetSocketAddress(this.ip_addr, this.port), ClientGlobal.g_connect_timeout);
+		return sock;
+	}
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2076 - 0
src/main/java/org/csource/fastdfs/StorageClient.java


+ 799 - 0
src/main/java/org/csource/fastdfs/StorageClient1.java

@@ -0,0 +1,799 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import org.csource.common.MyException;
+import org.csource.common.NameValuePair;
+
+import java.io.IOException;
+
+/**
+* Storage client for 1 field file id: combined group name and filename
+* @author Happy Fish / YuQing
+* @version Version 1.21
+*/
+public class StorageClient1 extends StorageClient
+{
+	public static final String SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR = "/";
+	
+/**
+* constructor
+*/
+	public StorageClient1()
+	{
+		super();
+	}
+	
+/**
+* constructor
+* @param trackerServer the tracker server, can be null
+* @param storageServer the storage server, can be null
+*/
+	public StorageClient1(TrackerServer trackerServer, StorageServer storageServer)
+	{
+		super(trackerServer, storageServer);
+	}
+
+	public static byte split_file_id(String file_id, String[] results)
+	{
+		int pos = file_id.indexOf(SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR);
+		if ((pos <= 0) || (pos == file_id.length() - 1))
+		{
+			return ProtoCommon.ERR_NO_EINVAL;
+		}
+		
+		results[0] = file_id.substring(0, pos); //group name
+		results[1] = file_id.substring(pos + 1); //file name
+		return 0;
+  }
+  	
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String local_filename, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_file(local_filename, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String group_name, String local_filename, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_file(group_name, local_filename, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+	
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(byte[] file_buff, String file_ext_name,	
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_file(file_buff, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String group_name, byte[] file_buff, String file_ext_name,	
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_file(group_name, file_buff, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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 file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String group_name, long file_size, 
+	       UploadCallback callback, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_file(group_name, file_size, callback, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_appender_file1(String local_filename, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_appender_file(local_filename, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_appender_file1(String group_name, String local_filename, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_appender_file(group_name, local_filename, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+	
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_appender_file1(byte[] file_buff, String file_ext_name,	
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_appender_file(file_buff, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_appender_file1(String group_name, byte[] file_buff, String file_ext_name,	
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_appender_file(group_name, file_buff, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* 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 file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_appender_file1(String group_name, long file_size, 
+	       UploadCallback callback, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String parts[] = this.upload_appender_file(group_name, file_size, callback, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+	
+	/**
+	* upload file to storage server (by file name, slave file mode)
+	* @param master_file_id the master file id 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String master_file_id, String prefix_name, 
+	       String local_filename, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(master_file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		parts = this.upload_file(parts[0], parts[1], prefix_name, 
+		                                  local_filename,	file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+	
+	/**
+	* upload file to storage server (by file buff, slave file mode)
+	* @param master_file_id the master file id 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String master_file_id, String prefix_name, 
+	       byte[] file_buff, String file_ext_name, NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(master_file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		parts = this.upload_file(parts[0], parts[1], prefix_name, file_buff, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* upload file to storage server (by file buff, slave file mode)
+	* @param master_file_id the master file id 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  file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String master_file_id, String prefix_name, 
+	       byte[] file_buff, int offset, int length, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(master_file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		parts = this.upload_file(parts[0], parts[1], prefix_name, file_buff, 
+		             offset, length, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+	
+	/**
+	* upload file to storage server (by callback)
+	* @param master_file_id the master file id 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 file id(including group name and filename) if success, <br>
+	*         return null if fail
+	*/
+	public String upload_file1(String master_file_id, String prefix_name, long file_size, 
+	       UploadCallback callback, String file_ext_name, 
+	       NameValuePair[] meta_list) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(master_file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		parts = this.upload_file(parts[0], parts[1], prefix_name, file_size, callback, file_ext_name, meta_list);
+		if (parts != null)
+		{
+			return parts[0] + SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR + parts[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	* append file to storage server (by file name)
+	* @param appender_file_id the appender file id
+	* @param local_filename local filename to append
+	* @return 0 for success, != 0 for error (error no)
+	*/
+	public int append_file1(String appender_file_id, String local_filename) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.append_file(parts[0], parts[1], local_filename);
+	}
+	
+	/**
+	* append file to storage server (by file buff)
+	* @param appender_file_id the appender file id
+	* @param file_buff file content/buff
+	* @return 0 for success, != 0 for error (error no)
+	*/
+	public int append_file1(String appender_file_id, byte[] file_buff) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.append_file(parts[0], parts[1], file_buff);
+	}
+	
+	/**
+	* append file to storage server (by file buff)
+	* @param appender_file_id the appender file id
+	* @param file_buff file content/buffer
+	* @param offset start offset of the buffer
+	* @param length the length of the buffer to append
+	* @return 0 for success, != 0 for error (error no)
+	*/
+	public int append_file1(String appender_file_id, byte[] file_buff, int offset, int length) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.append_file(parts[0], parts[1], file_buff, offset, length);
+	}
+
+	/**
+	* append file to storage server (by callback)
+	* @param appender_file_id the appender file id
+	* @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_file1(String appender_file_id, long file_size, UploadCallback callback) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.append_file(parts[0], parts[1], file_size, callback);
+	}
+	
+	/**
+	* modify appender file to storage server (by file name)
+	* @param appender_file_id the appender file id
+	* @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_file1(String appender_file_id, 
+			long file_offset, String local_filename) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.modify_file(parts[0], parts[1], file_offset, local_filename);
+	}
+	
+	/**
+	* modify appender file to storage server (by file buff)
+	* @param appender_file_id the appender file id
+	* @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_file1(String appender_file_id, 
+			long file_offset, byte[] file_buff) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.modify_file(parts[0], parts[1], file_offset, file_buff);
+	}
+	
+	/**
+	* modify appender file to storage server (by file buff)
+	* @param appender_file_id the appender file id
+	* @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_file1(String appender_file_id, 
+	       long file_offset, byte[] file_buff, int buffer_offset, int buffer_length) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.modify_file(parts[0], parts[1], file_offset, 
+				file_buff, buffer_offset, buffer_length);
+	}
+	
+	/**
+	* modify appender file to storage server (by callback)
+	* @param appender_file_id the appender file id
+	* @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_file1(String appender_file_id, 
+	       long file_offset, long modify_size, UploadCallback callback) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.modify_file(parts[0], parts[1], file_offset, modify_size, callback);
+	}
+	
+	/**
+	* delete file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @return 0 for success, none zero for fail (error code)
+	*/
+	public int delete_file1(String file_id) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.delete_file(parts[0], parts[1]);
+	}
+
+	/**
+	* truncate appender file to size 0 from storage server
+  * @param appender_file_id the appender file id
+	* @return 0 for success, none zero for fail (error code)
+	*/
+	public int truncate_file1(String appender_file_id) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.truncate_file(parts[0], parts[1]);
+	}
+	
+	/**
+	* truncate appender file from storage server
+  * @param appender_file_id the appender file id
+	* @param truncated_file_size truncated file size
+	* @return 0 for success, none zero for fail (error code)
+	*/
+	public int truncate_file1(String appender_file_id, long truncated_file_size) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(appender_file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.truncate_file(parts[0], parts[1], truncated_file_size);
+	}
+	
+	/**
+	* download file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @return file content/buffer, return null if fail
+	*/
+	public byte[] download_file1(String file_id) throws IOException, MyException
+	{
+		final long file_offset = 0;
+		final long download_bytes = 0;
+		
+		return this.download_file1(file_id, file_offset, download_bytes);
+	}
+	
+	/**
+	* download file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @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_file1(String file_id, long file_offset, long download_bytes) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		return this.download_file(parts[0], parts[1], file_offset, download_bytes);
+	}
+
+	/**
+	* download file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @param local_filename  the filename on local
+	* @return 0 success, return none zero errno if fail
+	*/
+	public int download_file1(String file_id, String local_filename) throws IOException, MyException
+	{
+		final long file_offset = 0;
+		final long download_bytes = 0;
+		
+		return this.download_file1(file_id, file_offset, download_bytes, local_filename);
+	}
+	
+	/**
+	* download file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @param file_offset the start offset of the file
+	* @param download_bytes download bytes, 0 for remain bytes from offset
+	* @param local_filename  the filename on local
+	* @return 0 success, return none zero errno if fail
+	*/
+	public int download_file1(String file_id, long file_offset, long download_bytes, String local_filename) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.download_file(parts[0], parts[1], file_offset, download_bytes, local_filename);
+	}
+	
+	/**
+	* download file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @param callback the callback object, will call callback.recv() when data arrive
+	* @return 0 success, return none zero errno if fail
+	*/
+	public int download_file1(String file_id, DownloadCallback callback) throws IOException, MyException
+	{
+		final long file_offset = 0;
+		final long download_bytes = 0;
+		
+		return this.download_file1(file_id, file_offset, download_bytes, callback);
+	}
+	
+	/**
+	* download file from storage server
+	* @param file_id the file id(including group name and filename)
+	* @param file_offset the start offset of the file
+	* @param download_bytes download bytes, 0 for remain bytes from offset
+	* @param callback the callback object, will call callback.recv() when data arrive
+	* @return 0 success, return none zero errno if fail
+	*/
+	public int download_file1(String file_id, long file_offset, long download_bytes, DownloadCallback callback) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.download_file(parts[0], parts[1], file_offset, download_bytes, callback);
+	}
+
+	/**
+	* get all metadata items from storage server
+	* @param file_id the file id(including group name and filename)
+	* @return meta info array, return null if fail
+	*/
+	public NameValuePair[] get_metadata1(String file_id)throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		return this.get_metadata(parts[0], parts[1]);
+	}
+	
+	/**
+	* set metadata items to storage server
+	* @param file_id the file id(including group name and filename)
+	*	@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_metadata1(String file_id, NameValuePair[] meta_list, byte op_flag) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return this.errno;
+		}
+		
+		return this.set_metadata(parts[0], parts[1], meta_list, op_flag);
+	}
+	
+	/**
+	* get file info from storage server
+	* @param file_id the file id(including group name and filename)
+	* @return FileInfo object for success, return null for fail
+	*/
+	public FileInfo query_file_info1(String file_id) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		return this.query_file_info(parts[0], parts[1]);
+	}
+	
+	/**
+	* get file info decoded from filename
+	* @param file_id the file id(including group name and filename)
+	* @return FileInfo object for success, return null for fail
+	*/
+	public FileInfo get_file_info1(String file_id) throws IOException, MyException
+	{
+		String[] parts = new String[2];
+		this.errno = this.split_file_id(file_id, parts);
+		if (this.errno != 0)
+		{
+			return null;
+		}
+		
+		return this.get_file_info(parts[0], parts[1]);
+	}
+}

+ 61 - 0
src/main/java/org/csource/fastdfs/StorageServer.java

@@ -0,0 +1,61 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+/**
+* Storage Server Info
+* @author Happy Fish / YuQing
+* @version Version 1.11
+*/
+public class StorageServer extends TrackerServer
+{
+	protected int store_path_index = 0;
+	
+/**
+* Constructor
+* @param ip_addr the ip address of storage server
+* @param port the port of storage server
+* @param store_path the store path index on the storage server
+*/
+	public StorageServer(String ip_addr, int port, int store_path) throws IOException
+	{		
+		super(ClientGlobal.getSocket(ip_addr, port), new InetSocketAddress(ip_addr, port));
+		this.store_path_index = store_path;
+	}
+
+/**
+* Constructor
+* @param ip_addr the ip address of storage server
+* @param port the port of storage server
+* @param store_path the store path index on the storage server
+*/
+	public StorageServer(String ip_addr, int port, byte store_path) throws IOException
+	{
+		super(ClientGlobal.getSocket(ip_addr, port), new InetSocketAddress(ip_addr, port));
+		if (store_path < 0)
+		{
+			this.store_path_index = 256 + store_path;
+		}
+		else
+		{
+			this.store_path_index = store_path;
+		}
+	}
+	
+/**
+* @return the store path index on the storage server
+*/
+	public int getStorePathIndex()
+	{
+		return this.store_path_index;
+	}
+}

+ 79 - 0
src/main/java/org/csource/fastdfs/StructBase.java

@@ -0,0 +1,79 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+
+/**
+* C struct body decoder
+* @author Happy Fish / YuQing
+* @version Version 1.17
+*/
+public abstract class StructBase
+{
+	protected static class FieldInfo
+	{
+		protected String name;
+		protected int offset;
+		protected int size;
+		
+		public FieldInfo(String name, int offset, int size)
+		{
+			this.name = name;
+			this.offset = offset;
+			this.size = size;
+		}
+	}
+	
+/**
+* set fields
+* @param bs byte array
+* @param offset start offset
+*/
+	public abstract void setFields(byte[] bs, int offset);
+	
+	protected String stringValue(byte[] bs, int offset, FieldInfo filedInfo)
+	{
+		try
+		{
+			return (new String(bs, offset + filedInfo.offset, filedInfo.size, ClientGlobal.g_charset)).trim();
+		}
+		catch(UnsupportedEncodingException ex)
+		{
+			ex.printStackTrace();
+			return null;
+		}
+	}
+	
+	protected long longValue(byte[] bs, int offset, FieldInfo filedInfo)
+	{
+		return ProtoCommon.buff2long(bs, offset + filedInfo.offset);
+	}
+	
+	protected int intValue(byte[] bs, int offset, FieldInfo filedInfo)
+	{
+		return (int) ProtoCommon.buff2long(bs, offset + filedInfo.offset);
+	}
+	
+	protected byte byteValue(byte[] bs, int offset, FieldInfo filedInfo)
+	{
+		return bs[offset + filedInfo.offset];
+	}
+
+	protected boolean booleanValue(byte[] bs, int offset, FieldInfo filedInfo)
+	{
+		return bs[offset + filedInfo.offset] != 0;
+	}
+	
+	protected Date dateValue(byte[] bs, int offset, FieldInfo filedInfo)
+	{
+		return new Date(ProtoCommon.buff2long(bs, offset + filedInfo.offset) * 1000);
+	}
+}

+ 226 - 0
src/main/java/org/csource/fastdfs/StructGroupStat.java

@@ -0,0 +1,226 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+/**
+* C struct body decoder
+* @author Happy Fish / YuQing
+* @version Version 1.18
+*/
+public class StructGroupStat extends StructBase
+{
+	protected static final int FIELD_INDEX_GROUP_NAME            = 0;
+	protected static final int FIELD_INDEX_TOTAL_MB              = 1;
+	protected static final int FIELD_INDEX_FREE_MB               = 2;
+	protected static final int FIELD_INDEX_TRUNK_FREE_MB         = 3;
+	protected static final int FIELD_INDEX_STORAGE_COUNT         = 4;
+	protected static final int FIELD_INDEX_STORAGE_PORT          = 5;
+	protected static final int FIELD_INDEX_STORAGE_HTTP_PORT     = 6;
+	protected static final int FIELD_INDEX_ACTIVE_COUNT          = 7;
+	protected static final int FIELD_INDEX_CURRENT_WRITE_SERVER  = 8;
+	protected static final int FIELD_INDEX_STORE_PATH_COUNT      = 9;
+	protected static final int FIELD_INDEX_SUBDIR_COUNT_PER_PATH = 10;
+	protected static final int FIELD_INDEX_CURRENT_TRUNK_FILE_ID = 11;
+	
+	protected static int fieldsTotalSize;
+	protected static StructBase.FieldInfo[] fieldsArray = new StructBase.FieldInfo[12];
+	
+	static
+	{
+		int offset = 0;
+		fieldsArray[FIELD_INDEX_GROUP_NAME] = new StructBase.FieldInfo("groupName", offset, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1);
+		offset += ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_MB] = new StructBase.FieldInfo("totalMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_FREE_MB] = new StructBase.FieldInfo("freeMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TRUNK_FREE_MB] = new StructBase.FieldInfo("trunkFreeMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_STORAGE_COUNT] = new StructBase.FieldInfo("storageCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_STORAGE_PORT] = new StructBase.FieldInfo("storagePort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT] = new StructBase.FieldInfo("storageHttpPort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_ACTIVE_COUNT] = new StructBase.FieldInfo("activeCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_CURRENT_WRITE_SERVER] = new StructBase.FieldInfo("currentWriteServer", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_STORE_PATH_COUNT] = new StructBase.FieldInfo("storePathCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH] = new StructBase.FieldInfo("subdirCountPerPath", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_CURRENT_TRUNK_FILE_ID] = new StructBase.FieldInfo("currentTrunkFileId", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsTotalSize = offset;
+	}
+	
+	protected String groupName;  //name of this group
+	protected long totalMB;      //total disk storage in MB
+	protected long freeMB;       //free disk space in MB
+	protected long trunkFreeMB;  //trunk free space in MB
+	protected int storageCount;  //storage server count
+  protected int storagePort;   //storage server port
+  protected int storageHttpPort; //storage server HTTP port
+  protected int activeCount;     //active storage server count
+  protected int currentWriteServer; //current storage server index to upload file
+  protected int storePathCount;     //store base path count of each storage server
+  protected int subdirCountPerPath; //sub dir count per store path
+	protected int currentTrunkFileId; //current trunk file id
+	
+/**
+* get group name
+* @return group name
+*/
+  public String getGroupName()
+  {
+  	return this.groupName;
+  }
+
+/**
+* get total disk space in MB
+* @return total disk space in MB
+*/
+  public long getTotalMB()
+  {
+  	return this.totalMB;
+  }
+  
+/**
+* get free disk space in MB
+* @return free disk space in MB
+*/
+  public long getFreeMB()
+  {
+  	return this.freeMB;
+  }
+
+/**
+* get trunk free space in MB
+* @return trunk free space in MB
+*/
+  public long getTrunkFreeMB()
+  {
+  	return this.trunkFreeMB;
+  }
+
+/**
+* get storage server count in this group
+* @return storage server count in this group
+*/
+  public int getStorageCount()
+  {
+  	return this.storageCount;
+  }
+  
+/**
+* get active storage server count in this group
+* @return active storage server count in this group
+*/
+  public int getActiveCount()
+  {
+  	return this.activeCount;
+  }
+  
+/**
+* get storage server port
+* @return storage server port
+*/
+  public int getStoragePort()
+  {
+  	return this.storagePort;
+  }
+  
+/**
+* get storage server HTTP port
+* @return storage server HTTP port
+*/
+  public int getStorageHttpPort()
+  {
+  	return this.storageHttpPort;
+  }
+
+/**
+* get current storage server index to upload file
+* @return current storage server index to upload file
+*/
+  public int getCurrentWriteServer()
+  {
+  	return this.currentWriteServer;
+  }
+  
+/**
+* get store base path count of each storage server
+* @return store base path count of each storage server
+*/
+  public int getStorePathCount()
+  {
+  	return this.storePathCount;
+  }
+  
+/**
+* get sub dir count per store path
+* @return sub dir count per store path
+*/
+  public int getSubdirCountPerPath()
+  {
+  	return this.subdirCountPerPath;
+  }
+  
+/**
+* get current trunk file id
+* @return current trunk file id
+*/
+  public int getCurrentTrunkFileId()
+  {
+  	return this.currentTrunkFileId;
+  }
+  
+/**
+* set fields
+* @param bs byte array
+* @param offset start offset
+*/
+	public void setFields(byte[] bs, int offset)
+	{
+		this.groupName = stringValue(bs, offset, fieldsArray[FIELD_INDEX_GROUP_NAME]);
+		this.totalMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MB]);
+		this.freeMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_FREE_MB]);
+		this.trunkFreeMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_TRUNK_FREE_MB]);
+		this.storageCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_COUNT]);
+	  this.storagePort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_PORT]);
+	  this.storageHttpPort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT]);
+	  this.activeCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_ACTIVE_COUNT]);
+	  this.currentWriteServer = intValue(bs, offset, fieldsArray[FIELD_INDEX_CURRENT_WRITE_SERVER]);
+	  this.storePathCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORE_PATH_COUNT]);
+	  this.subdirCountPerPath = intValue(bs, offset, fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH]);
+	  this.currentTrunkFileId = intValue(bs, offset, fieldsArray[FIELD_INDEX_CURRENT_TRUNK_FILE_ID]);
+	}
+
+/**
+* get fields total size
+* @return fields total size
+*/
+	public static int getFieldsTotalSize()
+	{
+		return fieldsTotalSize;
+	}
+}

+ 934 - 0
src/main/java/org/csource/fastdfs/StructStorageStat.java

@@ -0,0 +1,934 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.util.Date;
+
+/**
+* C struct body decoder
+* @author Happy Fish / YuQing
+* @version Version 1.20
+*/
+public class StructStorageStat extends StructBase
+{
+	protected static final int FIELD_INDEX_STATUS                   = 0;
+	protected static final int FIELD_INDEX_ID                       = 1;
+	protected static final int FIELD_INDEX_IP_ADDR                  = 2;
+	protected static final int FIELD_INDEX_DOMAIN_NAME              = 3;
+	protected static final int FIELD_INDEX_SRC_IP_ADDR              = 4;
+	protected static final int FIELD_INDEX_VERSION                  = 5;
+	protected static final int FIELD_INDEX_JOIN_TIME                = 6;
+	protected static final int FIELD_INDEX_UP_TIME                  = 7;
+	protected static final int FIELD_INDEX_TOTAL_MB                 = 8;
+	protected static final int FIELD_INDEX_FREE_MB                  = 9;
+	protected static final int FIELD_INDEX_UPLOAD_PRIORITY          = 10;
+	protected static final int FIELD_INDEX_STORE_PATH_COUNT         = 11;
+	protected static final int FIELD_INDEX_SUBDIR_COUNT_PER_PATH    = 12;
+	protected static final int FIELD_INDEX_CURRENT_WRITE_PATH       = 13;
+	protected static final int FIELD_INDEX_STORAGE_PORT             = 14;
+	protected static final int FIELD_INDEX_STORAGE_HTTP_PORT        = 15;
+	protected static final int FIELD_INDEX_TOTAL_UPLOAD_COUNT       = 16;
+	protected static final int FIELD_INDEX_SUCCESS_UPLOAD_COUNT     = 17;
+	protected static final int FIELD_INDEX_TOTAL_APPEND_COUNT       = 18;
+	protected static final int FIELD_INDEX_SUCCESS_APPEND_COUNT     = 19;
+	protected static final int FIELD_INDEX_TOTAL_MODIFY_COUNT       = 20;
+	protected static final int FIELD_INDEX_SUCCESS_MODIFY_COUNT     = 21;
+	protected static final int FIELD_INDEX_TOTAL_TRUNCATE_COUNT     = 22;
+	protected static final int FIELD_INDEX_SUCCESS_TRUNCATE_COUNT   = 23;
+	protected static final int FIELD_INDEX_TOTAL_SET_META_COUNT     = 24;
+	protected static final int FIELD_INDEX_SUCCESS_SET_META_COUNT   = 25;
+	protected static final int FIELD_INDEX_TOTAL_DELETE_COUNT       = 26;
+	protected static final int FIELD_INDEX_SUCCESS_DELETE_COUNT     = 27;
+	protected static final int FIELD_INDEX_TOTAL_DOWNLOAD_COUNT     = 28;
+	protected static final int FIELD_INDEX_SUCCESS_DOWNLOAD_COUNT   = 29;
+	protected static final int FIELD_INDEX_TOTAL_GET_META_COUNT     = 30;
+	protected static final int FIELD_INDEX_SUCCESS_GET_META_COUNT   = 31;
+	protected static final int FIELD_INDEX_TOTAL_CREATE_LINK_COUNT  = 32;
+	protected static final int FIELD_INDEX_SUCCESS_CREATE_LINK_COUNT= 33;
+	protected static final int FIELD_INDEX_TOTAL_DELETE_LINK_COUNT  = 34;
+	protected static final int FIELD_INDEX_SUCCESS_DELETE_LINK_COUNT= 35;	
+	protected static final int FIELD_INDEX_TOTAL_UPLOAD_BYTES       = 36;
+	protected static final int FIELD_INDEX_SUCCESS_UPLOAD_BYTES     = 37;
+	protected static final int FIELD_INDEX_TOTAL_APPEND_BYTES       = 38;
+	protected static final int FIELD_INDEX_SUCCESS_APPEND_BYTES     = 39;
+	protected static final int FIELD_INDEX_TOTAL_MODIFY_BYTES       = 40;
+	protected static final int FIELD_INDEX_SUCCESS_MODIFY_BYTES     = 41;
+	protected static final int FIELD_INDEX_TOTAL_DOWNLOAD_BYTES     = 42;
+	protected static final int FIELD_INDEX_SUCCESS_DOWNLOAD_BYTES   = 43;
+	protected static final int FIELD_INDEX_TOTAL_SYNC_IN_BYTES      = 44;
+	protected static final int FIELD_INDEX_SUCCESS_SYNC_IN_BYTES    = 45;
+	protected static final int FIELD_INDEX_TOTAL_SYNC_OUT_BYTES     = 46;
+	protected static final int FIELD_INDEX_SUCCESS_SYNC_OUT_BYTES   = 47;
+	protected static final int FIELD_INDEX_TOTAL_FILE_OPEN_COUNT    = 48;
+	protected static final int FIELD_INDEX_SUCCESS_FILE_OPEN_COUNT  = 49;
+	protected static final int FIELD_INDEX_TOTAL_FILE_READ_COUNT    = 50;
+	protected static final int FIELD_INDEX_SUCCESS_FILE_READ_COUNT  = 51;
+	protected static final int FIELD_INDEX_TOTAL_FILE_WRITE_COUNT   = 52;
+	protected static final int FIELD_INDEX_SUCCESS_FILE_WRITE_COUNT = 53;
+	protected static final int FIELD_INDEX_LAST_SOURCE_UPDATE       = 54;
+	protected static final int FIELD_INDEX_LAST_SYNC_UPDATE         = 55;
+	protected static final int FIELD_INDEX_LAST_SYNCED_TIMESTAMP    = 56;
+	protected static final int FIELD_INDEX_LAST_HEART_BEAT_TIME     = 57;
+	protected static final int FIELD_INDEX_IF_TRUNK_FILE            = 58;
+	
+	protected static int fieldsTotalSize;
+	protected static StructBase.FieldInfo[] fieldsArray = new StructBase.FieldInfo[59];
+	
+	static
+	{
+		int offset = 0;
+		
+		fieldsArray[FIELD_INDEX_STATUS] = new StructBase.FieldInfo("status", offset, 1);
+		offset += 1;
+		
+		fieldsArray[FIELD_INDEX_ID] = new StructBase.FieldInfo("id", offset, ProtoCommon.FDFS_STORAGE_ID_MAX_SIZE);
+		offset += ProtoCommon.FDFS_STORAGE_ID_MAX_SIZE;
+		
+		fieldsArray[FIELD_INDEX_IP_ADDR] = new StructBase.FieldInfo("ipAddr", offset, ProtoCommon.FDFS_IPADDR_SIZE);
+		offset += ProtoCommon.FDFS_IPADDR_SIZE;
+		
+		fieldsArray[FIELD_INDEX_DOMAIN_NAME] = new StructBase.FieldInfo("domainName", offset, ProtoCommon.FDFS_DOMAIN_NAME_MAX_SIZE);
+		offset += ProtoCommon.FDFS_DOMAIN_NAME_MAX_SIZE;
+
+		fieldsArray[FIELD_INDEX_SRC_IP_ADDR] = new StructBase.FieldInfo("srcIpAddr", offset, ProtoCommon.FDFS_IPADDR_SIZE);
+		offset += ProtoCommon.FDFS_IPADDR_SIZE;
+				
+		fieldsArray[FIELD_INDEX_VERSION] = new StructBase.FieldInfo("version", offset, ProtoCommon.FDFS_VERSION_SIZE);
+		offset += ProtoCommon.FDFS_VERSION_SIZE;
+		
+		fieldsArray[FIELD_INDEX_JOIN_TIME] = new StructBase.FieldInfo("joinTime", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_UP_TIME] = new StructBase.FieldInfo("upTime", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_TOTAL_MB] = new StructBase.FieldInfo("totalMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_FREE_MB] = new StructBase.FieldInfo("freeMB", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_UPLOAD_PRIORITY] = new StructBase.FieldInfo("uploadPriority", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_STORE_PATH_COUNT] = new StructBase.FieldInfo("storePathCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH] = new StructBase.FieldInfo("subdirCountPerPath", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_CURRENT_WRITE_PATH] = new StructBase.FieldInfo("currentWritePath", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_STORAGE_PORT] = new StructBase.FieldInfo("storagePort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT] = new StructBase.FieldInfo("storageHttpPort", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_COUNT] = new StructBase.FieldInfo("totalUploadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_COUNT] = new StructBase.FieldInfo("successUploadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_APPEND_COUNT] = new StructBase.FieldInfo("totalAppendCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_SUCCESS_APPEND_COUNT] = new StructBase.FieldInfo("successAppendCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_TOTAL_MODIFY_COUNT] = new StructBase.FieldInfo("totalModifyCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_COUNT] = new StructBase.FieldInfo("successModifyCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_TOTAL_TRUNCATE_COUNT] = new StructBase.FieldInfo("totalTruncateCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_SUCCESS_TRUNCATE_COUNT] = new StructBase.FieldInfo("successTruncateCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_SET_META_COUNT] = new StructBase.FieldInfo("totalSetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_SET_META_COUNT] = new StructBase.FieldInfo("successSetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_DELETE_COUNT] = new StructBase.FieldInfo("totalDeleteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_DELETE_COUNT] = new StructBase.FieldInfo("successDeleteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_COUNT] = new StructBase.FieldInfo("totalDownloadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_COUNT] = new StructBase.FieldInfo("successDownloadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_GET_META_COUNT] = new StructBase.FieldInfo("totalGetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_GET_META_COUNT] = new StructBase.FieldInfo("successGetMetaCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_CREATE_LINK_COUNT] = new StructBase.FieldInfo("totalCreateLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_CREATE_LINK_COUNT] = new StructBase.FieldInfo("successCreateLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_DELETE_LINK_COUNT] = new StructBase.FieldInfo("totalDeleteLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_DELETE_LINK_COUNT] = new StructBase.FieldInfo("successDeleteLinkCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+				
+		fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_BYTES] = new StructBase.FieldInfo("totalUploadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_BYTES] = new StructBase.FieldInfo("successUploadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_APPEND_BYTES] = new StructBase.FieldInfo("totalAppendBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_APPEND_BYTES] = new StructBase.FieldInfo("successAppendBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_MODIFY_BYTES] = new StructBase.FieldInfo("totalModifyBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_BYTES] = new StructBase.FieldInfo("successModifyBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_BYTES] = new StructBase.FieldInfo("totalDownloadloadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_BYTES] = new StructBase.FieldInfo("successDownloadloadBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_TOTAL_SYNC_IN_BYTES] = new StructBase.FieldInfo("totalSyncInBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_SYNC_IN_BYTES] = new StructBase.FieldInfo("successSyncInBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_TOTAL_SYNC_OUT_BYTES] = new StructBase.FieldInfo("totalSyncOutBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_SYNC_OUT_BYTES] = new StructBase.FieldInfo("successSyncOutBytes", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_FILE_OPEN_COUNT] = new StructBase.FieldInfo("totalFileOpenCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_FILE_OPEN_COUNT] = new StructBase.FieldInfo("successFileOpenCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+
+		fieldsArray[FIELD_INDEX_TOTAL_FILE_READ_COUNT] = new StructBase.FieldInfo("totalFileReadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_FILE_READ_COUNT] = new StructBase.FieldInfo("successFileReadCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_TOTAL_FILE_WRITE_COUNT] = new StructBase.FieldInfo("totalFileWriteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_SUCCESS_FILE_WRITE_COUNT] = new StructBase.FieldInfo("successFileWriteCount", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+        
+		fieldsArray[FIELD_INDEX_LAST_SOURCE_UPDATE] = new StructBase.FieldInfo("lastSourceUpdate", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_LAST_SYNC_UPDATE] = new StructBase.FieldInfo("lastSyncUpdate", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_LAST_SYNCED_TIMESTAMP] = new StructBase.FieldInfo("lastSyncedTimestamp", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_LAST_HEART_BEAT_TIME] = new StructBase.FieldInfo("lastHeartBeatTime", offset, ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE);
+		offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
+		
+		fieldsArray[FIELD_INDEX_IF_TRUNK_FILE] = new StructBase.FieldInfo("ifTrunkServer", offset, 1);
+		offset += 1;
+		
+		fieldsTotalSize = offset;
+	}
+	
+	protected byte status;
+	protected String id;
+	protected String ipAddr;
+	protected String srcIpAddr;
+	protected String domainName; //http domain name
+	protected String version;
+	protected long totalMB; //total disk storage in MB
+	protected long freeMB;  //free disk storage in MB
+	protected int uploadPriority;  //upload priority
+	protected Date joinTime; //storage join timestamp (create timestamp)
+	protected Date upTime;   //storage service started timestamp
+	protected int storePathCount;  //store base path count of each storage server
+	protected int subdirCountPerPath;
+	protected int storagePort;
+	protected int storageHttpPort; //storage http server port
+	protected int currentWritePath; //current write path index
+	protected long totalUploadCount;
+	protected long successUploadCount;
+	protected long totalAppendCount;
+	protected long successAppendCount;
+	protected long totalModifyCount;
+	protected long successModifyCount;
+	protected long totalTruncateCount;
+	protected long successTruncateCount;
+	protected long totalSetMetaCount;
+	protected long successSetMetaCount;
+	protected long totalDeleteCount;
+	protected long successDeleteCount;
+	protected long totalDownloadCount;
+	protected long successDownloadCount;
+	protected long totalGetMetaCount;
+	protected long successGetMetaCount;
+	protected long totalCreateLinkCount;
+	protected long successCreateLinkCount;
+	protected long totalDeleteLinkCount;
+	protected long successDeleteLinkCount;
+	protected long totalUploadBytes;
+	protected long successUploadBytes;
+	protected long totalAppendBytes;
+	protected long successAppendBytes;
+	protected long totalModifyBytes;
+	protected long successModifyBytes;
+	protected long totalDownloadloadBytes;
+	protected long successDownloadloadBytes;
+	protected long totalSyncInBytes;
+	protected long successSyncInBytes;
+	protected long totalSyncOutBytes;
+	protected long successSyncOutBytes;
+	protected long totalFileOpenCount;
+	protected long successFileOpenCount;
+	protected long totalFileReadCount;
+	protected long successFileReadCount;
+	protected long totalFileWriteCount;
+	protected long successFileWriteCount;
+	protected Date lastSourceUpdate;
+	protected Date lastSyncUpdate;
+	protected Date lastSyncedTimestamp;
+	protected Date lastHeartBeatTime;
+  protected boolean ifTrunkServer;
+  
+/**
+* get storage status
+* @return storage status
+*/
+  public byte getStatus()
+  {
+  	return this.status;
+  }
+  
+/**
+* get storage server id
+* @return storage server id
+*/
+  public String getId()
+  {
+  	return this.id;
+  }
+
+/**
+* get storage server ip address
+* @return storage server ip address
+*/
+  public String getIpAddr()
+  {
+  	return this.ipAddr;
+  }
+  
+/**
+* get source storage ip address
+* @return source storage ip address
+*/
+  public String getSrcIpAddr()
+  {
+  	return this.srcIpAddr;
+  }
+  
+/**
+* get the domain name of the storage server
+* @return the domain name of the storage server
+*/
+  public String getDomainName()
+  {
+  	return this.domainName;
+  }
+
+/**
+* get storage version
+* @return storage version
+*/
+  public String getVersion()
+  {
+  	return this.version;
+  }
+  
+/**
+* get total disk space in MB
+* @return total disk space in MB
+*/
+  public long getTotalMB()
+  {
+  	return this.totalMB;
+  }
+  
+/**
+* get free disk space in MB
+* @return free disk space in MB
+*/
+  public long getFreeMB()
+  {
+  	return this.freeMB;
+  }
+	
+/**
+* get storage server upload priority
+* @return storage server upload priority
+*/
+  public int getUploadPriority()
+  {
+  	return this.uploadPriority;
+  }
+  
+/**
+* get storage server join time
+* @return storage server join time
+*/
+  public Date getJoinTime()
+  {
+  	return this.joinTime;
+  }
+  
+/**
+* get storage server up time
+* @return storage server up time
+*/
+  public Date getUpTime()
+  {
+  	return this.upTime;
+  }
+
+/**
+* get store base path count of each storage server
+* @return store base path count of each storage server
+*/
+  public int getStorePathCount()
+  {
+  	return this.storePathCount;
+  }
+  
+/**
+* get sub dir count per store path
+* @return sub dir count per store path
+*/
+  public int getSubdirCountPerPath()
+  {
+  	return this.subdirCountPerPath;
+  }
+  
+/**
+* get storage server port
+* @return storage server port
+*/
+  public int getStoragePort()
+  {
+  	return this.storagePort;
+  }
+  
+/**
+* get storage server HTTP port
+* @return storage server HTTP port
+*/
+  public int getStorageHttpPort()
+  {
+  	return this.storageHttpPort;
+  }
+
+/**
+* get current write path index
+* @return current write path index
+*/
+  public int getCurrentWritePath()
+  {
+  	return this.currentWritePath;
+  }
+
+/**
+* get total upload file count
+* @return total upload file count
+*/
+  public long getTotalUploadCount()
+  {
+  	return this.totalUploadCount;
+  }
+
+/**
+* get success upload file count
+* @return success upload file count
+*/
+  public long getSuccessUploadCount()
+  {
+  	return this.successUploadCount;
+  }
+
+/**
+* get total append count
+* @return total append count
+*/
+  public long getTotalAppendCount()
+  {
+  	return this.totalAppendCount;
+  }
+
+/**
+* get success append count
+* @return success append count
+*/
+  public long getSuccessAppendCount()
+  {
+  	return this.successAppendCount;
+  }
+  
+/**
+* get total modify count
+* @return total modify count
+*/
+  public long getTotalModifyCount()
+  {
+  	return this.totalModifyCount;
+  }
+
+/**
+* get success modify count
+* @return success modify count
+*/
+  public long getSuccessModifyCount()
+  {
+  	return this.successModifyCount;
+  }
+  
+/**
+* get total truncate count
+* @return total truncate count
+*/
+  public long getTotalTruncateCount()
+  {
+  	return this.totalTruncateCount;
+  }
+
+/**
+* get success truncate count
+* @return success truncate count
+*/
+  public long getSuccessTruncateCount()
+  {
+  	return this.successTruncateCount;
+  }
+  
+/**
+* get total set meta data count
+* @return total set meta data count
+*/
+  public long getTotalSetMetaCount()
+  {
+  	return this.totalSetMetaCount;
+  }
+  
+/**
+* get success set meta data count
+* @return success set meta data count
+*/
+  public long getSuccessSetMetaCount()
+  {
+  	return this.successSetMetaCount;
+  }
+  
+/**
+* get total delete file count
+* @return total delete file count
+*/
+  public long getTotalDeleteCount()
+  {
+  	return this.totalDeleteCount;
+  }
+
+/**
+* get success delete file count
+* @return success delete file count
+*/
+  public long getSuccessDeleteCount()
+  {
+  	return this.successDeleteCount;
+  }
+  
+/**
+* get total download file count
+* @return total download file count
+*/
+  public long getTotalDownloadCount()
+  {
+  	return this.totalDownloadCount;
+  }
+
+/**
+* get success download file count
+* @return success download file count
+*/
+  public long getSuccessDownloadCount()
+  {
+  	return this.successDownloadCount;
+  }
+
+/**
+* get total get metadata count
+* @return total get metadata count
+*/
+  public long getTotalGetMetaCount()
+  {
+  	return this.totalGetMetaCount;
+  }
+
+/**
+* get success get metadata count
+* @return success get metadata count
+*/
+  public long getSuccessGetMetaCount()
+  {
+  	return this.successGetMetaCount;
+  }
+
+/**
+* get total create linke count
+* @return total create linke count
+*/
+  public long getTotalCreateLinkCount()
+  {
+  	return this.totalCreateLinkCount;
+  }
+
+/**
+* get success create linke count
+* @return success create linke count
+*/
+  public long getSuccessCreateLinkCount()
+  {
+  	return this.successCreateLinkCount;
+  }
+  
+/**
+* get total delete link count
+* @return total delete link count
+*/
+  public long getTotalDeleteLinkCount()
+  {
+  	return this.totalDeleteLinkCount;
+  }
+  
+/**
+* get success delete link count
+* @return success delete link count
+*/
+  public long getSuccessDeleteLinkCount()
+  {
+  	return this.successDeleteLinkCount;
+  }
+
+/**
+* get total upload file bytes
+* @return total upload file bytes
+*/
+  public long getTotalUploadBytes()
+  {
+  	return this.totalUploadBytes;
+  }
+  
+/**
+* get success upload file bytes
+* @return success upload file bytes
+*/
+  public long getSuccessUploadBytes()
+  {
+  	return this.successUploadBytes;
+  }
+
+/**
+* get total append bytes
+* @return total append bytes
+*/
+  public long getTotalAppendBytes()
+  {
+  	return this.totalAppendBytes;
+  }
+  
+/**
+* get success append bytes
+* @return success append bytes
+*/
+  public long getSuccessAppendBytes()
+  {
+  	return this.successAppendBytes;
+  }
+
+/**
+* get total modify bytes
+* @return total modify bytes
+*/
+  public long getTotalModifyBytes()
+  {
+  	return this.totalModifyBytes;
+  }
+  
+/**
+* get success modify bytes
+* @return success modify bytes
+*/
+  public long getSuccessModifyBytes()
+  {
+  	return this.successModifyBytes;
+  }
+  
+/**
+* get total download file bytes
+* @return total download file bytes
+*/
+  public long getTotalDownloadloadBytes()
+  {
+  	return this.totalDownloadloadBytes;
+  }
+  
+/**
+* get success download file bytes
+* @return success download file bytes
+*/
+  public long getSuccessDownloadloadBytes()
+  {
+  	return this.successDownloadloadBytes;
+  }
+
+/**
+* get total sync in bytes
+* @return total sync in bytes
+*/
+  public long getTotalSyncInBytes()
+  {
+  	return this.totalSyncInBytes;
+  }
+  
+/**
+* get success sync in bytes
+* @return success sync in bytes
+*/
+  public long getSuccessSyncInBytes()
+  {
+  	return this.successSyncInBytes;
+  }
+
+/**
+* get total sync out bytes
+* @return total sync out bytes
+*/
+  public long getTotalSyncOutBytes()
+  {
+  	return this.totalSyncOutBytes;
+  }
+  
+/**
+* get success sync out bytes
+* @return success sync out bytes
+*/
+  public long getSuccessSyncOutBytes()
+  {
+  	return this.successSyncOutBytes;
+  }
+
+/**
+* get total file opened count
+* @return total file opened bytes
+*/
+  public long getTotalFileOpenCount()
+  {
+  	return this.totalFileOpenCount;
+  }
+  
+/**
+* get success file opened count
+* @return success file opened count
+*/
+  public long getSuccessFileOpenCount()
+  {
+  	return this.successFileOpenCount;
+  }
+
+/**
+* get total file read count
+* @return total file read bytes
+*/
+  public long getTotalFileReadCount()
+  {
+  	return this.totalFileReadCount;
+  }
+  
+/**
+* get success file read count
+* @return success file read count
+*/
+  public long getSuccessFileReadCount()
+  {
+  	return this.successFileReadCount;
+  }
+
+/**
+* get total file write count
+* @return total file write bytes
+*/
+  public long getTotalFileWriteCount()
+  {
+  	return this.totalFileWriteCount;
+  }
+  
+/**
+* get success file write count
+* @return success file write count
+*/
+  public long getSuccessFileWriteCount()
+  {
+  	return this.successFileWriteCount;
+  }
+  
+/**
+* get last source update timestamp
+* @return last source update timestamp
+*/
+  public Date getLastSourceUpdate()
+  {
+  	return this.lastSourceUpdate;
+  }
+
+/**
+* get last synced update timestamp
+* @return last synced update timestamp
+*/
+  public Date getLastSyncUpdate()
+  {
+  	return this.lastSyncUpdate;
+  }
+  
+/**
+* get last synced timestamp
+* @return last synced timestamp
+*/
+  public Date getLastSyncedTimestamp()
+  {
+  	return this.lastSyncedTimestamp;
+  }
+
+/**
+* get last heart beat timestamp
+* @return last heart beat timestamp
+*/
+  public Date getLastHeartBeatTime()
+  {
+  	return this.lastHeartBeatTime;
+  }
+
+/**
+* if the trunk server
+* @return true for the trunk server, otherwise false
+*/
+  public boolean isTrunkServer()
+  {
+  	return this.ifTrunkServer;
+  }
+  
+/**
+* set fields
+* @param bs byte array
+* @param offset start offset
+*/
+	public void setFields(byte[] bs, int offset)
+	{
+		this.status = byteValue(bs, offset, fieldsArray[FIELD_INDEX_STATUS]);
+		this.id = stringValue(bs, offset, fieldsArray[FIELD_INDEX_ID]);
+		this.ipAddr = stringValue(bs, offset, fieldsArray[FIELD_INDEX_IP_ADDR]);
+		this.srcIpAddr = stringValue(bs, offset, fieldsArray[FIELD_INDEX_SRC_IP_ADDR]);
+		this.domainName = stringValue(bs, offset, fieldsArray[FIELD_INDEX_DOMAIN_NAME]);
+		this.version = stringValue(bs, offset, fieldsArray[FIELD_INDEX_VERSION]);
+		this.totalMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MB]);
+		this.freeMB = longValue(bs, offset, fieldsArray[FIELD_INDEX_FREE_MB]);
+		this.uploadPriority = intValue(bs, offset, fieldsArray[FIELD_INDEX_UPLOAD_PRIORITY]);
+		this.joinTime = dateValue(bs, offset, fieldsArray[FIELD_INDEX_JOIN_TIME]);
+		this.upTime = dateValue(bs, offset, fieldsArray[FIELD_INDEX_UP_TIME]);
+	  this.storePathCount = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORE_PATH_COUNT]);
+	  this.subdirCountPerPath = intValue(bs, offset, fieldsArray[FIELD_INDEX_SUBDIR_COUNT_PER_PATH]);
+	  this.storagePort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_PORT]);
+	  this.storageHttpPort = intValue(bs, offset, fieldsArray[FIELD_INDEX_STORAGE_HTTP_PORT]);
+	  this.currentWritePath = intValue(bs, offset, fieldsArray[FIELD_INDEX_CURRENT_WRITE_PATH]);
+		this.totalUploadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_COUNT]);
+		this.successUploadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_COUNT]);
+		this.totalAppendCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_APPEND_COUNT]);
+		this.successAppendCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_APPEND_COUNT]);
+		this.totalModifyCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MODIFY_COUNT]);
+		this.successModifyCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_COUNT]);
+		this.totalTruncateCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_TRUNCATE_COUNT]);
+		this.successTruncateCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_TRUNCATE_COUNT]);
+		this.totalSetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_SET_META_COUNT]);
+		this.successSetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_SET_META_COUNT]);
+		this.totalDeleteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DELETE_COUNT]);
+		this.successDeleteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DELETE_COUNT]);
+		this.totalDownloadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_COUNT]);
+		this.successDownloadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_COUNT]);
+		this.totalGetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_GET_META_COUNT]);
+		this.successGetMetaCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_GET_META_COUNT]);
+		this.totalCreateLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_CREATE_LINK_COUNT]);
+		this.successCreateLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_CREATE_LINK_COUNT]);
+		this.totalDeleteLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DELETE_LINK_COUNT]);
+		this.successDeleteLinkCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DELETE_LINK_COUNT]);
+		this.totalUploadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_UPLOAD_BYTES]);
+		this.successUploadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_UPLOAD_BYTES]);
+		this.totalAppendBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_APPEND_BYTES]);
+		this.successAppendBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_APPEND_BYTES]);
+		this.totalModifyBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_MODIFY_BYTES]);
+		this.successModifyBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_MODIFY_BYTES]);
+		this.totalDownloadloadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_DOWNLOAD_BYTES]);
+		this.successDownloadloadBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_DOWNLOAD_BYTES]);
+		this.totalSyncInBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_SYNC_IN_BYTES]);
+		this.successSyncInBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_SYNC_IN_BYTES]);
+		this.totalSyncOutBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_SYNC_OUT_BYTES]);
+		this.successSyncOutBytes = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_SYNC_OUT_BYTES]);
+		this.totalFileOpenCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_FILE_OPEN_COUNT]);
+		this.successFileOpenCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_FILE_OPEN_COUNT]);
+		this.totalFileReadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_FILE_READ_COUNT]);
+		this.successFileReadCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_FILE_READ_COUNT]);
+		this.totalFileWriteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_TOTAL_FILE_WRITE_COUNT]);
+		this.successFileWriteCount = longValue(bs, offset, fieldsArray[FIELD_INDEX_SUCCESS_FILE_WRITE_COUNT]);
+		this.lastSourceUpdate = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_SOURCE_UPDATE]);
+		this.lastSyncUpdate = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_SYNC_UPDATE]);
+		this.lastSyncedTimestamp = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_SYNCED_TIMESTAMP]);
+		this.lastHeartBeatTime = dateValue(bs, offset, fieldsArray[FIELD_INDEX_LAST_HEART_BEAT_TIME]);
+		this.ifTrunkServer = booleanValue(bs, offset, fieldsArray[FIELD_INDEX_IF_TRUNK_FILE]);
+	}
+
+/**
+* get fields total size
+* @return fields total size
+*/
+	public static int getFieldsTotalSize()
+	{
+		return fieldsTotalSize;
+	}
+}

+ 990 - 0
src/main/java/org/csource/fastdfs/TrackerClient.java

@@ -0,0 +1,990 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.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;
+	}
+}

+ 120 - 0
src/main/java/org/csource/fastdfs/TrackerGroup.java

@@ -0,0 +1,120 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+* Tracker server group
+* @author Happy Fish / YuQing
+* @version Version 1.17
+*/
+public class TrackerGroup
+{
+	protected Integer lock;
+	public int tracker_server_index;
+	public InetSocketAddress[] tracker_servers;
+	
+/**
+* Constructor
+* @param tracker_servers tracker servers
+*/
+	public TrackerGroup(InetSocketAddress[] tracker_servers)
+	{
+		this.tracker_servers = tracker_servers;
+		this.lock = new Integer(0);
+		this.tracker_server_index = 0;
+	}
+
+/**
+* return connected tracker server
+* @return connected tracker server, null for fail
+*/
+	public TrackerServer getConnection(int serverIndex) throws IOException
+	{
+		Socket sock = new Socket();
+		sock.setReuseAddress(true);
+		sock.setSoTimeout(ClientGlobal.g_network_timeout);
+		sock.connect(this.tracker_servers[serverIndex], ClientGlobal.g_connect_timeout);
+		return new TrackerServer(sock, this.tracker_servers[serverIndex]);
+	}
+	
+/**
+* return connected tracker server
+* @return connected tracker server, null for fail
+*/
+	public TrackerServer getConnection() throws IOException
+	{
+		int current_index;
+		
+		synchronized(this.lock)
+		{
+			this.tracker_server_index++;
+			if (this.tracker_server_index >= this.tracker_servers.length)
+			{
+				this.tracker_server_index = 0;
+			}
+			
+			current_index = this.tracker_server_index;
+		}
+		
+		try
+		{
+			return this.getConnection(current_index);
+	  }
+	  catch(IOException ex)
+	  {
+	  	System.err.println("connect to server " + this.tracker_servers[current_index].getAddress().getHostAddress() + ":" + this.tracker_servers[current_index].getPort() + " fail");
+	  	ex.printStackTrace(System.err);
+	  }
+	  
+	  for (int i=0; i<this.tracker_servers.length; i++)
+	  {
+	  	if (i == current_index)
+	  	{
+	  		continue;
+	  	}
+	  	
+			try
+			{
+				TrackerServer trackerServer = this.getConnection(i);
+				
+				synchronized(this.lock)
+				{
+					if (this.tracker_server_index == current_index)
+					{
+						this.tracker_server_index = i;
+					}
+				}
+				
+				return trackerServer;
+		  }
+		  catch(IOException ex)
+		  {
+		  	System.err.println("connect to server " + this.tracker_servers[i].getAddress().getHostAddress() + ":" + this.tracker_servers[i].getPort() + " fail");
+		  	ex.printStackTrace(System.err);
+		  }
+	  }
+	  
+	  return null;
+	}
+
+	public Object clone()
+	{
+		InetSocketAddress[] trackerServers = new InetSocketAddress[this.tracker_servers.length];
+		for (int i=0; i<trackerServers.length; i++)
+		{
+			trackerServers[i] = new InetSocketAddress(this.tracker_servers[i].getAddress().getHostAddress(), this.tracker_servers[i].getPort());
+		}
+		
+		return new TrackerGroup(trackerServers);
+	}
+}

+ 90 - 0
src/main/java/org/csource/fastdfs/TrackerServer.java

@@ -0,0 +1,90 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+* Tracker Server Info
+* @author Happy Fish / YuQing
+* @version Version 1.11
+*/
+public class TrackerServer
+{
+	protected Socket sock;
+	protected InetSocketAddress inetSockAddr;
+	
+/**
+* Constructor
+* @param sock Socket of server
+* @param inetSockAddr the server info
+*/
+	public TrackerServer(Socket sock, InetSocketAddress inetSockAddr)
+	{
+		this.sock = sock;
+		this.inetSockAddr = inetSockAddr;
+	}
+	
+/**
+* get the connected socket
+* @return the socket
+*/
+	public Socket getSocket() throws IOException
+	{
+		if (this.sock == null)
+		{
+			this.sock = ClientGlobal.getSocket(this.inetSockAddr);
+		}
+		
+		return this.sock;
+	}
+	
+/**
+* get the server info
+* @return the server info
+*/
+	public InetSocketAddress getInetSocketAddress()
+	{
+		return this.inetSockAddr;
+	}
+	
+	public OutputStream getOutputStream() throws IOException
+	{
+		return this.sock.getOutputStream();
+	}
+	
+	public InputStream getInputStream() throws IOException
+	{
+		return this.sock.getInputStream();
+	}
+
+	public void close() throws IOException
+	{
+		if (this.sock != null)
+		{
+			try
+			{
+				ProtoCommon.closeSocket(this.sock);
+			}
+			finally
+			{
+				this.sock = null;
+			}
+		}
+	}
+	
+	protected void finalize() throws Throwable
+	{
+		this.close();
+	}
+}

+ 27 - 0
src/main/java/org/csource/fastdfs/UploadCallback.java

@@ -0,0 +1,27 @@
+/**
+* Copyright (C) 2008 Happy Fish / YuQing
+*
+* 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.csource.fastdfs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+* upload file callback interface
+* @author Happy Fish / YuQing
+* @version Version 1.0
+*/
+public interface UploadCallback
+{
+	/**
+	* 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;
+}

+ 60 - 0
src/main/java/org/csource/fastdfs/UploadStream.java

@@ -0,0 +1,60 @@
+package org.csource.fastdfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+* Upload file by stream
+* @author zhouzezhong & Happy Fish / YuQing
+* @version Version 1.11
+*/
+public class UploadStream implements UploadCallback
+{
+	private InputStream inputStream; //input stream for reading
+	private long fileSize = 0;  //size of the uploaded file
+	
+	/** 
+	* constructor
+	* @param inputStream input stream for uploading
+	* @param fileSize size of uploaded file
+	*/
+	public UploadStream(InputStream inputStream, long fileSize)
+	{
+		super();
+		this.inputStream = inputStream;
+		this.fileSize = fileSize;
+	}
+
+	/**
+	* 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
+	{
+		long remainBytes = fileSize;
+		byte[] buff = new byte[256 * 1024];
+		int bytes;
+		while(remainBytes > 0)
+		{ 
+			try
+			{
+				if ((bytes=inputStream.read(buff, 0, remainBytes > buff.length ? buff.length : (int)remainBytes)) < 0)
+				{
+					return -1;
+				}
+			}
+			catch(IOException ex)
+			{
+				ex.printStackTrace(); 
+				return -1;
+			}
+			
+			out.write(buff, 0, bytes);
+			remainBytes -= bytes;
+		}
+		
+		return 0;
+	}
+}

+ 10 - 0
src/main/resources/application-dev.yml

@@ -16,3 +16,13 @@ spring:
         testOnReturn: false
         poolPreparedStatements: true
         maxPoolPreparedStatementPerConnectionSize: 20
+# FastDFS 服务配置
+fdfs:
+    network-timeout: 5
+    connect-timeout: 30
+    tracker-server:
+        - 177.77.77.159:22122
+    charset: UTF-8
+    tracker-http-port: 8080
+    anti-steal-token: false
+    secret-key: FastDFS1234567890

+ 11 - 1
src/main/resources/application-pro.yml

@@ -15,4 +15,14 @@ spring:
         testOnBorrow: false
         testOnReturn: false
         poolPreparedStatements: true
-        maxPoolPreparedStatementPerConnectionSize: 20
+        maxPoolPreparedStatementPerConnectionSize: 20
+# FastDFS 服务配置
+fdfs:
+    network-timeout: 5
+    connect-timeout: 30
+    tracker-server:
+        - 177.77.77.159:22122
+    charset: UTF-8
+    tracker-http-port: 8080
+    anti-steal-token: false
+    secret-key: FastDFS1234567890

+ 11 - 1
src/main/resources/application-test.yml

@@ -15,4 +15,14 @@ spring:
         testOnBorrow: false
         testOnReturn: false
         poolPreparedStatements: true
-        maxPoolPreparedStatementPerConnectionSize: 20
+        maxPoolPreparedStatementPerConnectionSize: 20
+# FastDFS 服务配置
+fdfs:
+    network-timeout: 5
+    connect-timeout: 30
+    tracker-server:
+        - 177.77.77.159:22122
+    charset: UTF-8
+    tracker-http-port: 8080
+    anti-steal-token: false
+    secret-key: FastDFS1234567890