Skip to content

Commit 69aa7c0

Browse files
committed
Merge pull request #621 from couchbase/feature/issue_197_gzipped_attachment
Fixed CBL Android 197 gzipped attachment
2 parents 257b984 + 86d0a03 commit 69aa7c0

3 files changed

Lines changed: 80 additions & 42 deletions

File tree

src/main/java/com/couchbase/lite/Attachment.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import com.couchbase.lite.internal.InterfaceAudience;
2222
import com.couchbase.lite.util.Log;
2323

24-
import java.io.IOException;
2524
import java.io.File;
25+
import java.io.IOException;
2626
import java.io.InputStream;
2727
import java.net.URL;
2828
import java.util.Collections;
@@ -132,7 +132,7 @@ public InputStream getContent() throws CouchbaseLiteException {
132132
throw new CouchbaseLiteException(Status.INTERNAL_SERVER_ERROR);
133133
}
134134
Attachment attachment = db.getAttachmentForSequence(sequence, this.name);
135-
body = attachment.getContent();
135+
body = attachment.getBodyIfNew();
136136
if (attachment.getGZipped()) {
137137
// Client does not expect a gzipped stream.
138138
// Only Router handles gzipped streams and uses getAttachmentForSequence directly.
@@ -270,6 +270,9 @@ else if (value != null) {
270270
}
271271

272272
/**
273+
* NOTE: getGZipped() can return correct value if Attachment is returned from Database.getAttachmentForSequence(long, String).
274+
* This method should be internal use only.
275+
*
273276
* @exclude
274277
*/
275278
@InterfaceAudience.Private

src/main/java/com/couchbase/lite/Database.java

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,8 @@ public synchronized boolean open() {
12611261
"key BLOB NOT NULL, " +
12621262
"type TEXT, " +
12631263
"length INTEGER NOT NULL, " +
1264+
"encoding INTEGER DEFAULT 0, " +
1265+
"encoded_length INTEGER, " +
12641266
"revpos INTEGER DEFAULT 0); " +
12651267
"CREATE INDEX attachments_by_sequence on attachments(sequence, filename); " +
12661268
"CREATE INDEX attachments_sequence ON attachments(sequence); " +
@@ -1289,9 +1291,16 @@ public synchronized boolean open() {
12891291
int revPos = (Integer) attachment.get("revpos");
12901292
int length = (Integer) attachment.get("length");
12911293
String digest = (String) attachment.get("digest");
1294+
int encodedLength = -1;
1295+
if(attachment.containsKey("encoded_length"))
1296+
encodedLength = (Integer)attachment.get("encoded_length");
1297+
AttachmentInternal.AttachmentEncoding encoding = AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone;
1298+
if(attachment.containsKey("encoding") && attachment.get("encoding").equals("gzip")){
1299+
encoding = AttachmentInternal.AttachmentEncoding.AttachmentEncodingGZIP;
1300+
}
12921301
BlobKey key = new BlobKey(digest);
12931302
try {
1294-
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revPos, key, length);
1303+
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revPos, key, length, encoding, encodedLength);
12951304
} catch (CouchbaseLiteException e) {
12961305
Log.e(Log.TAG_DATABASE, "Attachment information inserstion error: " + name + "=" + attachment.toString(), e);
12971306
return false;
@@ -2846,10 +2855,15 @@ void insertAttachmentForSequence(AttachmentInternal attachment, long sequence) t
28462855
attachment.getName(),
28472856
attachment.getContentType(),
28482857
attachment.getRevpos(),
2849-
attachment.getBlobKey());
2858+
attachment.getBlobKey(),
2859+
attachment.getLength(),
2860+
attachment.getEncoding(),
2861+
attachment.getEncodedLength());
28502862
}
28512863

28522864
/**
2865+
* Note: This method is used only from unit tests.
2866+
*
28532867
* @exclude
28542868
*/
28552869
@InterfaceAudience.Private
@@ -2874,37 +2888,28 @@ public void insertAttachmentForSequenceWithNameAndType(InputStream contentStream
28742888
*/
28752889
@InterfaceAudience.Private
28762890
public void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key) throws CouchbaseLiteException {
2877-
try {
2878-
ContentValues args = new ContentValues();
2879-
args.put("sequence", sequence);
2880-
args.put("filename", name);
2881-
if (key != null) {
2882-
args.put("key", key.getBytes());
2883-
args.put("length", attachments.getSizeOfBlob(key));
2884-
}
2885-
args.put("type", contentType);
2886-
args.put("revpos", revpos);
2887-
long result = database.insert("attachments", null, args);
2888-
if (result == -1) {
2889-
String msg = "Insert attachment failed (returned -1)";
2890-
Log.e(Database.TAG, msg);
2891-
throw new CouchbaseLiteException(msg, Status.INTERNAL_SERVER_ERROR);
2892-
}
2893-
} catch (SQLException e) {
2894-
Log.e(Database.TAG, "Error inserting attachment", e);
2895-
throw new CouchbaseLiteException(e, Status.INTERNAL_SERVER_ERROR);
2896-
}
2891+
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revpos, key, key != null ? attachments.getSizeOfBlob(key): -1, AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, -1);
28972892
}
28982893

2899-
private void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key, long length) throws CouchbaseLiteException {
2894+
private void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key, long length, AttachmentInternal.AttachmentEncoding encoding, long encodedLength) throws CouchbaseLiteException {
29002895
try {
29012896
ContentValues args = new ContentValues();
29022897
args.put("sequence", sequence);
29032898
args.put("filename", name);
2904-
args.put("key", key.getBytes());
2905-
args.put("length", length);
29062899
args.put("type", contentType);
29072900
args.put("revpos", revpos);
2901+
if (key != null) {
2902+
args.put("key", key.getBytes());
2903+
}
2904+
if (length >= 0) {
2905+
args.put("length", length);
2906+
}
2907+
if(encoding == AttachmentInternal.AttachmentEncoding.AttachmentEncodingGZIP) {
2908+
args.put("encoding", encoding.ordinal());
2909+
if (encodedLength >= 0) {
2910+
args.put("encoded_length", encodedLength);
2911+
}
2912+
}
29082913
long result = database.insert("attachments", null, args);
29092914
if (result == -1) {
29102915
String msg = "Insert attachment failed (returned -1)";
@@ -2916,6 +2921,7 @@ private void insertAttachmentForSequenceWithNameAndType(long sequence, String na
29162921
throw new CouchbaseLiteException(e, Status.INTERNAL_SERVER_ERROR);
29172922
}
29182923
}
2924+
29192925
/**
29202926
* @exclude
29212927
*/
@@ -3108,7 +3114,7 @@ public Map<String,Object> getAttachmentsDictForSequenceWithContent(long sequence
31083114

31093115
String args[] = { Long.toString(sequence) };
31103116
try {
3111-
cursor = database.rawQuery("SELECT filename, key, type, length, revpos FROM attachments WHERE sequence=?", args);
3117+
cursor = database.rawQuery("SELECT filename, key, type, encoding, length, encoded_length, revpos FROM attachments WHERE sequence=?", args);
31123118

31133119
if(!cursor.moveToNext()) {
31143120
return null;
@@ -3119,7 +3125,10 @@ public Map<String,Object> getAttachmentsDictForSequenceWithContent(long sequence
31193125
while(!cursor.isAfterLast()) {
31203126

31213127
boolean dataSuppressed = false;
3122-
int length = cursor.getInt(3);
3128+
int length = cursor.getInt(4);
3129+
3130+
AttachmentInternal.AttachmentEncoding encoding = AttachmentInternal.AttachmentEncoding.values()[cursor.getInt(3)];
3131+
int encodedLength = cursor.getInt(5);
31233132

31243133
byte[] keyData = cursor.getBlob(1);
31253134
BlobKey key = new BlobKey(keyData);
@@ -3132,39 +3141,42 @@ public Map<String,Object> getAttachmentsDictForSequenceWithContent(long sequence
31323141
}
31333142
else {
31343143
byte[] data = attachments.blobForKey(key);
3135-
31363144
if(data != null) {
31373145
dataBase64 = Base64.encodeBytes(data); // <-- very expensive
31383146
}
31393147
else {
31403148
Log.w(Database.TAG, "Error loading attachment. Sequence: %s", sequence);
31413149
}
3142-
31433150
}
3151+
}
31443152

3153+
String encodingStr = null;
3154+
if (encoding == AttachmentInternal.AttachmentEncoding.AttachmentEncodingGZIP) {
3155+
// NOTE: iOS decode if attachment is included int the dict.
3156+
encodingStr = "gzip";
31453157
}
31463158

31473159
Map<String, Object> attachment = new HashMap<String, Object>();
3148-
3149-
3150-
3151-
if(!(dataBase64 != null || dataSuppressed)) {
3160+
if (!(dataBase64 != null || dataSuppressed)) {
31523161
attachment.put("stub", true);
31533162
}
3154-
3155-
if(dataBase64 != null) {
3163+
if (dataBase64 != null) {
31563164
attachment.put("data", dataBase64);
31573165
}
3158-
31593166
if (dataSuppressed == true) {
31603167
attachment.put("follows", true);
31613168
}
3162-
31633169
attachment.put("digest", digestString);
31643170
String contentType = cursor.getString(2);
31653171
attachment.put("content_type", contentType);
3172+
if (encodingStr != null) {
3173+
attachment.put("encoding", encodingStr);
3174+
}
31663175
attachment.put("length", length);
3167-
attachment.put("revpos", cursor.getInt(4));
3176+
if (encodingStr != null && encodedLength >= 0) {
3177+
attachment.put("encoded_length", encodedLength);
3178+
}
3179+
attachment.put("revpos", cursor.getInt(6));
31683180

31693181
String filename = cursor.getString(0);
31703182
result.put(filename, attachment);
@@ -4254,7 +4266,7 @@ Map<String, AttachmentInternal> getAttachmentsFromRevision(RevisionInternal rev,
42544266
// If there's inline attachment data, decode and store it:
42554267
byte[] newContents;
42564268
try {
4257-
newContents = Base64.decode(newContentBase64);
4269+
newContents = Base64.decode(newContentBase64, Base64.DONT_GUNZIP);
42584270
} catch (IOException e) {
42594271
throw new CouchbaseLiteException(e, Status.BAD_ENCODING);
42604272
}

src/main/java/com/couchbase/lite/replicator/PusherInternal.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,15 @@ else if (attachment.containsKey("content-type")) {
615615
" issue #80): %s", attachment);
616616
}
617617

618-
FileBody fileBody = new FileBody(file, contentType);
618+
// NOTE: Content-Encoding might not be necessary to set. Apache FileBody does not set Content-Encoding.
619+
// FileBody always return null for getContentEncoding(), and Content-Encoding header is not set in multipart
620+
// CBL iOS: https://github.com/couchbase/couchbase-lite-ios/blob/feb7ff5eda1e80bd00e5eb19f1d46c793f7a1951/Source/CBL_Pusher.m#L449-L452
621+
String contentEncoding = null;
622+
if(attachment.containsKey("encoding")){
623+
contentEncoding = (String)attachment.get("encoding");
624+
}
625+
626+
FileBody fileBody = new CustomFileBody(file, contentType, contentEncoding);
619627
multiPart.addPart(attachmentKey, fileBody);
620628
}
621629

@@ -717,4 +725,19 @@ private static int findCommonAncestor(RevisionInternal rev, List<String> possibl
717725

718726
return generation;
719727
}
728+
729+
// CustomFileBody to support contentEncoding. FileBody returns always null for getContentEncoding()
730+
private static class CustomFileBody extends FileBody {
731+
private String contentEncoding = null;
732+
733+
public CustomFileBody(final File file, final String mimeType, final String contentEncoding) {
734+
super(file, mimeType);
735+
this.contentEncoding = contentEncoding;
736+
}
737+
738+
@Override
739+
public String getContentEncoding() {
740+
return contentEncoding;
741+
}
742+
}
720743
}

0 commit comments

Comments
 (0)