Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/main/java/com/couchbase/lite/Attachment.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import com.couchbase.lite.internal.InterfaceAudience;
import com.couchbase.lite.util.Log;

import java.io.IOException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
Expand Down Expand Up @@ -132,7 +132,7 @@ public InputStream getContent() throws CouchbaseLiteException {
throw new CouchbaseLiteException(Status.INTERNAL_SERVER_ERROR);
}
Attachment attachment = db.getAttachmentForSequence(sequence, this.name);
body = attachment.getContent();
body = attachment.getBodyIfNew();
if (attachment.getGZipped()) {
// Client does not expect a gzipped stream.
// Only Router handles gzipped streams and uses getAttachmentForSequence directly.
Expand Down Expand Up @@ -270,6 +270,9 @@ else if (value != null) {
}

/**
* NOTE: getGZipped() can return correct value if Attachment is returned from Database.getAttachmentForSequence(long, String).
* This method should be internal use only.
*
* @exclude
*/
@InterfaceAudience.Private
Expand Down
90 changes: 51 additions & 39 deletions src/main/java/com/couchbase/lite/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,8 @@ public synchronized boolean open() {
"key BLOB NOT NULL, " +
"type TEXT, " +
"length INTEGER NOT NULL, " +
"encoding INTEGER DEFAULT 0, " +
"encoded_length INTEGER, " +
"revpos INTEGER DEFAULT 0); " +
"CREATE INDEX attachments_by_sequence on attachments(sequence, filename); " +
"CREATE INDEX attachments_sequence ON attachments(sequence); " +
Expand Down Expand Up @@ -1297,9 +1299,16 @@ public synchronized boolean open() {
int revPos = (Integer) attachment.get("revpos");
int length = (Integer) attachment.get("length");
String digest = (String) attachment.get("digest");
int encodedLength = -1;
if(attachment.containsKey("encoded_length"))
encodedLength = (Integer)attachment.get("encoded_length");
AttachmentInternal.AttachmentEncoding encoding = AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone;
if(attachment.containsKey("encoding") && attachment.get("encoding").equals("gzip")){
encoding = AttachmentInternal.AttachmentEncoding.AttachmentEncodingGZIP;
}
BlobKey key = new BlobKey(digest);
try {
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revPos, key, length);
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revPos, key, length, encoding, encodedLength);
} catch (CouchbaseLiteException e) {
Log.e(Log.TAG_DATABASE, "Attachment information inserstion error: " + name + "=" + attachment.toString(), e);
return false;
Expand Down Expand Up @@ -2854,10 +2863,15 @@ void insertAttachmentForSequence(AttachmentInternal attachment, long sequence) t
attachment.getName(),
attachment.getContentType(),
attachment.getRevpos(),
attachment.getBlobKey());
attachment.getBlobKey(),
attachment.getLength(),
attachment.getEncoding(),
attachment.getEncodedLength());
}

/**
* Note: This method is used only from unit tests.
*
* @exclude
*/
@InterfaceAudience.Private
Expand All @@ -2882,37 +2896,28 @@ public void insertAttachmentForSequenceWithNameAndType(InputStream contentStream
*/
@InterfaceAudience.Private
public void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key) throws CouchbaseLiteException {
try {
ContentValues args = new ContentValues();
args.put("sequence", sequence);
args.put("filename", name);
if (key != null) {
args.put("key", key.getBytes());
args.put("length", attachments.getSizeOfBlob(key));
}
args.put("type", contentType);
args.put("revpos", revpos);
long result = database.insert("attachments", null, args);
if (result == -1) {
String msg = "Insert attachment failed (returned -1)";
Log.e(Database.TAG, msg);
throw new CouchbaseLiteException(msg, Status.INTERNAL_SERVER_ERROR);
}
} catch (SQLException e) {
Log.e(Database.TAG, "Error inserting attachment", e);
throw new CouchbaseLiteException(e, Status.INTERNAL_SERVER_ERROR);
}
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revpos, key, key != null ? attachments.getSizeOfBlob(key): -1, AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, -1);
}

private void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key, long length) throws CouchbaseLiteException {
private void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key, long length, AttachmentInternal.AttachmentEncoding encoding, long encodedLength) throws CouchbaseLiteException {
try {
ContentValues args = new ContentValues();
args.put("sequence", sequence);
args.put("filename", name);
args.put("key", key.getBytes());
args.put("length", length);
args.put("type", contentType);
args.put("revpos", revpos);
if (key != null) {
args.put("key", key.getBytes());
}
if (length >= 0) {
args.put("length", length);
}
if(encoding == AttachmentInternal.AttachmentEncoding.AttachmentEncodingGZIP) {
args.put("encoding", encoding.ordinal());
if (encodedLength >= 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have an assert statement here to ensure that there is no case that the encodedLength < 0 when gzip encoding is specified?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encodedLength is information only. In case encodedLength is less than 0, the code does not store the value. It will set null value in encoded-length column in SQLite attachments table.

args.put("encoded_length", encodedLength);
}
}
long result = database.insert("attachments", null, args);
if (result == -1) {
String msg = "Insert attachment failed (returned -1)";
Expand All @@ -2924,6 +2929,7 @@ private void insertAttachmentForSequenceWithNameAndType(long sequence, String na
throw new CouchbaseLiteException(e, Status.INTERNAL_SERVER_ERROR);
}
}

/**
* @exclude
*/
Expand Down Expand Up @@ -3116,7 +3122,7 @@ public Map<String,Object> getAttachmentsDictForSequenceWithContent(long sequence

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

if(!cursor.moveToNext()) {
return null;
Expand All @@ -3127,7 +3133,10 @@ public Map<String,Object> getAttachmentsDictForSequenceWithContent(long sequence
while(!cursor.isAfterLast()) {

boolean dataSuppressed = false;
int length = cursor.getInt(3);
int length = cursor.getInt(4);

AttachmentInternal.AttachmentEncoding encoding = AttachmentInternal.AttachmentEncoding.values()[cursor.getInt(3)];
int encodedLength = cursor.getInt(5);

byte[] keyData = cursor.getBlob(1);
BlobKey key = new BlobKey(keyData);
Expand All @@ -3140,39 +3149,42 @@ public Map<String,Object> getAttachmentsDictForSequenceWithContent(long sequence
}
else {
byte[] data = attachments.blobForKey(key);

if(data != null) {
dataBase64 = Base64.encodeBytes(data); // <-- very expensive
}
else {
Log.w(Database.TAG, "Error loading attachment. Sequence: %s", sequence);
}

}
}

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

Map<String, Object> attachment = new HashMap<String, Object>();



if(!(dataBase64 != null || dataSuppressed)) {
if (!(dataBase64 != null || dataSuppressed)) {
attachment.put("stub", true);
}

if(dataBase64 != null) {
if (dataBase64 != null) {
attachment.put("data", dataBase64);
}

if (dataSuppressed == true) {
attachment.put("follows", true);
}

attachment.put("digest", digestString);
String contentType = cursor.getString(2);
attachment.put("content_type", contentType);
if (encodingStr != null) {
attachment.put("encoding", encodingStr);
}
attachment.put("length", length);
attachment.put("revpos", cursor.getInt(4));
if (encodingStr != null && encodedLength >= 0) {
attachment.put("encoded_length", encodedLength);
}
attachment.put("revpos", cursor.getInt(6));

String filename = cursor.getString(0);
result.put(filename, attachment);
Expand Down Expand Up @@ -4262,7 +4274,7 @@ Map<String, AttachmentInternal> getAttachmentsFromRevision(RevisionInternal rev,
// If there's inline attachment data, decode and store it:
byte[] newContents;
try {
newContents = Base64.decode(newContentBase64);
newContents = Base64.decode(newContentBase64, Base64.DONT_GUNZIP);
} catch (IOException e) {
throw new CouchbaseLiteException(e, Status.BAD_ENCODING);
}
Expand Down
25 changes: 24 additions & 1 deletion src/main/java/com/couchbase/lite/replicator/PusherInternal.java
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,15 @@ else if (attachment.containsKey("content-type")) {
" issue #80): %s", attachment);
}

FileBody fileBody = new FileBody(file, contentType);
// NOTE: Content-Encoding might not be necessary to set. Apache FileBody does not set Content-Encoding.
// FileBody always return null for getContentEncoding(), and Content-Encoding header is not set in multipart
// CBL iOS: https://github.com/couchbase/couchbase-lite-ios/blob/feb7ff5eda1e80bd00e5eb19f1d46c793f7a1951/Source/CBL_Pusher.m#L449-L452
String contentEncoding = null;
if(attachment.containsKey("encoding")){
contentEncoding = (String)attachment.get("encoding");
}

FileBody fileBody = new CustomFileBody(file, contentType, contentEncoding);
multiPart.addPart(attachmentKey, fileBody);
}

Expand Down Expand Up @@ -714,4 +722,19 @@ private static int findCommonAncestor(RevisionInternal rev, List<String> possibl

return generation;
}

// CustomFileBody to support contentEncoding. FileBody returns always null for getContentEncoding()
private static class CustomFileBody extends FileBody {
private String contentEncoding = null;

public CustomFileBody(final File file, final String mimeType, final String contentEncoding) {
super(file, mimeType);
this.contentEncoding = contentEncoding;
}

@Override
public String getContentEncoding() {
return contentEncoding;
}
}
}