/**
* This file is part of Waarp Project.
*
* Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the
* COPYRIGHT.txt in the distribution for a full listing of individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Waarp . If not, see
* <http://www.gnu.org/licenses/>.
*/
package org.waarp.openr66.protocol.utils;
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.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.waarp.common.command.exception.CommandAbstractException;
import org.waarp.common.digest.FilesystemBasedDigest;
import org.waarp.common.digest.FilesystemBasedDigest.DigestAlgo;
import org.waarp.common.file.filesystembased.FilesystemBasedFileParameterImpl;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.utility.DetectionUtils;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.openr66.context.R66Session;
import org.waarp.openr66.context.filesystem.R66Dir;
import org.waarp.openr66.context.filesystem.R66File;
import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolSystemException;
/**
* File Utils
*
* @author Frederic Bregier
*
*/
public class FileUtils {
/**
* Copy one file to another one
*
* @param from
* @param to
* @param move
* True if the copy is in fact a move operation
* @param append
* True if the copy is in append
* @throws OpenR66ProtocolSystemException
*/
public static void copy(File from, File to, boolean move, boolean append)
throws OpenR66ProtocolSystemException {
if (from == null || to == null) {
throw new OpenR66ProtocolSystemException(
"Source or Destination is null");
}
File directoryTo = to.getParentFile();
if (createDir(directoryTo)) {
if (move && from.renameTo(to)) {
return;
}
FileChannel fileChannelIn = getFileChannel(from, false, false);
if (fileChannelIn == null) {
throw new OpenR66ProtocolSystemException(
"Cannot read source file");
// return false;
}
FileChannel fileChannelOut = getFileChannel(to, true, append);
if (fileChannelOut == null) {
try {
fileChannelIn.close();
} catch (IOException e) {
}
throw new OpenR66ProtocolSystemException(
"Cannot write destination file");
}
if (write(fileChannelIn, fileChannelOut) > -1) {
if (move) {
// do not test the delete
from.delete();
}
return;
}
throw new OpenR66ProtocolSystemException("Cannot copy");
}
throw new OpenR66ProtocolSystemException(
"Cannot access to parent dir of destination");
}
/**
* Copy a group of files to a directory
*
* @param from
* @param directoryTo
* @param move
* True if the copy is in fact a move operation
* @return the group of copy files or null (partially or totally) if an error occurs
* @throws OpenR66ProtocolSystemException
*/
public static File[] copy(File[] from, File directoryTo, boolean move)
throws OpenR66ProtocolSystemException {
if (from == null || directoryTo == null) {
return null;
}
File[] to = null;
if (createDir(directoryTo)) {
to = new File[from.length];
for (int i = 0; i < from.length; i++) {
try {
to[i] = copyToDir(from[i], directoryTo, move);
} catch (OpenR66ProtocolSystemException e) {
throw e;
}
}
}
return to;
}
/**
* Copy one file to a directory
*
* @param from
* @param directoryTo
* @param move
* True if the copy is in fact a move operation
* @return The copied file or null if an error occurs
* @throws OpenR66ProtocolSystemException
*/
public static File copyToDir(File from, File directoryTo, boolean move)
throws OpenR66ProtocolSystemException {
if (from == null || directoryTo == null) {
throw new OpenR66ProtocolSystemException(
"Source or Destination is null");
}
if (createDir(directoryTo)) {
File to = new File(directoryTo, from.getName());
if (move && from.renameTo(to)) {
return to;
}
FileChannel fileChannelIn = getFileChannel(from, false, false);
if (fileChannelIn == null) {
throw new OpenR66ProtocolSystemException(
"Cannot read source file");
}
FileChannel fileChannelOut = getFileChannel(to, true, false);
if (fileChannelOut == null) {
try {
fileChannelIn.close();
} catch (IOException e) {
}
throw new OpenR66ProtocolSystemException(
"Cannot write destination file");
}
if (write(fileChannelIn, fileChannelOut) > 0) {
if (move) {
// do not test the delete
from.delete();
}
return to;
}
throw new OpenR66ProtocolSystemException(
"Cannot write destination file");
}
throw new OpenR66ProtocolSystemException(
"Cannot access to parent dir of destination");
}
/**
* Create the directory associated with the File as path
*
* @param directory
* @return True if created, False else.
*/
public final static boolean createDir(File directory) {
if (directory == null) {
return false;
}
if (directory.isDirectory()) {
return true;
}
return directory.mkdirs();
}
/**
* Delete physically the file
*
* @param file
* @return True if OK, else if not (or if the file never exists).
*/
public final static boolean delete(File file) {
if (!file.exists()) {
return true;
}
return file.delete();
}
/**
* Delete the directory associated with the File as path if empty
*
* @param directory
* @return True if deleted, False else.
*/
public final static boolean deleteDir(File directory) {
if (directory == null) {
return true;
}
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return false;
}
return directory.delete();
}
/**
* Delete physically the file but when the JVM exits (useful for temporary file)
*
* @param file
*/
public final static void deleteOnExit(File file) {
if (!file.exists()) {
return;
}
file.deleteOnExit();
}
/**
* Delete the directory and its subdirs associated with the File as path if empty
*
* @param directory
* @return True if deleted, False else.
*/
public static boolean deleteRecursiveDir(File directory) {
if (directory == null) {
return true;
}
boolean retour = true;
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return false;
}
File[] list = directory.listFiles();
if (list == null || list.length == 0) {
list = null;
retour = directory.delete();
return retour;
}
int len = list.length;
for (int i = 0; i < len; i++) {
if (list[i].isDirectory()) {
if (!deleteRecursiveFileDir(list[i])) {
retour = false;
}
} else {
retour = false;
}
}
list = null;
if (retour) {
retour = directory.delete();
}
return retour;
}
/**
* Delete the directory and its subdirs associated with the File dir if empty
*
* @param dir
* @return True if deleted, False else.
*/
private static boolean deleteRecursiveFileDir(File dir) {
if (dir == null) {
return true;
}
boolean retour = true;
if (!dir.exists()) {
return true;
}
File[] list = dir.listFiles();
if (list == null || list.length == 0) {
list = null;
return dir.delete();
}
int len = list.length;
for (int i = 0; i < len; i++) {
if (list[i].isDirectory()) {
if (!deleteRecursiveFileDir(list[i])) {
retour = false;
}
} else {
retour = false;
list = null;
return retour;
}
}
list = null;
if (retour) {
retour = dir.delete();
}
return retour;
}
/**
* Change or create the R66File associated with the context
*
* @param logger
* @param session
* @param filenameSrc
* new filename
* @param isPreStart
* @param isSender
* @param isThrough
* @param file
* old R66File if any (might be null)
* @return the R66File
* @throws OpenR66RunnerErrorException
*/
public final static R66File getFile(WaarpLogger logger, R66Session session, String filenameSrc,
boolean isPreStart, boolean isSender, boolean isThrough, R66File file) throws OpenR66RunnerErrorException {
String filename;
logger.debug("PreStart: " + isPreStart);
logger.debug("Dir is: " + session.getDir().getFullPath());
logger.debug("File is: " + filenameSrc);
if (isPreStart) {
filename = R66Dir.normalizePath(filenameSrc);
if (filename.startsWith("file:/")) {
if (DetectionUtils.isWindows()) {
filename = filename.substring("file:/".length());
} else {
filename = filename.substring("file:".length());
}
if (filename.contains("%")) {
try {
filename = URLDecoder.decode(filename, WaarpStringUtils.UTF8.name());
} catch (UnsupportedEncodingException e) {
logger.warn("Filename convertion to UTF8 cannot be read: " +
filename);
}
}
}
logger.debug("File becomes: " + filename);
} else {
filename = filenameSrc;
}
if (isSender) {
try {
if (file == null) {
try {
file = (R66File) session.getDir().setFile(filename, false);
} catch (CommandAbstractException e) {
logger.warn("File not placed in normal directory", e);
// file is not under normal base directory, so is external
// File should already exist but can be using special code ('*?')
file = session.getDir().setFileNoCheck(filename);
}
}
if (isThrough) {
// no test on file since it does not really exist
logger.debug("File is in through mode: {}", file);
} else if (!file.canRead()) {
logger.debug("File {} cannot be read, so try external from " + filename, file);
// file is not under normal base directory, so is external
// File should already exist but cannot use special code ('*?')
R66File file2 = new R66File(session, session.getDir(), filename);
if (!file2.canRead()) {
throw new OpenR66RunnerErrorException("File cannot be read: " +
file.getTrueFile().getAbsolutePath());
}
file = file2;
}
} catch (CommandAbstractException e) {
throw new OpenR66RunnerErrorException(e);
}
} else {
// not sender so file is just registered as is but no test of existence
file = new R66File(session, session.getDir(), filename);
}
return file;
}
/**
* @param _FileName
* @param _Path
* @return true if the file exist in the specified path
*/
public final static boolean fileExist(String _FileName, String _Path) {
boolean exist = false;
String fileString = _Path + File.separator + _FileName;
File file = new File(fileString);
if (file.exists()) {
exist = true;
}
return exist;
}
/**
* Returns the FileChannel in Out MODE (if isOut is True) or in In MODE (if isOut is False)
* associated with the file. In out MODE, it can be in append MODE.
*
* @param isOut
* @param append
* @return the FileChannel (OUT or IN)
* @throws OpenR66ProtocolSystemException
*/
private static FileChannel getFileChannel(File file, boolean isOut,
boolean append) throws OpenR66ProtocolSystemException {
FileChannel fileChannel = null;
try {
if (isOut) {
@SuppressWarnings("resource")
FileOutputStream fileOutputStream = new FileOutputStream(file
.getPath(), append);
fileChannel = fileOutputStream.getChannel();
if (append) {
// Bug in JVM since it does not respect the API (position
// should be set as length)
try {
fileChannel.position(file.length());
} catch (IOException e) {
}
}
} else {
if (!file.exists()) {
throw new OpenR66ProtocolSystemException(
"File does not exist");
}
@SuppressWarnings("resource")
FileInputStream fileInputStream = new FileInputStream(file
.getPath());
fileChannel = fileInputStream.getChannel();
}
} catch (FileNotFoundException e) {
throw new OpenR66ProtocolSystemException("File not found", e);
}
return fileChannel;
}
/**
* Get the list of files from a given directory
*
* @param directory
* @return the list of files (as an array)
*/
public final static File[] getFiles(File directory) {
if (directory == null || !directory.isDirectory()) {
return null;
}
return directory.listFiles();
}
/**
* Get the list of files from a given directory and a filter
*
* @param directory
* @param filter
* @return the list of files (as an array)
*/
public final static File[] getFiles(File directory, FilenameFilter filter) {
if (directory == null || !directory.isDirectory()) {
return null;
}
return directory.listFiles(filter);
}
/**
* Calculates and returns the hash of the contents of the given file.
*
* @param f
* FileInterface to hash
* @return the hash from the given file
* @throws OpenR66ProtocolSystemException
**/
public final static String getHash(File f) throws OpenR66ProtocolSystemException {
try {
return FilesystemBasedDigest.getHex(FilesystemBasedDigest.getHash(f,
FilesystemBasedFileParameterImpl.useNio, Configuration.configuration.getDigest()));
} catch (IOException e) {
throw new OpenR66ProtocolSystemException(e);
}
}
/**
*
* @param buffer
* @return the hash from the given Buffer
*/
public final static ByteBuf getHash(ByteBuf buffer, DigestAlgo algo) {
byte[] newkey;
try {
newkey = FilesystemBasedDigest.getHash(buffer, algo);
} catch (IOException e) {
return Unpooled.EMPTY_BUFFER;
}
return Unpooled.wrappedBuffer(newkey);
}
/**
* Compute global hash (if possible)
*
* @param digest
* @param buffer
*/
public static void computeGlobalHash(FilesystemBasedDigest digest, ByteBuf buffer) {
if (digest == null) {
return;
}
digest.Update(buffer);
}
/**
* Compute global hash (if possible) from a file but up to length
*
* @param digest
* @param file
* @param length
*/
public static void computeGlobalHash(FilesystemBasedDigest digest, File file, int length) {
if (digest == null) {
return;
}
byte[] bytes = new byte[65536];
int still = length;
int len = still > 65536 ? 65536 : still;
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
while (inputStream.read(bytes, 0, len) > 0) {
digest.Update(bytes, 0, len);
still -= length;
if (still <= 0) {
break;
}
len = still > 65536 ? 65536 : still;
}
} catch (FileNotFoundException e) {
// error
} catch (IOException e) {
// error
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
}
}
/**
* Write one fileChannel to another one. Close the fileChannels
*
* @param fileChannelIn
* source of file
* @param fileChannelOut
* destination of file
* @return The size of copy if is OK
* @throws AtlasIoException
*/
private static long write(FileChannel fileChannelIn,
FileChannel fileChannelOut) throws OpenR66ProtocolSystemException {
if (fileChannelIn == null) {
if (fileChannelOut != null) {
try {
fileChannelOut.close();
} catch (IOException e) {
}
}
throw new OpenR66ProtocolSystemException("FileChannelIn is null");
}
if (fileChannelOut == null) {
try {
fileChannelIn.close();
} catch (IOException e) {
}
throw new OpenR66ProtocolSystemException("FileChannelOut is null");
}
long size = 0;
long transfert = 0;
try {
transfert = fileChannelOut.position();
size = fileChannelIn.size();
long chunkSize = size;
while (transfert < size) {
chunkSize = size - transfert;
transfert += fileChannelOut.transferFrom(fileChannelIn, transfert, chunkSize);
}
} catch (IOException e) {
try {
fileChannelOut.close();
fileChannelIn.close();
} catch (IOException e1) {
}
throw new OpenR66ProtocolSystemException(
"An error during copy occurs", e);
}
try {
fileChannelOut.close();
fileChannelIn.close();
} catch (IOException e) {// Close error can be ignored
}
boolean retour = size == transfert;
if (!retour) {
throw new OpenR66ProtocolSystemException("Copy is not complete: " +
transfert + " bytes instead of " + size + " original bytes");
}
return size;
}
}