]> source.dussan.org Git - jackcess.git/commitdiff
add methods to get the actual attachment content
authorJames Ahlborn <jtahlborn@yahoo.com>
Sun, 2 Jun 2013 02:23:36 +0000 (02:23 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sun, 2 Jun 2013 02:23:36 +0000 (02:23 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@733 f203690c-595d-4dc9-a70b-905162fa7fd2

pom.xml
src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Database.java
src/java/com/healthmarketscience/jackcess/PageChannel.java
src/java/com/healthmarketscience/jackcess/complex/Attachment.java
src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
test/src/java/com/healthmarketscience/jackcess/ComplexColumnTest.java

diff --git a/pom.xml b/pom.xml
index 380ffaffbeb23269cd5ce131a2f75e9277685955..08e90dcb64371f250fb44185799db00aed5a5234 100644 (file)
--- a/pom.xml
+++ b/pom.xml
               read-only support</role>
       </roles>
     </contributor>
+    <contributor>
+      <name>Lorenzo Carrara</name>
+      <roles>
+        <role>Reverse engineered the attachment data encoding.</role>
+      </roles>
+    </contributor>
   </contributors>
   <issueManagement>
     <system>SourceForge2</system>
index c5b95758de5300a54a48edc777a0eb52d1d7e278..d26c22d7c388f4d60764f1bd6dc775a72629fe2d 100644 (file)
       <action dev="jahlborn" type="update">
         Add more methods to Database for retrieving Relationships.
       </action>
+      <action dev="jahlborn" type="update">
+        Add methods to get the actual attachment content, thanks to Lorenzo
+        Carrara.
+      </action>
     </release>
     <release version="1.2.12" date="2013-05-09">
       <action dev="jahlborn" type="fix" system="SourceForge2" issue="94">
index 3c0274f1000b3e02ec3bacd654cfea4dcff9be0a..5f445b16a75f9b26ee58ec91e80b9780555405b4 100644 (file)
@@ -899,8 +899,7 @@ public class Column implements Comparable<Column> {
   private byte[] readLongValue(byte[] lvalDefinition)
     throws IOException
   {
-    ByteBuffer def = ByteBuffer.wrap(lvalDefinition)
-      .order(PageChannel.DEFAULT_BYTE_ORDER);
+    ByteBuffer def = PageChannel.wrap(lvalDefinition);
     int lengthWithFlags = def.getInt();
     int length = lengthWithFlags & (~LONG_VALUE_TYPE_MASK);
 
index 3a720b2652aa01ca370f250c168b57d3c21c1077..450c9b9235c560ef66dfbe619f780ff6d6712374 100644 (file)
@@ -2409,8 +2409,7 @@ public class Database
     double dateVal = Double.longBitsToDouble(buffer.getLong());
 
     byte[] pwdMask = new byte[4];
-    ByteBuffer.wrap(pwdMask).order(PageChannel.DEFAULT_BYTE_ORDER)
-      .putInt((int)dateVal);
+    PageChannel.wrap(pwdMask).putInt((int)dateVal);
 
     return pwdMask;
   }
index 58b40170bfb84bf7c195d611da328d03ab997774..27cb0abd2a0b73cea04eb81ea5bb1c9679994c82 100644 (file)
@@ -380,4 +380,12 @@ public class PageChannel implements Channel, Flushable {
       .position(position)
       .mark();
   }
+
+  /**
+   * Returns a ByteBuffer wrapping the given bytes and configured with the
+   * default byte order.
+   */
+  public static ByteBuffer wrap(byte[] bytes) {
+    return ByteBuffer.wrap(bytes).order(DEFAULT_BYTE_ORDER);
+  }
 }
index 2f4b0461cf5dedfeff4c46b609e9dc8aed8dce3e..be7e3026988d03bfe598e7085295c9dafae38c5a 100644 (file)
@@ -19,6 +19,7 @@ USA
 
 package com.healthmarketscience.jackcess.complex;
 
+import java.io.IOException;
 import java.util.Date;
 
 /**
@@ -32,6 +33,10 @@ public interface Attachment extends ComplexValue
 
   public void setFileData(byte[] data);
 
+  public byte[] getDecodedFileData() throws IOException;
+
+  public void setDecodedFileData(byte[] data);
+
   public String getFileName();
 
   public void setFileName(String fileName);
index 5410e41e28713679de98216db0a1c45f30bf50d9..eb3ca7387d7260e2d710a426e18c483836f5eacc 100644 (file)
@@ -19,13 +19,19 @@ USA
 
 package com.healthmarketscience.jackcess.complex;
 
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.zip.InflaterInputStream;
 
 import com.healthmarketscience.jackcess.ByteUtil;
 import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.PageChannel;
 import com.healthmarketscience.jackcess.Table;
 
 
@@ -39,6 +45,9 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
   private static final String FILE_NAME_COL_NAME = "FileName";
   private static final String FILE_TYPE_COL_NAME = "FileType";
 
+  private static final int DATA_TYPE_RAW = 0;
+  private static final int DATA_TYPE_COMPRESSED = 1;
+
   private final Column _fileUrlCol;
   private final Column _fileNameCol;
   private final Column _fileTypeCol;
@@ -142,7 +151,7 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
     byte[] data = (byte[])getFileDataColumn().getRowValue(rawValue);
     
     return new AttachmentImpl(id, complexValueFk, url, name, type, data,
-                              ts, flags);
+                              ts, flags, null);
   }
 
   @Override
@@ -179,7 +188,33 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
       String type, byte[] data, Date timeStamp, Integer flags)
   {
     return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
-                              data, timeStamp, flags);
+                              data, timeStamp, flags, null);
+  }
+
+  public static Attachment newDecodedAttachment(byte[] decodedData) {
+    return newDecodedAttachment(INVALID_COMPLEX_VALUE_ID, decodedData);
+  }
+  
+  public static Attachment newDecodedAttachment(
+      ComplexValueForeignKey complexValueFk, byte[] decodedData) {
+    return newDecodedAttachment(complexValueFk, null, null, null, decodedData,
+                                null, null);
+  }
+
+  public static Attachment newDecodedAttachment(
+      String url, String name, String type, byte[] decodedData,
+      Date timeStamp, Integer flags)
+  {
+    return newDecodedAttachment(INVALID_COMPLEX_VALUE_ID, url, name, type, 
+                                decodedData, timeStamp, flags);
+  }
+  
+  public static Attachment newDecodedAttachment(
+      ComplexValueForeignKey complexValueFk, String url, String name,
+      String type, byte[] decodedData, Date timeStamp, Integer flags)
+  {
+    return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
+                              null, timeStamp, flags, decodedData);
   }
 
   
@@ -235,10 +270,11 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
     private byte[] _data;
     private Date _timeStamp;
     private Integer _flags;
+    private byte[] _decodedData;
 
     private AttachmentImpl(int id, ComplexValueForeignKey complexValueFk,
                            String url, String name, String type, byte[] data,
-                           Date timeStamp, Integer flags)
+                           Date timeStamp, Integer flags, byte[] decodedData)
     {
       super(id, complexValueFk);
       _url = url;
@@ -247,14 +283,31 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
       _data = data;
       _timeStamp = timeStamp;
       _flags = flags;
+      _decodedData = decodedData;
     }
     
     public byte[] getFileData() {
+      if((_data == null) && (_decodedData != null)) {
+        _data = encodeData();
+      }
       return _data;
     }
 
     public void setFileData(byte[] data) {
       _data = data;
+      _decodedData = null;
+    }
+
+    public byte[] getDecodedFileData() throws IOException {
+      if((_decodedData == null) && (_data != null)) {
+        _decodedData = decodeData();
+      }
+      return _decodedData;
+    }
+
+    public void setDecodedFileData(byte[] data) {
+      _decodedData = data;
+      _data = null;
     }
 
     public String getFileName() {
@@ -313,6 +366,72 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
         + ", " + getFileTimeStamp() + ", " + getFileFlags()  + ", " +
         ByteUtil.toHexString(getFileData());
     } 
+
+    /**
+     * Decodes the raw attachment file data to get the _actual_ content.
+     */
+    private byte[] decodeData() throws IOException {
+
+      if(_data.length < 8) {
+        // nothing we can do
+        throw new IOException("Unknown encoded attachment data format");
+      }
+
+      // read initial header info
+      ByteBuffer bb = PageChannel.wrap(_data);
+      int typeFlag = bb.getInt();
+      int dataLen = bb.getInt();
+
+      DataInputStream contentStream = null;
+      try {
+        InputStream bin = new ByteArrayInputStream(
+            _data, 8, _data.length - 8);
+
+        if(typeFlag == DATA_TYPE_RAW) {
+          // nothing else to do
+        } else if(typeFlag == DATA_TYPE_COMPRESSED) {
+          // actual content is deflate compressed
+          bin = new InflaterInputStream(bin);
+        } else {
+          throw new IOException(
+              "Unknown encoded attachment data type " + typeFlag);
+        }
+
+        contentStream = new DataInputStream(bin);
+
+        // header is the "file extension" of the data.  no clue why we need
+        // that again since it's already a separate field in the attachment
+        // table.  just skip it
+        byte[] tmpBytes = new byte[4];
+        contentStream.readFully(tmpBytes);
+        int headerLen = PageChannel.wrap(tmpBytes).getInt();
+        contentStream.skipBytes(headerLen - 4);
+
+        // calculate actual data length and read it (note, header length
+        // includes the bytes for the length)
+        tmpBytes = new byte[dataLen - headerLen];
+        contentStream.readFully(tmpBytes);
+
+        return tmpBytes;
+
+      } finally {
+        if(contentStream != null) {
+          try {
+            contentStream.close();
+          } catch(IOException e) {
+            // ignored
+          }
+        }
+      }
+    }
+
+    /**
+     * Encodes the actual attachment file data to get the raw, stored format.
+     */
+    private byte[] encodeData() {
+      // FIXME, writeme
+      throw new UnsupportedOperationException();
+    }
   }
-  
+
 }
index 80735e3646f0a188dd0c495c69a056102f851370..170a92ff3db05907bd8cb1526b7f9c88c5b66fef 100644 (file)
@@ -215,6 +215,24 @@ public class ComplexValueForeignKey extends Number
     return a;
   }
 
+  public Attachment addDecodedAttachment(byte[] decodedData)
+    throws IOException
+  {
+    return addDecodedAttachment(null, null, null, decodedData, null, null);
+  }
+  
+  public Attachment addDecodedAttachment(
+      String url, String name, String type, byte[] decodedData,
+      Date timeStamp, Integer flags)
+    throws IOException
+  {
+    reset();
+    Attachment a = AttachmentColumnInfo.newDecodedAttachment(
+        this, url, name, type, decodedData, timeStamp, flags);
+    getAttachmentInfo().addValue(a);
+    return a;
+  }
+
   public Attachment updateAttachment(Attachment attachment)
     throws IOException
   {
index 019dd741b973b5ddf6d4e697dfa0103450af3581..6dbcbed524572053a07d8acfd8310a8839aa6a14 100644 (file)
@@ -367,11 +367,12 @@ public class ComplexColumnTest extends TestCase
       assertEquals(fileNames.length, attachments.size());
       for(int i = 0; i < fileNames.length; ++i) {
         String fname = fileNames[i];
-        byte[] dataBytes = getFileBytes(fname);
         Attachment a = attachments.get(i);
         assertEquals(fname, a.getFileName());
         assertEquals("txt", a.getFileType());
-        assertTrue(Arrays.equals(dataBytes, a.getFileData()));
+        assertTrue(Arrays.equals(getFileBytes(fname), a.getFileData()));
+        assertTrue(Arrays.equals(getDecodedFileBytes(fname), 
+                                 a.getDecodedFileData()));
       }
     }
   }
@@ -430,17 +431,41 @@ public class ComplexColumnTest extends TestCase
     throw new RuntimeException("unexpected bytes");
   }
   
+  private static byte[] getDecodedFileBytes(String fname) throws Exception
+  {
+    if("test_data.txt".equals(fname)) {
+      return TEST_DEC_BYTES;
+    }
+    if("test_data2.txt".equals(fname)) {
+      return TEST2_DEC_BYTES;
+    }
+    throw new RuntimeException("unexpected bytes");
+  }
+  
   private static byte b(int i) { return (byte)i; }
   
+  private static byte[] getAsciiBytes(String str) {
+    try {
+      return str.getBytes("US-ASCII");
+    } catch(Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+  
   private static final byte[] TEST_BYTES = new byte[] {
     b(0x01),b(0x00),b(0x00),b(0x00),b(0x3A),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
     b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),
     b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0x52),b(0xA9),b(0x0F),b(0x7A)
   };
+
+  private static final byte[] TEST_DEC_BYTES = getAsciiBytes("this is some test data for attachment.");
   
   private static final byte[] TEST2_BYTES = new byte[] {
     b(0x01),b(0x00),b(0x00),b(0x00),b(0x3F),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
     b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0xB9),b(0xF9),b(0x45),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),
     b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0xA5),b(0x0B),b(0x11),b(0x4D)
   };
+
+  private static final byte[] TEST2_DEC_BYTES = getAsciiBytes("this is some more test data for attachment.");
+  
 }