All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.lite.BlobStoreWriter Maven / Gradle / Ivy

package com.couchbase.lite;

import com.couchbase.lite.support.Base64;
import com.couchbase.lite.support.security.SymmetricKey;
import com.couchbase.lite.support.security.SymmetricKeyException;
import com.couchbase.lite.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Lets you stream a large attachment to a BlobStore asynchronously, e.g. from a network download.
 *
 * @exclude
 */
public class BlobStoreWriter {

    /**
     * The underlying blob store where it should be stored.
     */
    private BlobStore store = null;

    /**
     * The number of bytes in the blob.
     */
    private int length = 0;

    /**
     * After finishing, this is the key for looking up the blob through the CBL_BlobStore.
     */
    private BlobKey blobKey = null;

    /**
     * After finishing, store md5 getDigest result here
     */
    private byte[] md5DigestResult = null;

    /**
     * Message getDigest for sha1 that is updated as data is appended
     */
    private MessageDigest sha1Digest = null;
    private MessageDigest md5Digest = null;

    private BufferedOutputStream outStream = null;
    private File tempFile = null;

    /**
     * An encryptor for encrypting the blob content.
     */
    private SymmetricKey.Encryptor encryptor = null;

    public BlobStoreWriter(BlobStore store) {
        this.store = store;
        try {
            sha1Digest = MessageDigest.getInstance("SHA-1");
            sha1Digest.reset();
            md5Digest = MessageDigest.getInstance("MD5");
            md5Digest.reset();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
        try {
            openTempFile();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        SymmetricKey encryptionKey = store.getEncryptionKey();
        if (encryptionKey != null) {
            try {
                encryptor = encryptionKey.createEncryptor();
            } catch (SymmetricKeyException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    private void openTempFile() throws IOException {
        File tempDir = store.tempDir();
        String filePrefix = BlobStore.TMP_FILE_PREFIX;
        String fileExtension = BlobStore.TMP_FILE_EXTENSION;
        tempFile = File.createTempFile(filePrefix, fileExtension, tempDir);
        outStream = new BufferedOutputStream(new FileOutputStream(tempFile));
    }

    /**
     * Appends data to the blob. Call this when new data is available.
     */
    public void appendData(byte[] data) throws IOException, SymmetricKeyException {
        if (data == null)
            return;
        appendData(data, 0, data.length);
    }

    public void appendData(byte[] data, int off, int len) throws IOException, SymmetricKeyException {
        if (data == null)
            return;
        length += len;
        sha1Digest.update(data, off, len);
        md5Digest.update(data, off, len);
        if (encryptor != null) {
            data = encryptor.encrypt(data, off, len);
            if (data != null)
                outStream.write(data, 0, data.length);
        } else
            outStream.write(data, off, len);
    }

    void appendInputStream(InputStream inputStream) throws IOException, SymmetricKeyException {
        byte[] buffer = new byte[1024];
        int len;
        length = 0;
        try {
            while ((len = inputStream.read(buffer)) != -1) {
                appendData(buffer, 0, len);
            }
        } finally {
            try {
                // Question: Should this method close the stream?
                if (inputStream != null)
                    inputStream.close();
            } catch (IOException e) {
                Log.w(Log.TAG_BLOB_STORE, "Exception closing input stream", e);
            }
        }
    }

    /**
     * Call this after all the data has been added.
     */
    public void finish() throws IOException, SymmetricKeyException {
        if (outStream != null) {
            if (encryptor != null)
                outStream.write(encryptor.encrypt(null));

            // FileOutputStream is also closed cascadingly
            outStream.close();
            outStream = null;

            // Only create the key if we got all the data successfully
            blobKey = new BlobKey(sha1Digest.digest());
            md5DigestResult = md5Digest.digest();
        }
    }

    /**
     * Call this to cancel before finishing the data.
     */
    public void cancel() {
        try {
            // FileOutputStream is also closed cascadingly
            if (outStream != null) {
                outStream.close();
                outStream = null;
            }
            // Clear encryptor:
            encryptor = null;
        } catch (IOException e) {
            Log.w(Log.TAG_BLOB_STORE, "Exception closing buffered output stream", e);
        }
        tempFile.delete();
    }

    /**
     * Installs a finished blob into the store.
     */
    public boolean install() {
        if (tempFile == null)
            return true;  // already installed
        // Move temp file to correct location in blob store:
        String destPath = store.getRawPathForKey(blobKey);
        File destPathFile = new File(destPath);
        if (tempFile.renameTo(destPathFile))
            // If the move fails, assume it means a file with the same name already exists; in that
            // case it must have the identical contents, so we're still OK.
            tempFile = null;
        else
            cancel();
        return true;
    }

    public String mD5DigestString() {
        String base64Md5Digest = Base64.encodeBytes(md5DigestResult);
        return String.format("md5-%s", base64Md5Digest);
    }

    public String sHA1DigestString() {
        String base64Sha1Digest = Base64.encodeBytes(blobKey.getBytes());
        return String.format("sha1-%s", base64Sha1Digest);
    }

    public int getLength() {
        return length;
    }

    public BlobKey getBlobKey() {
        return blobKey;
    }

    public String getFilePath() {
        return tempFile.getPath();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy