/**
* Copyright (c) 2016 Couchbase, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package com.couchbase.lite.syncgateway;
import com.couchbase.lite.Attachment;
import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.Database;
import com.couchbase.lite.DatabaseOptions;
import com.couchbase.lite.Document;
import com.couchbase.lite.LiteTestCaseWithDB;
import com.couchbase.lite.UnsavedRevision;
import com.couchbase.lite.replicator.Replication;
import com.couchbase.lite.support.Base64;
import com.couchbase.lite.util.Log;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Created by hideki on 5/7/15.
*/
public class GzippedAttachmentTest extends LiteTestCaseWithDB {
public static final String TAG = "GzippedAttachmentTest";
@Override
protected void setUp() throws Exception {
super.setUp();
if (!syncgatewayTestsEnabled()) {
return;
}
}
/**
* https://github.com/couchbase/couchbase-lite-android/issues/197
* Gzipped attachment support with Replicator does not seem to be working
* <p/>
* https://github.com/couchbase/couchbase-lite-android/blob/master/src/androidTest/java/com/couchbase/lite/replicator/ReplicationTest.java#L2071
*/
public void testGzippedAttachment() throws Exception {
if (!syncgatewayTestsEnabled()) {
return;
}
Database pushDB = manager.getDatabase("pushdb");
Database pullDB = manager.getDatabase("pulldb");
String attachmentName = "attachment.png";
// 1. store attachment with doc
// 1.a load attachment data from asset
InputStream attachmentStream = getAsset(attachmentName);
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
IOUtils.copy(attachmentStream, baos);
baos.close();
attachmentStream.close();
byte[] bytes = baos.toByteArray();
// 1.b apply GZIP + Base64
String attachmentBase64 = Base64.encodeBytes(bytes, Base64.GZIP);
// 1.c attachment Map object
Map<String, Object> attachmentMap = new HashMap<String, Object>();
attachmentMap.put("content_type", "image/png");
attachmentMap.put("data", attachmentBase64);
attachmentMap.put("encoding", "gzip");
attachmentMap.put("length", bytes.length);
// 1.d attachments Map object
Map<String, Object> attachmentsMap = new HashMap<String, Object>();
attachmentsMap.put(attachmentName, attachmentMap);
// 1.e document property Map object
Map<String, Object> propsMap = new HashMap<String, Object>();
propsMap.put("_attachments", attachmentsMap);
// 1.f store document into database
Document putDoc = pushDB.createDocument();
putDoc.putProperties(propsMap);
String docId = putDoc.getId();
URL remote = getReplicationURL();
// push
final CountDownLatch latch1 = new CountDownLatch(1);
Replication pusher = pushDB.createPushReplication(remote);
pusher.addChangeListener(new Replication.ChangeListener() {
@Override
public void changed(Replication.ChangeEvent event) {
Log.e(TAG, "push 1:" + event.toString());
if (event.getCompletedChangeCount() > 0) {
latch1.countDown();
}
}
});
runReplication(pusher);
assertTrue(latch1.await(30, TimeUnit.SECONDS));
// pull
Replication puller = pullDB.createPullReplication(remote);
final CountDownLatch latch2 = new CountDownLatch(1);
puller.addChangeListener(new Replication.ChangeListener() {
@Override
public void changed(Replication.ChangeEvent event) {
Log.e(TAG, "pull 1:" + event.toString());
if (event.getCompletedChangeCount() > 0) {
latch2.countDown();
}
}
});
runReplication(puller);
assertTrue(latch2.await(30, TimeUnit.SECONDS));
Log.e(TAG, "Fetching doc1 via id: " + docId);
Document pullDoc = pullDB.getDocument(docId);
assertNotNull(pullDoc);
assertTrue(pullDoc.getCurrentRevisionId().startsWith("1-"));
Attachment attachment = pullDoc.getCurrentRevision().getAttachment(attachmentName);
assertEquals(bytes.length, attachment.getLength());
assertEquals("image/png", attachment.getContentType());
assertEquals("gzip", attachment.getMetadata().get("encoding"));
InputStream is = attachment.getContent();
byte[] receivedBytes = getBytesFromInputStream(is);
assertEquals(bytes.length, receivedBytes.length);
is.close();
assertTrue(Arrays.equals(bytes, receivedBytes));
pushDB.close();
pullDB.close();
pushDB.delete();
pullDB.delete();
}
private static byte[] getBytesFromInputStream(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 8];
int len = 0;
try {
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
os.flush();
} catch (IOException e) {
Log.e(Log.TAG, "is.read(buffer) or os.flush() error", e);
return null;
}
return os.toByteArray();
}
public void testImageAttachmentReplication() throws Exception {
if (!syncgatewayTestsEnabled()) {
return;
}
URL remote = getReplicationURL();
Database pushDB = getDatabase("pushdb");
pushDB.delete();
pushDB = getDatabase("pushdb");
Database pullDB = getDatabase("pulldb");
pullDB.delete();
pullDB = getDatabase("pulldb");
// Create a document with an image attached:
Map<String, Object> props = new HashMap<String, Object>();
props.put("foo", "bar");
Document doc = pushDB.createDocument();
doc.putProperties(props);
UnsavedRevision newRev = doc.createRevision();
newRev.setAttachment("attachment", "image/png", getAsset("attachment.png"));
newRev.save();
String docId = doc.getId();
// Push:
final CountDownLatch latch1 = new CountDownLatch(1);
Replication pusher = pushDB.createPushReplication(remote);
pusher.addChangeListener(new Replication.ChangeListener() {
@Override
public void changed(Replication.ChangeEvent event) {
Log.e(TAG, "push 1:" + event.toString());
if (event.getCompletedChangeCount() > 0) {
latch1.countDown();
}
}
});
runReplication(pusher);
assertTrue(latch1.await(5, TimeUnit.SECONDS));
// Pull:
Replication puller = pullDB.createPullReplication(remote);
final CountDownLatch latch2 = new CountDownLatch(1);
puller.addChangeListener(new Replication.ChangeListener() {
@Override
public void changed(Replication.ChangeEvent event) {
Log.e(TAG, "pull 1:" + event.toString());
if (event.getCompletedChangeCount() > 0) {
latch2.countDown();
}
}
});
runReplication(puller);
assertTrue(latch2.await(5, TimeUnit.SECONDS));
// Check document:
Document pullDoc = pullDB.getDocument(docId);
assertNotNull(pullDoc);
assertTrue(pullDoc.getCurrentRevisionId().startsWith("2-"));
// Check attachment:
Attachment attachment = pullDoc.getCurrentRevision().getAttachment("attachment");
byte[] originalBytes = getBytesFromInputStream(getAsset("attachment.png"));
assertEquals(originalBytes.length, attachment.getLength());
assertEquals("image/png", attachment.getContentType());
assertTrue(Arrays.equals(originalBytes, getBytesFromInputStream(attachment.getContent())));
pushDB.close();
pullDB.close();
pushDB.delete();
pullDB.delete();
}
private Database getDatabase(String name) throws CouchbaseLiteException {
DatabaseOptions options = new DatabaseOptions();
options.setCreate(true);
if (isEncryptionTestEnabled())
options.setEncryptionKey("seekrit");
return manager.openDatabase(name, options);
}
}