/**
* 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.context.filesystem;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.channel.ChannelFuture;
import org.waarp.common.command.exception.CommandAbstractException;
import org.waarp.common.digest.FilesystemBasedDigest;
import org.waarp.common.exception.FileEndOfTransferException;
import org.waarp.common.exception.FileTransferException;
import org.waarp.common.file.DataBlock;
import org.waarp.common.file.filesystembased.FilesystemBasedDirImpl;
import org.waarp.common.file.filesystembased.FilesystemBasedFileImpl;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.openr66.context.ErrorCode;
import org.waarp.openr66.context.R66Result;
import org.waarp.openr66.context.R66Session;
import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException;
import org.waarp.openr66.database.data.DbTaskRunner;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolPacketException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolSystemException;
import org.waarp.openr66.protocol.localhandler.LocalChannelReference;
import org.waarp.openr66.protocol.localhandler.RetrieveRunner;
import org.waarp.openr66.protocol.utils.ChannelUtils;
import org.waarp.openr66.protocol.utils.FileUtils;
/**
* File representation
*
* @author frederic bregier
*
*/
public class R66File extends FilesystemBasedFileImpl {
/**
* Internal Logger
*/
private static final WaarpLogger logger = WaarpLoggerFactory
.getLogger(R66File.class);
/**
* Does the current file is external (i.e. out of R66 base directory)
*/
private boolean isExternal = false;
/**
* @param session
* @param dir
* @param path
* @param append
* @throws CommandAbstractException
*/
public R66File(R66Session session, R66Dir dir, String path, boolean append)
throws CommandAbstractException {
super(session, dir, path, append);
}
/**
* This constructor is for External file
*
* @param session
* @param dir
* @param path
*/
public R66File(R66Session session, R66Dir dir, String path) {
super(session, dir, path);
isExternal = true;
}
/**
* Start the retrieve (send to the remote host the local file)
*
* @param running
* When false, should stop the runner
* @throws OpenR66RunnerErrorException
* @throws OpenR66ProtocolSystemException
*/
public void retrieveBlocking(AtomicBoolean running) throws OpenR66RunnerErrorException,
OpenR66ProtocolSystemException {
boolean retrieveDone = false;
LocalChannelReference localChannelReference = getSession()
.getLocalChannelReference();
FilesystemBasedDigest digest = null;
logger.debug("File to retrieve: " + this.toString());
try {
if (!isReady) {
return;
}
DataBlock block = null;
try {
block = readDataBlock();
} catch (FileEndOfTransferException e) {
// Last block (in fact, no data to read)
retrieveDone = true;
return;
}
if (block == null) {
// Last block (in fact, no data to read)
retrieveDone = true;
return;
}
if (Configuration.configuration.isGlobalDigest()) {
try {
digest = new FilesystemBasedDigest(Configuration.configuration.getDigest());
} catch (NoSuchAlgorithmException e2) {
// ignore
}
}
ChannelFuture future1 = null, future2 = null;
if ((block != null && (running.get()))) {
block.getBlock().retain();
future1 = RetrieveRunner.writeWhenPossible(
block, localChannelReference);
if (Configuration.configuration.isGlobalDigest()) {
FileUtils.computeGlobalHash(digest, block.getBlock());
}
}
// While not last block
while (block != null && (!block.isEOF()) && (running.get())) {
try {
future1.await();
} catch (InterruptedException e) {
}
if (!future1.isSuccess()) {
return;
}
try {
block = readDataBlock();
} catch (FileEndOfTransferException e) {
// Wait for last write
try {
future1.await();
} catch (InterruptedException e1) {
}
if (future1.isSuccess()) {
retrieveDone = true;
}
return;
}
block.getBlock().retain();
future2 = RetrieveRunner.writeWhenPossible(
block, localChannelReference);
if (Configuration.configuration.isGlobalDigest()) {
FileUtils.computeGlobalHash(digest, block.getBlock());
}
future1 = future2;
}
if (!running.get()) {
// stopped
return;
}
// Wait for last write
if (future1 != null) {
try {
future1.await();
} catch (InterruptedException e) {
}
if (!future1.isSuccess()) {
return;
}
}
if (block != null) {
block.getBlock().release();
block.clear();
}
retrieveDone = true;
return;
} catch (FileTransferException e) {
// An error occurs!
getSession()
.setFinalizeTransfer(
false,
new R66Result(new OpenR66ProtocolSystemException(e),
getSession(), false, ErrorCode.TransferError, getSession()
.getRunner()));
} catch (OpenR66ProtocolPacketException e) {
// An error occurs!
getSession()
.setFinalizeTransfer(
false,
new R66Result(e, getSession(), false,
ErrorCode.Internal, getSession().getRunner()));
} finally {
if (retrieveDone) {
String hash = null;
if (digest != null) {
hash = FilesystemBasedDigest.getHex(digest.Final());
}
try {
if (hash == null) {
ChannelUtils.writeEndTransfer(localChannelReference);
} else {
ChannelUtils.writeEndTransfer(localChannelReference, hash);
}
} catch (OpenR66ProtocolPacketException e) {
// An error occurs!
getSession().setFinalizeTransfer(
false,
new R66Result(e, getSession(), false,
ErrorCode.Internal, getSession().getRunner()));
}
} else {
// An error occurs!
getSession().setFinalizeTransfer(
false,
new R66Result(new OpenR66ProtocolSystemException("Transfer in error"),
getSession(), false, ErrorCode.TransferError, getSession()
.getRunner()));
}
}
}
/**
* This method is a good to have in a true FileInterface implementation.
*
* @return the File associated with the current FileInterface operation
*/
public File getTrueFile() {
if (isExternal) {
return new File(currentFile);
}
try {
return getFileFromPath(getFile());
} catch (CommandAbstractException e) {
logger.warn("Exception while getting file: " + this, e);
return null;
}
}
/**
*
* @return the basename of the current file
*/
public String getBasename() {
return getBasename(currentFile);
}
/**
*
* @param path
* @return the basename from the given path
*/
public static String getBasename(String path) {
int pos = path.lastIndexOf('/');
int pos2 = path.lastIndexOf('\\');
if (pos2 > pos) {
pos = pos2;
}
if (pos > 0) {
return path.substring(pos + 1);
}
return path;
}
@Override
public R66Session getSession() {
return (R66Session) session;
}
@Override
public boolean canRead() throws CommandAbstractException {
if (isExternal) {
File file = new File(currentFile);
logger.debug("Final File: " + file + " CanRead: " + file.canRead());
return file.canRead();
}
return super.canRead();
}
@Override
public boolean canWrite() throws CommandAbstractException {
if (isExternal) {
File file = new File(currentFile);
if (file.exists()) {
return file.canWrite();
}
return file.getParentFile().canWrite();
}
return super.canWrite();
}
@Override
public boolean delete() throws CommandAbstractException {
if (isExternal) {
File file = new File(currentFile);
checkIdentify();
if (!isReady) {
return false;
}
if (!file.exists()) {
return true;
}
closeFile();
return file.delete();
}
return super.delete();
}
@Override
public boolean exists() throws CommandAbstractException {
if (isExternal) {
File file = new File(currentFile);
return file.exists();
}
return super.exists();
}
@Override
protected FileChannel getFileChannel() {
if (!isExternal) {
return super.getFileChannel();
}
if (!isReady) {
return null;
}
File trueFile = getTrueFile();
FileChannel fileChannel;
try {
@SuppressWarnings("resource")
FileInputStream fileInputStream = new FileInputStream(trueFile);
fileChannel = fileInputStream.getChannel();
if (getPosition() > 0) {
fileChannel = fileChannel.position(getPosition());
}
} catch (FileNotFoundException e) {
logger.error("FileInterface not found in getFileChannel:", e);
return null;
} catch (IOException e) {
logger.error("Change position in getFileChannel:", e);
return null;
}
return fileChannel;
}
@Override
protected RandomAccessFile getRandomFile() {
if (!isExternal) {
return super.getRandomFile();
}
if (!isReady) {
return null;
}
File trueFile = getTrueFile();
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(trueFile, "rw");
raf.seek(getPosition());
} catch (FileNotFoundException e) {
logger.error("File not found in getRandomFile:", e);
return null;
} catch (IOException e) {
logger.error("Change position in getRandomFile:", e);
return null;
}
return raf;
}
/**
* Returns the FileOutputStream in Out mode associated with the current file.
*
* @param append
* True if the FileOutputStream should be in append mode
* @return the FileOutputStream (OUT)
*/
protected FileOutputStream getFileOutputStream(boolean append) {
if (!isExternal) {
return super.getFileOutputStream(append);
}
if (!isReady) {
return null;
}
File trueFile = getTrueFile();
if (getPosition() > 0) {
if (trueFile.length() < getPosition()) {
logger.error("Cannot Change position in getFileOutputStream: file is smaller than required position");
return null;
}
RandomAccessFile raf = getRandomFile();
try {
raf.setLength(getPosition());
raf.close();
} catch (IOException e) {
logger.error("Change position in getFileOutputStream:", e);
return null;
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(trueFile, append);
} catch (FileNotFoundException e) {
logger.error("File not found in getRandomFile:", e);
return null;
}
return fos;
}
@Override
public boolean isDirectory() throws CommandAbstractException {
if (isExternal) {
File dir = new File(currentFile);
return dir.isDirectory();
}
return super.isDirectory();
}
@Override
public boolean isFile() throws CommandAbstractException {
if (isExternal) {
File file = new File(currentFile);
return file.isFile();
}
return super.isFile();
}
@Override
public long length() throws CommandAbstractException {
if (isExternal) {
File file = new File(currentFile);
if (file.canRead()) {
return file.length();
} else {
return -1;
}
}
return super.length();
}
protected String getFullInDir() {
DbTaskRunner runner = getSession().getRunner();
if (runner != null) {
R66Dir dir = new R66Dir(getSession());
try {
dir.changeDirectory(runner.getRule().getRecvPath());
return dir.getFullPath();
} catch (CommandAbstractException e) {
}
}
return null;
}
@Override
public boolean renameTo(String path) throws CommandAbstractException {
if (!isExternal) {
return super.renameTo(path);
}
checkIdentify();
if (!isReady) {
logger.warn("File not ready: {}", this);
return false;
}
File file = getTrueFile();
if (file.canRead()) {
File newFile = getFileFromPath(path);
File parentFile = newFile.getParentFile();
if (parentFile == null) {
String dir = getFullInDir();
if (dir != null) {
newFile = new File(dir, newFile.getPath());
parentFile = newFile.getParentFile();
}
}
if (newFile.exists()) {
logger.warn("Target file already exists: "+newFile.getAbsolutePath());
return false;
}
if (newFile.getAbsolutePath().equals(file.getAbsolutePath())) {
// already in the right position
isReady = true;
return true;
}
if (parentFile != null && parentFile.canWrite()) {
if (!file.renameTo(newFile)) {
FileOutputStream fileOutputStream = null;
try {
try {
fileOutputStream = new FileOutputStream(newFile);
} catch (FileNotFoundException e) {
logger
.warn("Cannot find file: " + newFile.getName(),
e);
return false;
}
FileChannel fileChannelOut = fileOutputStream.getChannel();
if (get(fileChannelOut)) {
delete();
} else {
try {
fileChannelOut.close();
} catch (IOException e) {
}
logger.error("Cannot write file: {}", newFile);
return false;
}
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
}
}
}
}
currentFile = getRelativePath(newFile);
isExternal = false;
isReady = true;
logger.debug("File renamed to: {} and real position: {}", this, newFile);
return true;
}
}
logger.warn("Cannot read file: {}", file);
return false;
}
/**
* Move the current file to the path as destination
*
* @param path
* @param external
* if True, the path is outside authentication control
* @return True if the operation is done
* @throws CommandAbstractException
*/
public boolean renameTo(String path, boolean external)
throws CommandAbstractException {
if (!external) {
return renameTo(path);
}
checkIdentify();
if (!isReady) {
return false;
}
File file = getTrueFile();
if (file.canRead()) {
File newFile = new File(path);
File parentFile = newFile.getParentFile();
if (parentFile == null) {
String dir = getFullInDir();
if (dir != null) {
newFile = new File(dir, newFile.getPath());
parentFile = newFile.getParentFile();
}
}
if (newFile.exists()) {
logger.warn("Target file already exists: "+newFile.getAbsolutePath());
return false;
}
if (newFile.getAbsolutePath().equals(file.getAbsolutePath())) {
// already in the right position
isReady = true;
return true;
}
if (parentFile != null && parentFile.canWrite()) {
if (!file.renameTo(newFile)) {
FileOutputStream fileOutputStream = null;
try {
try {
fileOutputStream = new FileOutputStream(newFile);
} catch (FileNotFoundException e) {
logger
.warn("Cannot find file: " + newFile.getName(),
e);
return false;
}
FileChannel fileChannelOut = fileOutputStream.getChannel();
if (get(fileChannelOut)) {
delete();
} else {
try {
fileChannelOut.close();
} catch (IOException e) {
}
logger.error("Cannot write file: {}", newFile);
return false;
}
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
}
}
}
}
currentFile = FilesystemBasedDirImpl.normalizePath(newFile
.getAbsolutePath());
isExternal = true;
isReady = true;
return true;
}
logger.error("Cannot write to parent directory: {}", newFile.getParent());
}
logger.error("Cannot read file: {}", file);
return false;
}
/**
* Replace the current file with the new filename after closing the previous one.
*
* @param filename
* @param isExternal
* @throws CommandAbstractException
*/
public void replaceFilename(String filename, boolean isExternal)
throws CommandAbstractException {
closeFile();
currentFile = filename;
this.isExternal = isExternal;
isReady = true;
}
@Override
public boolean closeFile() throws CommandAbstractException {
boolean status = super.closeFile();
// FORCE re-open file
isReady = true;
return status;
}
/**
*
* @return True if this file is outside OpenR66 Base directory
*/
public boolean isExternal() {
return isExternal;
}
@Override
public String toString() {
return "File: " + currentFile + " Ready " + isReady + " isExternal " + isExternal + " " +
getPosition();
}
}