org.mhisoft.wallet.service.AttachmentService.java Source code

Java tutorial

Introduction

Here is the source code for org.mhisoft.wallet.service.AttachmentService.java

Source

/*
 *
 *  * Copyright (c) 2014- MHISoft LLC and/or its affiliates. All rights reserved.
 *  * Licensed to MHISoft LLC under one or more contributor
 *  * license agreements. See the NOTICE file distributed with
 *  * this work for additional information regarding copyright
 *  * ownership. MHISoft LLC licenses this file to you 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 org.mhisoft.wallet.service;

import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;

import org.apache.commons.io.IOUtils;
import org.mhisoft.common.util.CompressionUtil;
import org.mhisoft.common.util.FileUtils;
import org.mhisoft.common.util.StringUtils;
import org.mhisoft.common.util.security.PBEEncryptor;
import org.mhisoft.wallet.model.FileAccessEntry;
import org.mhisoft.wallet.model.FileAccessFlag;
import org.mhisoft.wallet.model.FileAccessTable;
import org.mhisoft.wallet.model.WalletItem;
import org.mhisoft.wallet.model.WalletModel;
import org.mhisoft.wallet.view.DialogUtils;

/**
 * Description:
 *
 * @author Tony Xue
 * @since Mar, 2017
 */
public class AttachmentService {

    private static final Logger logger = Logger.getLogger(AttachmentService.class.getName());

    static DecimalFormat intDF = new DecimalFormat("###,###");

    public String getAttachmentFileName(String walletFileName) {
        String[] parts = FileUtils.splitFileParts(walletFileName);
        String attachmentFileName = parts[0] + File.separator + parts[1] + "_attachments." + parts[2];
        return attachmentFileName;
    }

    public void saveAttachments(final String filename, final WalletModel model, final PBEEncryptor encryptor) {
        //iterate the model item's FileAccessEntry
        boolean deleted = false;
        File f = new File(filename);
        if (!f.exists()) {
            /* create new store */
            newAttachmentStore(filename, model, encryptor);
        } else {

            FileAccessTable t = new FileAccessTable();
            double deleteCount = 0, totalCount = 0;
            for (WalletItem item : model.getItemsFlatList()) {
                if (item.getAttachmentEntry() != null) {
                    totalCount++;

                    if (item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.Delete
                            || item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.Update)
                        //these are new deleted attachments
                        deleteCount++;

                }
            }

            //delete count need to add the orphan records (marked as DELETE) in the attachment store.
            deleteCount += model.getDeletedEntriesInStore();

            if (deleteCount == totalCount) {
                //remove it.
                deleteAttachmentStore(filename);
                deleted = true;
            } else if (deleteCount / totalCount > 0.3) {
                /* transfer to a new store */
                logger.fine("\n");
                logger.fine("compactAttachmentStore");
                compactAttachmentStore(filename, model, encryptor);
            } else {
                /* append to existing store */
                logger.fine("\n");
                logger.fine("appendAttachmentStore");
                appendAttachmentStore(filename, model, encryptor);
            }
        }

        FileAccessTable t = null;
        if (!deleted) {
            logger.fine("file size: " + intDF.format(new File(filename).length()));
            //Refresh / Reload the wallet item file access entries after save.
            t = read(filename, encryptor);
            if (t != null)
                model.setDeletedEntriesInStore(t.getDeletedEntries());

        }

        //reset the item attachment entries.
        //drive from item.
        for (WalletItem item : model.getItemsFlatList()) {
            item.setAttachmentEntry(t == null ? null : t.getEntry(item.getSysGUID()));
            if (item.getAttachmentEntry() != null)
                item.getAttachmentEntry().setAccessFlag(FileAccessFlag.None);
            item.setNewAttachmentEntry(null);
        }

    }

    /**
     * Read the attachments from the old attachment store and write to the new attachment store.
     * It is really copy attachments from one store to another.
     * use case: export, change password;
     *     store version change
     *
     *     DELETED   entries are not transfered.
     *
     * @param oldStoreName      The old store name, needed to read the attachment content out.
     * @param model             The old model, need the encryptor from it to read old attachment for transfer.
     * @param expModel          the new store model, items list is from the new model.
     * @param expModelEncryptor The encryptor for writing the new attachment store.
     * @param resetModelAttachmentEntries  do not reset for upgrade scenario.
     */

    public boolean transferAttachmentStore(final String oldStoreName, String newStoreName, final WalletModel model,
            final WalletModel expModel, final PBEEncryptor expModelEncryptor,
            final boolean resetModelAttachmentEntries) {

        logger.fine("transferAttachmentStore()");

        File newFile = new File(newStoreName);
        if (newFile.exists()) {
            if (!newFile.delete()) {
                DialogUtils.getInstance().error("Can't delete the tmp file:" + newStoreName);
                return false;
            }
        }

        FileAccessTable t = new FileAccessTable();
        double deleteCount = 0, totalCount = 0;
        for (WalletItem item : expModel.getItemsFlatList()) {
            if (item.getAttachmentEntry() != null) {
                totalCount++;

                if (item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.Delete
                        || item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.Update)
                    //these are new deleted attachments
                    deleteCount++;
                else if (item.getAttachmentEntry().getFileName() != null)
                    t.addEntry(item.getAttachmentEntry());

            }

        }

        //delete count need to add the orphan records (marked as DELETE) in the attachment store.
        deleteCount += expModel.getDeletedEntriesInStore();

        if (deleteCount == totalCount) {
            //all deleted or updated.
            //nothing new need to be transfered.
            return false;
        }

        DataOutputStream dataOut = null;
        if (t.getSize() > 0) {
            //new store
            //write it out
            try {
                dataOut = new DataOutputStream(new FileOutputStream(new File(newStoreName)));

                //write the total number of entries first
                /*#0*/
                dataOut.writeInt(t.getEntries().size());
                //itemStartPos = 4;

                writeFileEntries(expModel, true, oldStoreName, 4, dataOut, t, model.getEncryptorForRead(),
                        expModelEncryptor);

                dataOut.flush();
                dataOut.close();

            } catch (IOException e) {
                e.printStackTrace();
                DialogUtils.getInstance().error("transferAttachmentStore failed:", e.getMessage());
            } finally {
                if (dataOut != null)
                    try {
                        dataOut.close();
                    } catch (IOException e) {
                        //e.printStackTrace();
                    }
            }

        }

        if (resetModelAttachmentEntries) {
            //now clear the access flag on the item
            for (WalletItem item : model.getItemsFlatList()) {
                if (item.getAttachmentEntry() != null && item.getAttachmentEntry().getFile() != null //
                        && item.getAttachmentEntry().getAccessFlag() != null) {
                    item.getAttachmentEntry().setAccessFlag(FileAccessFlag.None);
                }
            }
        }

        return t.getSize() > 0;

    }

    /**
     * Create a new attachment store.
     *
     * @param filename
     * @param model
     * @param encryptor
     */
    public void newAttachmentStore(final String filename, final WalletModel model, final PBEEncryptor encryptor) {

        FileAccessTable t = new FileAccessTable();
        for (WalletItem item : model.getItemsFlatList()) {
            if (item.getAttachmentEntry() != null && item.getAttachmentEntry().getFileName() != null)
                t.addEntry(item.getAttachmentEntry());
        }

        DataOutputStream dataOut = null;
        if (t.getSize() > 0) {
            //new store
            //write it out
            try {
                dataOut = new DataOutputStream(new FileOutputStream(new File(filename)));

                //write the total number of entries first
                /*#0*/
                dataOut.writeInt(t.getEntries().size());
                //itemStartPos = 4;

                writeFileEntries(model, false, null, 4, dataOut, t, model.getEncryptorForRead(), encryptor);

                dataOut.flush();
            } catch (IOException e) {
                e.printStackTrace();
                DialogUtils.getInstance().error("Error writing attachment entries.", e.getMessage());
            } finally {
                if (dataOut != null)
                    try {
                        dataOut.close();
                    } catch (IOException e) {
                        //e.printStackTrace();
                    }
            }

            //now clear the access flag on the item
            for (WalletItem item : model.getItemsFlatList()) {
                if (item.getAttachmentEntry() != null && item.getAttachmentEntry().getFile() != null //
                        && item.getAttachmentEntry().getAccessFlag() != null) {
                    item.getAttachmentEntry().setAccessFlag(FileAccessFlag.None);
                }
            }

        }
    }

    /**
     * Append to the existing store for Merged and Created/Updated attachments.
     * @param filename
     * @param model
     * @param encryptor
     */
    protected void appendAttachmentStore(final String filename, final WalletModel model,
            final PBEEncryptor encryptor) {

        FileAccessTable t = new FileAccessTable();
        for (WalletItem item : model.getItemsFlatList()) {
            if (item.getAttachmentEntry() == null)
                continue;
            if (item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.Merge) {
                t.addEntry(item.getAttachmentEntry());
            } else if (FileAccessFlag.Create == item.getAttachmentEntry().getAccessFlag()
                    || FileAccessFlag.Update == item.getAttachmentEntry().getAccessFlag()) {

                if (item.getNewAttachmentEntry() != null && item.getNewAttachmentEntry().getFile() != null) {
                    t.addEntry(item.getNewAttachmentEntry());
                } else if (item.getAttachmentEntry().getFile() != null) {
                    t.addEntry(item.getAttachmentEntry());
                }
            }
        }

        RandomAccessFile attachmentFileStore = null;
        try {
            attachmentFileStore = new RandomAccessFile(filename, "rw");

            if (t.getSize() > 0) {

                //write the total number of entries first
                int entriesCount = attachmentFileStore.readInt();

                //add to be appended ones
                entriesCount += t.getSize();
                attachmentFileStore.seek(0);
                attachmentFileStore.writeInt(entriesCount);

                //seek to the end
                long itemStartPos = attachmentFileStore.length();
                attachmentFileStore.seek(itemStartPos);

                //append new entries to the end of the store.
                writeFileEntries(model, false, filename, itemStartPos, attachmentFileStore, t,
                        model.getEncryptorForRead(), encryptor);

            }

            /*marked the deleted entries **/
            for (WalletItem item : model.getItemsFlatList()) {
                if (item.getAttachmentEntry() == null)
                    continue;
                if (FileAccessFlag.Delete == item.getAttachmentEntry().getAccessFlag() //the attachment is deleted
                        //the entry had the content saved in the store, now it is replaced. the new content is appended to the end of the file.
                        // the file entry at the old position needs to be marked as DELETE.
                        || (FileAccessFlag.Update == item.getAttachmentEntry().getAccessFlag()
                                && item.getAttachmentEntry().getEncSize() > 0)
                                && item.getAttachmentEntry().getPosition() > 0) {

                    attachmentFileStore.seek(item.getAttachmentEntry().getPosition() + 40);
                    attachmentFileStore.writeInt(FileAccessFlag.Delete.ordinal());
                }
            }

            attachmentFileStore.close();
            attachmentFileStore = null;

        } catch (IOException e) {
            e.printStackTrace();
            DialogUtils.getInstance().error("Error writing attachment entries.", e.getMessage());
        } finally {
            if (attachmentFileStore != null)
                try {
                    attachmentFileStore.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
        }

    } //appendAttachmentStore

    protected void deleteAttachmentStore(final String oldStorefName) {
        new File(oldStorefName).delete();
    }

    /**
     * abandon the old store and transfer everthing to the wnew store.
     * The same model for both old and new store.
     *
     * @param oldStorefName
     * @param model
     * @param encryptor
     */
    protected void compactAttachmentStore(final String oldStorefName, final WalletModel model,
            final PBEEncryptor encryptor) {
        String newStoreName = oldStorefName + ".tmp";
        File newFile = new File(newStoreName);
        if (newFile.exists()) {
            if (!newFile.delete()) {
                DialogUtils.getInstance().error("Can't delete the tmp file:" + newStoreName);
            }
        }

        FileAccessTable t = new FileAccessTable();
        for (WalletItem item : model.getItemsFlatList()) {
            if (item.getAttachmentEntry() == null)
                continue;

            if (item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.None) {
                //no change , need to transfer to the new file.
                t.addEntry(item.getAttachmentEntry());
            } else if (item.getAttachmentEntry().getAccessFlag() == FileAccessFlag.Merge) {
                t.addEntry(item.getAttachmentEntry());
            } else if (FileAccessFlag.Create == item.getAttachmentEntry().getAccessFlag()
                    || FileAccessFlag.Update == item.getAttachmentEntry().getAccessFlag()) {
                if (item.getAttachmentEntry() != null) {
                    if (item.getAttachmentEntry().getNewEntry() != null)
                        t.addEntry(item.getNewAttachmentEntry());
                    else
                        item.getAttachmentEntry();
                }
            }
        }

        RandomAccessFile attachmentFileStore = null;
        if (t.getSize() > 0) {
            try {
                attachmentFileStore = new RandomAccessFile(newStoreName, "rw");
                attachmentFileStore.seek(0);
                attachmentFileStore.writeInt(t.getSize());

                writeFileEntries(model, true, oldStorefName, 4, attachmentFileStore, t, model.getEncryptorForRead(),
                        encryptor);

                attachmentFileStore.close();
                attachmentFileStore = null;

                //now do the swap of the store to the new one.
                new File(oldStorefName).delete();
                newFile.renameTo(new File(oldStorefName));

            } catch (IOException e) {
                e.printStackTrace();
                DialogUtils.getInstance().error("compactAttachmentStore() failed", e.getMessage());
            } finally {
                if (attachmentFileStore != null)
                    try {
                        attachmentFileStore.close();
                    } catch (IOException e) {
                        //e.printStackTrace();
                    }
            }
        } else {
            //need to handle all images are deleted
            //just remove the old store.
            new File(oldStorefName).delete();
        }

    }

    /*
        total numbers of entries (int)
    entry[0] :
        
       0:      UUID: 40          --------> start pos here
      40:      access flag : 4
      44:      position (long) : 8
      52:      cipherParam size : int, 4 bytes
      56:      cipherParameters body : var size C
               filename (without path) encrypted string F
                  size of the content: int, 4 bytes
               encrypted contents: var size
        
                 --------> next entry start pos here
        
        
        
     */

    /**
     * @param transferStoreMode a new store will be created because the old one has t
     * @param oldStoreFileName  provide along with the transferStoreMode. for read the contents to be transfered to the new store  when doing compacting.
     * @param itemStartPos      start position for the data output file.
     * @param dataOut           data output file/stream
     * @param t                 the  FileAccessTable contains the entries to write.
     * @param encryptor         The encryptor
     * @throws IOException
     */
    protected void writeFileEntries(WalletModel model, boolean transferStoreMode, String oldStoreFileName,
            final long itemStartPos, DataOutput dataOut, final FileAccessTable t,
            final PBEEncryptor oldEncryptorForRead, final PBEEncryptor encryptor) throws IOException {

        long pos = itemStartPos;

        //write it out
        //dataOut = new DataOutputStream(new FileOutputStream(new File(outoutFIleName)));

        //write the total number of entries first
        /*#0*/
        //dataOut.writeInt(t.getEntries().size());
        //itemStartPos = 4;
        String impAttachmentStoreName = null;
        if (model.getImpModel() != null)
            impAttachmentStoreName = getAttachmentFileName(model.getImpModel().getVaultFileName());

        //write each entry
        for (int i = 0; i < t.getEntries().size(); i++) {

            FileAccessEntry fileAccessEntry = t.getEntries().get(i);

            //not handleing the deleted entries.
            if (fileAccessEntry.getAccessFlag() == FileAccessFlag.Delete) {
                continue;
            }

            logger.fine("------------------");
            logger.fine("Write entry: " + fileAccessEntry.getGUID() + "-" + fileAccessEntry.getFileName());
            logger.fine("\t access flag: " + fileAccessEntry.getAccessFlag());
            logger.fine("\t start pos: " + pos);

            fileAccessEntry.setPosition(pos);

            /*  UUID*/
            int uuidSize = FileUtils.writeString(dataOut, fileAccessEntry.getGUID());
            pos += 40;

            /*  accessflag */
            dataOut.writeInt(0);
            pos += 4;

            /* position */
            dataOut.writeLong(fileAccessEntry.getPosition());
            pos += 8;

            /* write filename encrypted */
            String strFName = FileUtils.getFileNameWithoutPath(fileAccessEntry.getFileName());
            logger.fine("\t write file name: " + strFName);
            byte[] _byteFileName = StringUtils.getBytes(strFName);
            pos = writeEncryptedContent(_byteFileName, encryptor, dataOut, pos);
            logger.fine("\t pos: " + pos);

            /* Attachment body */
            byte[] attachmentBytes;

            if (fileAccessEntry.getAccessFlag() == FileAccessFlag.Merge && fileAccessEntry.getEncSize() > 0) {
                if (impAttachmentStoreName == null)
                    throw new IOException("impAttachmentStoreName is not set.");
                //depends on the import model version, it might or might not be compressed
                //test case: import an old version data file with attachments.
                byte[] _impBytes = readCompressedFileContent(model.getImpModel().getCurrentDataFileVersion(),
                        impAttachmentStoreName, fileAccessEntry, model.getImpModel().getEncryptor());
                if (model.getImpModel().getCurrentDataFileVersion() >= WalletModel.LATEST_DATA_VERSION) {
                    //it is compressed.
                    attachmentBytes = _impBytes;
                } else {
                    //not compressed, need to compress it.
                    attachmentBytes = CompressionUtil.getCompressedBytes(new ByteArrayInputStream(_impBytes));
                }

            } else if (transferStoreMode && fileAccessEntry.getAccessFlag() == FileAccessFlag.None
                    && fileAccessEntry.getEncSize() > 0) {
                // no change, this is an transfer to the new store. need to read the filecontent from the old store.
                // could be upgrade scenario as well.
                // should be able to skip the compression-decompression process.
                byte[] _bytes = readCompressedFileContent(model.getCurrentDataFileVersion(), oldStoreFileName,
                        fileAccessEntry, oldEncryptorForRead);

                if (model.getCurrentDataFileVersion() < WalletModel.LATEST_DATA_VERSION) {
                    //it was not compressed.
                    attachmentBytes = CompressionUtil.getCompressedBytes(new ByteArrayInputStream(_bytes));
                    logger.fine("\t transfer from old store. compress contennt. before " + _bytes.length
                            + ", after:" + attachmentBytes.length);
                } else
                    attachmentBytes = _bytes;

            } else {
                /* for CREATE OR UPDATE or a new store */
                //read from file
                //since v14 , start to compress the contents.
                File f = fileAccessEntry.getFile();
                FileInputStream sourceInputStream = new FileInputStream(f);
                if (model.getCurrentDataFileVersion() < WalletModel.LATEST_DATA_VERSION) {
                    //for testing, write the old version format. un-compressed
                    attachmentBytes = FileUtils.readFile(sourceInputStream);
                    logger.fine("\t write entry by reading from file, not compressed: " + fileAccessEntry.getFile()
                            + ", original size:" + fileAccessEntry.getFile().length());
                } else {
                    attachmentBytes = CompressionUtil.getCompressedBytes(sourceInputStream);
                    logger.fine("\t write entry by reading from file: " + fileAccessEntry.getFile()
                            + ", original size:" + fileAccessEntry.getFile().length() + ", compressed size:"
                            + attachmentBytes.length);
                }
            }

            //encrypted the compressed bytes and write out.
            pos = writeEncryptedContent(attachmentBytes, encryptor, dataOut, pos);

        }

    }

    /**
     * Write the file content to the dataout, which is the attachment store.
     * @param content
     * @param encryptor
     * @param dataOut
     * @param pos
     * @return
     * @throws IOException
     */
    //return pos
    private long writeEncryptedContent(byte[] content, final PBEEncryptor encryptor, DataOutput dataOut, long pos)
            throws IOException {

        logger.fine("writeEncryptedContent");
        PBEEncryptor.EncryptionResult ret = encryptor.encrypt(content);
        byte[] _byteEncrypted = ret.getEncryptedData();

        /*  cipherParameters size 4 bytes*/
        byte[] cipherParameters = ret.getCipherParameters();
        dataOut.writeInt(cipherParameters.length); //length is 100 bytes
        //logger.fine("\t cipherParameters length: " + cipherParameters.length);
        pos += 4;

        /* cipherParameters body*/
        dataOut.write(cipherParameters);
        pos += cipherParameters.length;

        /*  size of the  content*/
        logger.fine("\t size of the content: " + _byteEncrypted.length);
        dataOut.writeInt(_byteEncrypted.length);
        pos += 4;

        /*   content */
        dataOut.write(_byteEncrypted);
        pos += _byteEncrypted.length;

        return pos;

    }

    class ReadContentVO {
        long pos;
        AlgorithmParameters algorithmParameters;

    }

    private ReadContentVO readCipherParameter(RandomAccessFile fileIn, long pos)
            throws IOException, NoSuchAlgorithmException {
        ReadContentVO ret = new ReadContentVO();
        ret.pos = pos;

        /*#3: ciperParameters size 4 bytes*/
        int cipherParametersLength = fileIn.readInt();
        ret.pos += 4;
        //logger.fine("read cipherParametersLength:" + cipherParametersLength);

        /*#4: cipherParameters body*/
        byte[] _byteCiper = new byte[cipherParametersLength];
        int readBytes = fileIn.read(_byteCiper);
        if (readBytes != cipherParametersLength)
            throw new RuntimeException("read " + readBytes + " bytes only, expected to read:" + _byteCiper);
        ret.pos += _byteCiper.length;

        ret.algorithmParameters = AlgorithmParameters.getInstance(PBEEncryptor.ALGORITHM);
        ret.algorithmParameters.init(_byteCiper);

        return ret;

    }

    public FileAccessTable read(String dataFile, final PBEEncryptor encryptor) {
        logger.fine("\n\nread attachment file:" + dataFile);
        FileAccessTable t = null;
        RandomAccessFile fileIn = null;
        try {
            File fIn = new File(dataFile);
            if (!fIn.exists()) {
                return t;
            }

            fileIn = new RandomAccessFile(dataFile, "rw");
            int numberOfEntries = fileIn.readInt();
            t = new FileAccessTable();
            long pos = 4;
            int readBytes = 0;
            for (int i = 0; i < numberOfEntries; i++) {

                fileIn.seek(pos);

                /* UUIID and position */
                String UUID = FileUtils.readString(fileIn);
                FileAccessEntry fileAccessEntry = new FileAccessEntry(UUID);
                pos += 40;
                logger.fine("Read entry, UUID:" + UUID);

                /*  accessflag */
                fileAccessEntry.setAccessFlag(FileAccessFlag.values[fileIn.readInt()]);
                pos += 4;
                logger.fine("\t access flag:" + fileAccessEntry.getAccessFlag());

                /*  pos */
                fileAccessEntry.setPosition(fileIn.readLong());
                pos += 8;
                logger.fine("\t position:" + fileAccessEntry.getPosition());

                /* read filename */
                ReadContentVO vo = readCipherParameter(fileIn, pos);
                pos = vo.pos;
                int encSize_FileName = fileIn.readInt();
                pos += 4;
                byte[] _encedBytes = new byte[encSize_FileName];
                fileIn.readFully(_encedBytes);
                pos += encSize_FileName;
                byte[] byte_filename = encryptor.decrypt(_encedBytes, vo.algorithmParameters);
                fileAccessEntry.setFileName(StringUtils.bytesToString(byte_filename));
                logger.fine("\t file name:" + fileAccessEntry.getFileName());

                /* attachment */
                vo = readCipherParameter(fileIn, pos);
                pos = vo.pos;
                fileAccessEntry.setAlgorithmParameters(vo.algorithmParameters);

                /*#5  size of the content (int): 4 bytes */
                int encSize = fileIn.readInt();
                pos += 4;

                fileAccessEntry.setPosOfContent(pos);
                fileAccessEntry.setEncSize(encSize);
                logger.fine("\t encSize:" + encSize);

                if (fileAccessEntry.getAccessFlag() != FileAccessFlag.Delete)
                    t.addEntry(fileAccessEntry);
                else {
                    logger.fine("\tentries is marked as deleted.");
                    t.deletedEntries++;
                }

                /* #6 file content */
                pos += encSize;

                /* delay read it on demand
                byte[] _encBytes =readFileContent(dataFile, pos, encSize ) ;
                byte[] fileContent = encryptor.decrypt(_encBytes, algorithmParameters);
                pos +=  fileContent.length;
                    
                fileAccessEntry.setFileContent(fileContent);
                fileAccessEntry.setSize(fileContent.length); //decrypted size.
                */

            }

            fileIn.close();
            fileIn = null;

        } catch (Exception e) {
            e.printStackTrace();
            DialogUtils.getInstance().error("Error occurred in reading attachments.", e.getMessage());
        } finally {
            if (fileIn != null)
                try {
                    fileIn.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }

        DebugUtil.append(
                "Attachment Store total entries:" + t.getSize() + "\n" + "Orphan records :" + t.deletedEntries);

        return t;
    }

    protected byte[] readCompressedFileContent(final int storeVersion, String fileStoreDataFile,
            FileAccessEntry entry, PBEEncryptor encryptor) {
        return _readFileContent(false, storeVersion, fileStoreDataFile, entry, encryptor);
    }

    /**
     * Read and decrypt the file content based on the mark on the entry.
     * It is also deflated if was compressed.
     * The fileAccessEntry file content and size will be set.
     *
     * @param fileStoreDataFile
     * @param entry
     * @param encryptor
     * @return
     */

    public byte[] readFileContent(final int storeVersion, String fileStoreDataFile, FileAccessEntry entry,
            PBEEncryptor encryptor) {
        return _readFileContent(true, storeVersion, fileStoreDataFile, entry, encryptor);
    }

    private byte[] _readFileContent(boolean decompress, final int storeVersion, String fileStoreDataFile,
            FileAccessEntry entry, PBEEncryptor encryptor) {

        try {
            RandomAccessFile fileStore = new RandomAccessFile(fileStoreDataFile, "rw");
            fileStore.seek(entry.getPosOfContent());
            byte[] _encedBytes = new byte[entry.getEncSize()];
            fileStore.readFully(_encedBytes);
            byte[] fileContent = encryptor.decrypt(_encedBytes, entry.getAlgorithmParameters());

            //after decrypt, deflate the compressed data
            if (decompress && storeVersion >= WalletModel.LATEST_DATA_VERSION) {
                GZIPInputStream decompressedStream = new GZIPInputStream(new ByteArrayInputStream(fileContent));
                fileContent = IOUtils.toByteArray(decompressedStream);
            }

            entry.setFileContent(fileContent);
            entry.setSize(fileContent.length); //decrypted size.

            fileStore.close();
            return fileContent;

        } catch (IOException e) {
            e.printStackTrace();
            DialogUtils.getInstance().error("Can't read " + fileStoreDataFile + ":" + e.toString());
        }
        return null;
    }

    //re-read the attachments from file and refresh the model items.
    public void reloadAttachments(final String vaultFileName, final WalletModel model) {
        String attachmetStoreName = getAttachmentFileName(vaultFileName);
        FileAccessTable t = read(attachmetStoreName, model.getEncryptor());

        // reset first
        for (WalletItem walletItem : model.getItemsFlatList()) {
            walletItem.setAttachmentEntry(null);
            walletItem.setNewAttachmentEntry(null);
        }

        //walk through the  FileAccessTable
        if (t != null && t.getEntries() != null) {
            for (FileAccessEntry fileAccessEntry : t.getEntries()) {

                WalletItem walletItem = model.findItem(fileAccessEntry.getGUID());
                if (walletItem != null) {
                    walletItem.setAttachmentEntry(fileAccessEntry);
                    walletItem.setNewAttachmentEntry(null);
                }
            }
        }
    }

}