package ef.impl.volume;
import ef.api.*;
import ef.api.Error;
import ef.impl.archive.ArchiveType;
import ef.impl.archive.Archiver;
import ef.impl.image.ImageHelper;
import ef.impl.misc.MimeUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* User: sduchenko
* Date: 18.04.13
* Time: 13:34
*/
public class FsVolume implements Volume {
private final PathHashConverter hashConverter;
private final FsConfig config;
private final File rootFile;
public FsVolume(PathHashConverter hashConverter, FsConfig config) {
this.hashConverter = hashConverter;
this.config = config;
this.rootFile = new File(config.getRootPath());
}
@Override
public String id() {
return config.getVolumeId();
}
@Override
public FileEntry root() {
SimpleFileEntry entry = buildEntry(rootFile, null);
entry.setVolumeId(config.getVolumeId());
entry.setDirs(true);
return entry;
}
@Override
public FileEntry get(String target) {
FileHolder holder = getRequiredFile(target);
if(isRoot(holder)){
return root();
}
FileHolder parent = getRequiredParent(holder);
return buildEntry(holder, parent.hash);
}
@Override
public String readText(String target) {
FileHolder holder = getRequiredFile(target);
try {
return FileUtils.readFileToString(holder.file, config.getDefaultTextFileEncoding());
} catch (IOException e) {
throw new ErrException(e, Error.errRead);
}
}
@Override
public FileData read(String target) {
FileHolder holder = getRequiredFile(target);
assertFile(holder);
FileEntry entry = buildEntry(holder, null);
FileInputStream fis;
try {
fis = new FileInputStream(holder.file);
} catch (FileNotFoundException e) {
throw new ErrException(e, Error.errRead, holder.file.getName());
}
return new SimpleFileData(entry, fis);
}
@Override
public void writeText(String target, String text) {
FileHolder holder = getRequiredFile(target);
if(holder.file.isDirectory() || !holder.file.canWrite()){
throw new ErrException(Error.errWrite);
}
try {
FileUtils.writeStringToFile(holder.file, text, config.getDefaultTextFileEncoding());
} catch (IOException e) {
throw new ErrException(e, Error.errWrite);
}
}
@Override
public void remove(String target) {
FileHolder holder = getRequiredFile(target);
try {
FileUtils.forceDelete(holder.file);
} catch (IOException e) {
throw new ErrException(e, Error.errRm);
}
}
@Override
public FileEntry upload(String targetDir, String fileName, InputStream is) {
FileHolder dir = getRequiredFile(targetDir);
assertDir(dir);
String path = dir.path + File.separator + fileName;
File file = new File(path);
if(file.exists()){
throw new ErrException(Error.errExists);
}
OutputStream fos = null;
try {
fos = new FileOutputStream(file);
IOUtils.copy(is, fos);
} catch (IOException e) {
throw new ErrException(e, Error.errUpload);
} finally {
try {
if(fos != null){
fos.close();
}
} catch (IOException ignored) {}
}
return buildEntry(file, targetDir);
}
@Override
public List<FileEntry> list(String targetDir) {
FileHolder dir = getRequiredFile(targetDir);
assertDir(dir);
File[] files = listFiles(dir.file);
List<FileEntry> list = new ArrayList<FileEntry>(files.length);
for(File file: files){
SimpleFileEntry entry = buildEntry(file, targetDir);
entry.setDirs(file.isDirectory() && isHaveSubFolders(file));
list.add(entry);
}
return list;
}
@Override
public List<FileEntry> parents(String targetDir) {
FileHolder dir = getRequiredFile(targetDir);
assertDir(dir);
List<FileEntry> list = new ArrayList<FileEntry>();
if(isRoot(dir)){
return list;
}
FileHolder current = getRequiredParent(dir);
int level = 0;
while (true){
// add dirs on same level
File[] files = listFiles(current.file);
for(File file: files){
if(file.isDirectory()){
SimpleFileEntry entry = buildEntry(file, current.hash);
entry.setDirs(level > 0 || isHaveSubFolders(file));
list.add(entry);
}
}
if(isRoot(current)){
break;
}
current = getRequiredParent(current);
level++;
}
return list;
}
@Override
public List<FileEntry> tree(String targetDir) {
FileHolder dir = getRequiredFile(targetDir);
assertDir(dir);
List<FileEntry> list = new ArrayList<FileEntry>();
File[] files = listFiles(dir.file);
for(File file: files){
if(file.isDirectory()){
SimpleFileEntry entry = buildEntry(file, dir.hash);
entry.setDirs(isHaveSubFolders(file));
list.add(entry);
}
}
return list;
}
private File[] listFiles(File file) {
File[] files = file.listFiles();
if(files == null){
throw new ErrException(Error.errAccess);
}
return files;
}
private boolean isHaveSubFolders(File file){
File[] files = file.listFiles();
if(files == null){
return false;
}
for(File sub: files){
if(sub.isDirectory()){
return true;
}
}
return false;
}
@Override
public FileEntry mkDir(String targetDir, String dirName) {
FileHolder holder = getRequiredFile(targetDir);
assertDir(holder);
File newFile = new File(holder.path+File.separator+dirName);
if(!newFile.mkdir()){
throw new ErrException(Error.errMkdir);
}
return buildEntry(newFile, targetDir);
}
@Override
public FileEntry mkFile(String targetDir, String fileName) {
FileHolder holder = getRequiredFile(targetDir);
assertDir(holder);
File newFile = new File(holder.path+File.separator+fileName);
boolean created;
try {
created = newFile.createNewFile();
} catch (IOException e) {
throw new ErrException(e, Error.errMkfile);
}
if(!created){
throw new ErrException(Error.errMkfile);
}
return buildEntry(newFile, targetDir);
}
@Override
public FileEntry rename(String target, String fileName) {
FileHolder holder = getRequiredFile(target);
assertNotRoot(holder);
FileHolder parent = getRequiredParent(holder);
String newFileName = parent.path + File.separator + fileName;
File newFile = new File(newFileName);
if(newFile.exists()){
throw new ErrException(Error.errExists);
}
if(!holder.file.renameTo(newFile)){
throw new ErrException(Error.errRename);
}
return buildEntry(newFile, parent.hash);
}
@Override
public FileEntry copy(String target, String targetDir) {
FileHolder dirHolder = getRequiredFile(targetDir);
FileHolder holder = getRequiredFile(target);
assertDir(dirHolder);
String name = holder.file.getName();
String path = dirHolder.path + File.separator + name;
File file = new File(path);
if(file.exists()){
throw new ErrException(Error.errExists);
}
try {
if(holder.file.isDirectory()){
FileUtils.copyDirectoryToDirectory(holder.file, dirHolder.file);
}else{
FileUtils.copyFileToDirectory(holder.file, dirHolder.file);
}
} catch (IOException e) {
throw new ErrException(e, Error.errCopy);
}
if(!file.exists()){
throw new ErrException(Error.errCopy);
}
return buildEntry(file, dirHolder.hash);
}
@Override
public FileEntry move(String target, String targetDir) {
FileHolder dirHolder = getRequiredFile(targetDir);
FileHolder holder = getRequiredFile(target);
assertDir(dirHolder);
String name = holder.file.getName();
String path = dirHolder.path + File.separator + name;
File file = new File(path);
if(file.exists()){
throw new ErrException(Error.errExists);
}
try {
if(holder.file.isDirectory()){
FileUtils.moveDirectoryToDirectory(holder.file, dirHolder.file, true);
}else{
FileUtils.moveFileToDirectory(holder.file, dirHolder.file, true);
}
} catch (IOException e) {
throw new ErrException(e, Error.errMove);
}
if(!file.exists()){
throw new ErrException(Error.errMove);
}
return buildEntry(file, targetDir);
}
@Override
public FileEntry duplicate(String target) {
FileHolder holder = getRequiredFile(target);
FileHolder parent = getRequiredParent(holder);
File newFile = getDuplicateName(holder, parent);
try {
if(holder.file.isDirectory()){
FileUtils.copyDirectory(holder.file, newFile);
}else{
FileUtils.copyFile(holder.file, newFile);
}
} catch (IOException e) {
throw new ErrException(Error.errCopy);
}
if(!newFile.exists()){
throw new ErrException(Error.errCopy);
}
return buildEntry(newFile, parent.hash);
}
@Override
public FileEntry archive(String mimeType, String[] targets) {
ArchiveType type;
try {
type = ArchiveType.fromMime(mimeType);
} catch (Exception e) {
throw new ErrException(e, Error.errArcType);
}
List<FileHolder> holders = getRequiredFiles(targets);
FileHolder parent = getRequiredParent(holders.get(0));
File archiveFile = getNewArchiveFile(parent.path + File.separator + parent.file.getName(), type.getFileExt());
List<File> files = new ArrayList<File>();
for(FileHolder holder: holders){
files.add(holder.file);
}
try {
Archiver.createArchive(type, files, archiveFile);
} catch (Exception e) {
throw new ErrException(e, Error.errArchive);
}
return buildEntry(archiveFile, parent.hash);
}
@Override
public FileEntry extract(String target) {
FileHolder holder = getRequiredFile(target);
FileHolder parent = getRequiredParent(holder);
assertFile(holder);
String ext = FilenameUtils.getExtension(holder.path);
ArchiveType type;
try {
type = ArchiveType.fromFileExt(ext);
} catch (Exception e) {
throw new ErrException(Error.errArcType);
}
String name = FilenameUtils.getBaseName(holder.file.getName());
name = getBaseName(name);
File dstDir = getNewDir(parent.file, name);
if(!dstDir.mkdir()){
throw new ErrException(Error.errExists, name);
}
try {
Archiver.extractArchive(type, holder.file, dstDir);
} catch (IOException e) {
throw new ErrException(e, Error.errExtract);
}
SimpleFileEntry entry = buildEntry(dstDir, parent.hash);
entry.setDirs(isHaveSubFolders(dstDir));
return entry;
}
@Override
public FileEntry resizeImage(String target, long newWidth, long newHeight) {
FileHolder holder = getRequiredFile(target);
try {
ImageHelper.resize(holder.file, newWidth, newHeight);
} catch (IOException e) {
throw new ErrException(e, Error.errResize);
}
return buildEntry(holder, getRequiredParent(holder).hash);
}
@Override
public FileEntry rotateImage(String target, long newWidth, long newHeight, long degree) {
FileHolder holder = getRequiredFile(target);
try {
ImageHelper.rotate(holder.file, degree);
} catch (IOException e) {
throw new ErrException(e, Error.errResize);
}
return buildEntry(holder, getRequiredParent(holder).hash);
}
@Override
public FileEntry cropImage(String target, long newWidth, long newHeight, long x, long y) {
FileHolder holder = getRequiredFile(target);
try {
ImageHelper.crop(holder.file, newWidth, newHeight, x, y);
} catch (IOException e) {
throw new ErrException(e, Error.errResize);
}
return buildEntry(holder, getRequiredParent(holder).hash);
}
private static final Pattern BASE_NAME_PATTERN = Pattern.compile("^(.*)\\(\\d+\\)$");
private String getBaseName(String name) {
Matcher matcher = BASE_NAME_PATTERN.matcher(name);
return matcher.matches() ? matcher.group(1) : name;
}
private File getNewDir(File parent, String name) {
int num = 0;
while(num++<MAX_STEPS){
File file = new File(parent, num==0 ? name : name+"("+num+")");
if(!file.exists()){
return file;
}
}
throw new ErrException(Error.errExists, name);
}
private static final int MAX_STEPS = 100;
private File getNewArchiveFile(String parentPath, String ext){
String newName = parentPath+ "." + ext;
File archiveFile = new File(newName);
if(!archiveFile.exists()){
return archiveFile;
}
int num = 0;
while(++num<MAX_STEPS){
newName = parentPath + "(" + num + ")." + ext;
archiveFile = new File(newName);
if(!archiveFile.exists()){
return archiveFile;
}
}
throw new ErrException(Error.errExists);
}
private Pattern FILE_COPY_PATTERN = Pattern.compile("^(.*) copy (\\d+)(.*)?$", Pattern.CASE_INSENSITIVE);
private Pattern FILE_EXT_PATTERN = Pattern.compile("^(.*)(\\.[^\\.]*)$", Pattern.CASE_INSENSITIVE);
private File getDuplicateName(FileHolder holder, FileHolder parent) {
String name = holder.file.getName();
Matcher matcher = FILE_COPY_PATTERN.matcher(name);
String baseName;
int num = 0;
String ext = "";
if(!matcher.matches()){
if(holder.file.isDirectory()){
baseName = name;
}else{
matcher = FILE_EXT_PATTERN.matcher(name);
if(matcher.matches()){
baseName = matcher.group(1);
ext = matcher.group(2);
}else{
baseName = name;
}
}
}else {
baseName = matcher.group(1);
num = Integer.valueOf(matcher.group(2));
ext = matcher.groupCount() == 3 ? matcher.group(3) : "";
}
baseName = baseName + " copy ";
int step = 100;
while(step-->0){
name = parent.path + File.separator + baseName + (++num) + ext;
File file = new File(name);
if(!file.exists()){
return file;
}
}
throw new ErrException(Error.errUnknown);
}
private List<FileHolder> getRequiredFiles(String[] hashes){
List<FileHolder> holders = new ArrayList<FileHolder>();
for(String hash: hashes){
holders.add(getRequiredFile(hash));
}
return holders;
}
private FileHolder getRequiredFile(String hash){
String path = hashConverter.hashToPath(hash);
File file = new File(path);
if(!file.exists()){
throw new ErrException(Error.errFileNotFound);
}
FileHolder holder = new FileHolder();
holder.file = file;
holder.hash = hash;
holder.path = path;
return holder;
}
private FileHolder getRequiredParent(FileHolder holder){
File parent = holder.file.getParentFile();
if(parent == null){
throw new ErrException(Error.errFolderNotFound);
}
FileHolder parentHolder = new FileHolder();
parentHolder.file = parent;
parentHolder.path = getFilePath(parent);
parentHolder.hash = hashConverter.pathToHash(parentHolder.path);
return parentHolder;
}
private void assertDir(FileHolder holder){
if(!holder.file.isDirectory()) {
throw new ErrException(Error.errNotFolder, holder.file.getName());
}
}
private void assertFile(FileHolder holder){
if(holder.file.isDirectory()) {
throw new ErrException(Error.errNotFile, holder.file.getName());
}
}
private void assertNotRoot(FileHolder holder){
if(isRoot(holder)){
throw new ErrException(Error.errAccess, holder.file.getName());
}
}
private boolean isRoot(FileHolder holder){
return holder.path.equalsIgnoreCase(config.getRootPath()) || holder.path.length() <= config.getRootPath().length();
}
private static class FileHolder {
String path;
String hash;
File file;
}
private SimpleFileEntry buildEntry(FileHolder holder, String pHash){
SimpleFileEntry entry = new SimpleFileEntry();
File file = holder.file;
entry.setMime(MimeUtil.getMime(file));
entry.setHash(holder.hash);
entry.setName(file.getName());
entry.setSize(file.length());
entry.setLocked(false);
entry.setRead(file.canRead());
entry.setWrite(file.canWrite());
entry.setPhash(pHash);
entry.setDirs(false);
entry.setTs(file.lastModified());
return entry;
}
private SimpleFileEntry buildEntry(File file, String pHash){
FileHolder holder = new FileHolder();
holder.file = file;
holder.hash = hashConverter.pathToHash(getFilePath(file));
return buildEntry(holder, pHash);
}
private String getFilePath(File file){
try {
return file.getCanonicalPath();
} catch (IOException e) {
throw new ErrException(e);
}
}
}