package com.sromku.simple.storage;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.StatFs;
import com.sromku.simple.storage.helpers.ImmutablePair;
import com.sromku.simple.storage.helpers.OrderType;
import com.sromku.simple.storage.helpers.SizeUnit;
import com.sromku.simple.storage.security.SecurityUtil;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.Cipher;
/**
* Common class for internal and external storage implementations
*
* @author Roman Kushnarenko - sromku (sromku@gmail.com)
*
*/
abstract class AbstractDiskStorage implements Storage {
protected static final String UTF_8 = "UTF-8";
AbstractDiskStorage() {
}
protected SimpleStorageConfiguration getConfiguration() {
return SimpleStorage.getConfiguration();
}
@Override
public boolean createDirectory(String name) {
String path = buildPath(name);
// Check if the directory already exist
if (isDirectoryExists(path)) {
throw new RuntimeException("The direcory already exist. You can't override the existing one. Use createDirectory(String path, boolean override)");
}
File directory = new File(path);
// Create a new directory
boolean wasCreated = directory.mkdirs();
return wasCreated;
}
@Override
public boolean createDirectory(String name, boolean override) {
// If override==false, then don't override
if (!override) {
if (isDirectoryExists(name)) {
return true;
} else {
return createDirectory(name);
}
}
// Check if directory exists. If yes, then delete all directory
if (isDirectoryExists(name)) {
deleteDirectory(name);
}
// Create new directory
boolean wasCreated = createDirectory(name);
// If directory is already exist then wasCreated=false
if (!wasCreated) {
throw new RuntimeException("Couldn't create new direcory");
}
return true;
}
@Override
public boolean deleteDirectory(String name) {
String path = buildPath(name);
return deleteDirectoryImpl(path);
}
@Override
public boolean isDirectoryExists(String name) {
String path = buildPath(name);
return new File(path).exists();
}
@Override
public boolean createFile(String directoryName, String fileName, String content) {
return createFile(directoryName, fileName, content.getBytes());
}
@Override
public boolean createFile(String directoryName, String fileName, Storable storable) {
return createFile(directoryName, fileName, storable.getBytes());
}
@Override
public boolean createFile(String directoryName, String fileName, byte[] content) {
String path = buildPath(directoryName, fileName);
try {
OutputStream stream = new FileOutputStream(new File(path));
/*
* Check if needs to be encrypted. If yes, then encrypt it.
*/
if (getConfiguration().isEncrypted()) {
content = encrypt(content, Cipher.ENCRYPT_MODE);
}
stream.write(content);
stream.flush();
stream.close();
} catch (IOException e) {
throw new RuntimeException("Failed to create", e);
}
return true;
}
@Override
public boolean createFile(String directoryName, String fileName, Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
return createFile(directoryName, fileName, byteArray);
}
@Override
public boolean deleteFile(String directoryName, String fileName) {
String path = buildPath(directoryName, fileName);
File file = new File(path);
return file.delete();
}
@Override
public boolean isFileExist(String directoryName, String fileName) {
String path = buildPath(directoryName, fileName);
return new File(path).exists();
}
@Override
public byte[] readFile(String directoryName, String fileName) {
String path = buildPath(directoryName, fileName);
final FileInputStream stream;
try {
stream = new FileInputStream(new File(path));
return readFile(stream);
} catch (FileNotFoundException e) {
throw new RuntimeException("Failed to read file to input stream", e);
}
}
@Override
public String readTextFile(String directoryName, String fileName) {
byte[] bytes = readFile(directoryName, fileName);
String content = new String(bytes);
return content;
}
@Override
public void appendFile(String directoryName, String fileName, String content) {
appendFile(directoryName, fileName, content.getBytes());
}
@Override
public void appendFile(String directoryName, String fileName, byte[] bytes) {
if (!isFileExist(directoryName, fileName)) {
throw new RuntimeException("Impossible to append content, because such file doesn't exist");
}
try {
String path = buildPath(directoryName, fileName);
FileOutputStream stream = new FileOutputStream(new File(path), true);
stream.write(bytes);
stream.write(System.getProperty("line.separator").getBytes());
stream.flush();
stream.close();
} catch (IOException e) {
throw new RuntimeException("Failed to append content to file", e);
}
}
@Override
public List<File> getNestedFiles(String directoryName) {
String buildPath = buildPath(directoryName);
File file = new File(buildPath);
List<File> out = new ArrayList<File>();
getDirectoryFilesImpl(file, out);
return out;
}
@Override
public List<File> getFiles(String directoryName, final String matchRegex) {
String buildPath = buildPath(directoryName);
File file = new File(buildPath);
List<File> out = null;
if (matchRegex != null) {
FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String fileName) {
if (fileName.matches(matchRegex)) {
return true;
}
return false;
}
};
out = Arrays.asList(file.listFiles(filter));
} else {
out = Arrays.asList(file.listFiles());
}
return out;
}
@Override
public List<File> getFiles(String directoryName, OrderType orderType) {
List<File> files = getFiles(directoryName, (String) null);
Collections.sort(files, orderType.getComparator());
return files;
}
@Override
public File getFile(String name) {
String path = buildPath(name);
File file = new File(path);
return file;
}
@Override
public File getFile(String directoryName, String fileName) {
String path = buildPath(directoryName, fileName);
return new File(path);
}
@Override
public void rename(File file, String newName) {
String name = file.getName();
String newFullName = file.getAbsolutePath().replaceAll(name, newName);
File newFile = new File(newFullName);
file.renameTo(newFile);
}
@Override
public double getSize(File file, SizeUnit unit) {
long length = file.length();
return (double) length / (double) unit.inBytes();
}
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public long getFreeSpace(SizeUnit sizeUnit) {
String path = buildAbsolutePath();
StatFs statFs = new StatFs(path);
long availableBlocks;
long blockSize;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
availableBlocks = statFs.getAvailableBlocks();
blockSize = statFs.getBlockSize();
} else {
availableBlocks = statFs.getAvailableBlocksLong();
blockSize = statFs.getBlockSizeLong();
}
long freeBytes = availableBlocks * blockSize;
return freeBytes / sizeUnit.inBytes();
}
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public long getUsedSpace(SizeUnit sizeUnit) {
String path = buildAbsolutePath();
StatFs statFs = new StatFs(path);
long availableBlocks;
long blockSize;
long totalBlocks;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
availableBlocks = statFs.getAvailableBlocks();
blockSize = statFs.getBlockSize();
totalBlocks = statFs.getBlockCount();
} else {
availableBlocks = statFs.getAvailableBlocksLong();
blockSize = statFs.getBlockSizeLong();
totalBlocks = statFs.getBlockCountLong();
}
long usedBytes = totalBlocks * blockSize - availableBlocks * blockSize;
return usedBytes / sizeUnit.inBytes();
}
@Override
public void copy(File file, String directoryName, String fileName) {
if (!file.isFile()) {
return;
}
FileInputStream inStream = null;
FileOutputStream outStream = null;
try {
inStream = new FileInputStream(file);
outStream = new FileOutputStream(new File(buildPath(directoryName, fileName)));
FileChannel inChannel = inStream.getChannel();
FileChannel outChannel = outStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (Exception e) {
throw new StorageException(e);
} finally {
closeQuietly(inStream);
closeQuietly(outStream);
}
}
@Override
public void move(File file, String directoryName, String fileName) {
copy(file, directoryName, fileName);
file.delete();
}
protected byte[] readFile(final FileInputStream stream) {
class Reader extends Thread {
byte[] array = null;
}
Reader reader = new Reader() {
public void run() {
LinkedList<ImmutablePair<byte[], Integer>> chunks = new LinkedList<ImmutablePair<byte[], Integer>>();
// read the file and build chunks
int size = 0;
int globalSize = 0;
do {
try {
int chunkSize = getConfiguration().getChuckSize();
// read chunk
byte[] buffer = new byte[chunkSize];
size = stream.read(buffer, 0, chunkSize);
if (size > 0) {
globalSize += size;
// add chunk to list
chunks.add(new ImmutablePair<byte[], Integer>(buffer, size));
}
} catch (Exception e) {
// very bad
}
} while (size > 0);
try {
stream.close();
} catch (Exception e) {
// very bad
}
array = new byte[globalSize];
// append all chunks to one array
int offset = 0;
for (ImmutablePair<byte[], Integer> chunk : chunks) {
// flush chunk to array
System.arraycopy(chunk.element1, 0, array, offset, chunk.element2);
offset += chunk.element2;
}
};
};
reader.start();
try {
reader.join();
} catch (InterruptedException e) {
throw new RuntimeException("Failed on reading file from storage while the locking Thread", e);
}
if (getConfiguration().isEncrypted()) {
return encrypt(reader.array, Cipher.DECRYPT_MODE);
} else {
return reader.array;
}
}
protected abstract String buildAbsolutePath();
protected abstract String buildPath(String name);
protected abstract String buildPath(String directoryName, String fileName);
/**
* Encrypt or Descrypt the content. <br>
*
* @param content
* The content to encrypt or descrypt.
* @param encryptionMode
* Use: {@link Cipher#ENCRYPT_MODE} or
* {@link Cipher#DECRYPT_MODE}
* @return
*/
protected synchronized byte[] encrypt(byte[] content, int encryptionMode) {
final byte[] secretKey = getConfiguration().getSecretKey();
final byte[] ivx = getConfiguration().getIvParameter();
return SecurityUtil.encrypt(content, encryptionMode, secretKey, ivx);
}
/**
* Delete the directory and all sub content.
*
* @param path
* The absolute directory path. For example:
* <i>mnt/sdcard/NewFolder/</i>.
* @return <code>True</code> if the directory was deleted, otherwise return
* <code>False</code>
*/
private boolean deleteDirectoryImpl(String path) {
File directory = new File(path);
// If the directory exists then delete
if (directory.exists()) {
File[] files = directory.listFiles();
if (files == null) {
return true;
}
// Run on all sub files and folders and delete them
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectoryImpl(files[i].getAbsolutePath());
} else {
files[i].delete();
}
}
}
return directory.delete();
}
/**
* Get all files under the directory
*
* @param directory
* @param out
* @return
*/
private void getDirectoryFilesImpl(File directory, List<File> out) {
if (directory.exists()) {
File[] files = directory.listFiles();
if (files == null) {
return;
} else {
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
getDirectoryFilesImpl(files[i], out);
} else {
out.add(files[i]);
}
}
}
}
}
private void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
}
}
}
}