org.waarp.common.file.filesystembased.FilesystemBasedFileImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.waarp.common.file.filesystembased.FilesystemBasedFileImpl.java

Source

/**
 * 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.common.file.filesystembased;

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.ByteBuffer;
import java.nio.channels.ClosedChannelException;
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.command.exception.Reply550Exception;
import org.waarp.common.exception.FileEndOfTransferException;
import org.waarp.common.exception.FileTransferException;
import org.waarp.common.file.AbstractFile;
import org.waarp.common.file.DataBlock;
import org.waarp.common.file.DirInterface;
import org.waarp.common.file.SessionInterface;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;

/**
 * File implementation for Filesystem Based
 * 
 * @author Frederic Bregier
 * 
 */
public abstract class FilesystemBasedFileImpl extends AbstractFile {
    /**
     * Internal Logger
     */
    private static final WaarpLogger logger = WaarpLoggerFactory.getLogger(FilesystemBasedFileImpl.class);

    /**
     * SessionInterface
     */
    protected final SessionInterface session;

    /**
     * DirInterface associated with this file at creation. It is not necessary the directory that
     * owns this file.
     */
    private final FilesystemBasedDirImpl dir;

    /**
     * {@link FilesystemBasedAuthImpl}
     */
    private final FilesystemBasedAuthImpl auth;

    /**
     * Current file if any
     */
    protected String currentFile = null;

    /**
     * Is this file in append mode
     */
    protected boolean isAppend = false;

    /**
     * @param session
     * @param dir
     *            It is not necessary the directory that owns this file.
     * @param path
     * @param append
     * @throws CommandAbstractException
     */
    public FilesystemBasedFileImpl(SessionInterface session, FilesystemBasedDirImpl dir, String path,
            boolean append) throws CommandAbstractException {
        this.session = session;
        auth = (FilesystemBasedAuthImpl) session.getAuth();
        this.dir = dir;
        currentFile = path;
        isAppend = append;
        File file = getFileFromPath(path);
        if (append) {
            try {
                setPosition(file.length());
            } catch (IOException e) {
                // not ready
                return;
            }
        } else {
            try {
                setPosition(0);
            } catch (IOException e) {
            }
        }
        isReady = true;
    }

    /**
     * Special constructor for possibly external file
     * 
     * @param session
     * @param dir
     *            It is not necessary the directory that owns this file.
     * @param path
     */
    public FilesystemBasedFileImpl(SessionInterface session, FilesystemBasedDirImpl dir, String path) {
        this.session = session;
        auth = (FilesystemBasedAuthImpl) session.getAuth();
        this.dir = dir;
        currentFile = path;
        isReady = true;
        isAppend = false;
        position = 0;
    }

    public void clear() throws CommandAbstractException {
        super.clear();
        currentFile = null;
        isAppend = false;
    }

    public SessionInterface getSession() {
        return session;
    }

    public DirInterface getDir() {
        return dir;
    }

    /**
     * Get the File from this path, checking first its validity
     * 
     * @param path
     * @return the FileInterface
     * @throws CommandAbstractException
     */
    protected File getFileFromPath(String path) throws CommandAbstractException {
        String newdir = getDir().validatePath(path);
        if (dir.isAbsolute(newdir)) {
            return new File(newdir);
        }
        String truedir = auth.getAbsolutePath(newdir);
        File file = new File(truedir);
        logger.debug("Final File: " + truedir + " CanRead: " + file.canRead());
        return file;
    }

    /**
     * Get the relative path (without mount point)
     * 
     * @param file
     * @return the relative path
     */
    protected String getRelativePath(File file) {
        return auth.getRelativePath(FilesystemBasedDirImpl.normalizePath(file.getAbsolutePath()));
    }

    public boolean isDirectory() throws CommandAbstractException {
        checkIdentify();
        File dir1 = getFileFromPath(currentFile);
        return dir1.isDirectory();
    }

    public boolean isFile() throws CommandAbstractException {
        checkIdentify();
        return getFileFromPath(currentFile).isFile();
    }

    public String getFile() throws CommandAbstractException {
        checkIdentify();
        return currentFile;
    }

    public boolean closeFile() throws CommandAbstractException {
        if (bfileChannelIn != null) {
            try {
                bfileChannelIn.close();
            } catch (IOException e) {
            }
            bfileChannelIn = null;
            bbyteBuffer = null;
        }
        if (fileOutputStream != null) {
            /*
             * try { rafOut.getFD().sync(); } catch (SyncFailedException e1) { } catch (IOException
             * e1) { }
             */
            try {
                fileOutputStream.flush();
                fileOutputStream.close();
            } catch (ClosedChannelException e) {
                // ignore
            } catch (IOException e) {
                throw new Reply550Exception("Close in error");
            }
            fileOutputStream = null;
        }
        position = 0;
        isReady = false;
        // Do not clear the filename itself
        return true;
    }

    public boolean abortFile() throws CommandAbstractException {
        if (isInWriting() && ((FilesystemBasedFileParameterImpl) getSession().getFileParameter()).deleteOnAbort) {
            delete();
        }
        closeFile();
        return true;
    }

    public long length() throws CommandAbstractException {
        checkIdentify();
        if (!isReady) {
            return -1;
        }
        if (!exists()) {
            return -1;
        }
        return getFileFromPath(currentFile).length();
    }

    public boolean isInReading() throws CommandAbstractException {
        if (!isReady) {
            return false;
        }
        return bfileChannelIn != null;
    }

    public boolean isInWriting() throws CommandAbstractException {
        if (!isReady) {
            return false;
        }
        return fileOutputStream != null;
    }

    public boolean canRead() throws CommandAbstractException {
        checkIdentify();
        if (!isReady) {
            return false;
        }
        return getFileFromPath(currentFile).canRead();
    }

    public boolean canWrite() throws CommandAbstractException {
        checkIdentify();
        if (!isReady) {
            return false;
        }
        File file = getFileFromPath(currentFile);
        if (file.exists()) {
            return file.canWrite();
        }
        return file.getParentFile().canWrite();
    }

    public boolean exists() throws CommandAbstractException {
        checkIdentify();
        if (!isReady) {
            return false;
        }
        return getFileFromPath(currentFile).exists();
    }

    public boolean delete() throws CommandAbstractException {
        checkIdentify();
        if (!isReady) {
            return false;
        }
        if (!exists()) {
            return true;
        }
        closeFile();
        return getFileFromPath(currentFile).delete();
    }

    public boolean renameTo(String path) throws CommandAbstractException {
        checkIdentify();
        if (!isReady) {
            logger.warn("File not ready: {}", this);
            return false;
        }
        File file = getFileFromPath(currentFile);
        if (file.canRead()) {
            File newFile = getFileFromPath(path);
            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 (newFile.getParentFile().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.warn("Cannot write file: {}", newFile);
                            return false;
                        }
                    } finally {
                        try {
                            if (fileOutputStream != null) {
                                fileOutputStream.close();
                            }
                        } catch (IOException e) {
                        }
                    }
                }
                currentFile = getRelativePath(newFile);
                isReady = true;
                logger.debug("File renamed to: {} and real position: {}", this, newFile);
                return true;
            } else {
                logger.warn("Cannot write file: {} from {}", newFile, file);
                return false;
            }
        }
        logger.warn("Cannot read file: {}", file);
        return false;
    }

    public DataBlock readDataBlock() throws FileTransferException, FileEndOfTransferException {
        if (isReady) {
            DataBlock dataBlock = new DataBlock();
            ByteBuf buffer = null;
            buffer = getBlock(getSession().getBlockSize());
            if (buffer != null) {
                dataBlock.setBlock(buffer);
                if (dataBlock.getByteCount() < getSession().getBlockSize()) {
                    dataBlock.setEOF(true);
                }
                return dataBlock;
            }
        }
        throw new FileTransferException("No file is ready");
    }

    public void writeDataBlock(DataBlock dataBlock) throws FileTransferException {
        if (isReady) {
            if (dataBlock.isEOF()) {
                writeBlockEnd(dataBlock.getBlock());
                return;
            }
            writeBlock(dataBlock.getBlock());
            return;
        }
        throw new FileTransferException("No file is ready while trying to write: " + dataBlock.toString());
    }

    /**
     * Valid Position of this file
     */
    private long position = 0;

    /**
     * FileOutputStream Out
     */
    private FileOutputStream fileOutputStream = null;
    /**
     * FileChannel In
     */
    private FileChannel bfileChannelIn = null;

    /**
     * Associated ByteBuffer
     */
    private ByteBuffer bbyteBuffer = null;

    /**
     * Return the current position in the FileInterface. In write mode, it is the current file
     * length.
     * 
     * @return the position
     */
    public long getPosition() {
        return position;
    }

    /**
     * Change the position in the file.
     * 
     * @param position
     *            the position to set
     * @throws IOException
     */
    public void setPosition(long position) throws IOException {
        this.position = position;
        if (bfileChannelIn != null) {
            bfileChannelIn = bfileChannelIn.position(position);
        }
        /*
         * if (rafOut != null) { rafOut.seek(position); }
         */
        if (fileOutputStream != null) {
            fileOutputStream.flush();
            fileOutputStream.close();
            fileOutputStream = getFileOutputStream(true);
            if (fileOutputStream == null) {
                throw new IOException("File cannot changed of Position");
            }
        }
    }

    private byte[] reusableBytes = null;

    /**
     * Write the current FileInterface with the given ByteBuf. The file is not limited to 2^32
     * bytes since this write operation is in add mode.
     * 
     * In case of error, the current already written blocks are maintained and the position is not
     * changed.
     * 
     * @param buffer
     *            added to the file
     * @throws FileTransferException
     */
    private void writeBlock(ByteBuf buffer) throws FileTransferException {
        if (!isReady) {
            throw new FileTransferException("No file is ready");
        }
        // An empty buffer is allowed
        if (buffer == null) {
            return;// could do FileEndOfTransfer ?
        }
        if (fileOutputStream == null) {
            // rafOut = getRandomFile();
            fileOutputStream = getFileOutputStream(position > 0);
        }
        if (fileOutputStream == null) {
            throw new FileTransferException("Internal error, file is not ready");
        }
        int bufferSize = buffer.readableBytes();
        int start = 0;
        byte[] newbuf;
        if (buffer.hasArray()) {
            start = buffer.arrayOffset();
            newbuf = buffer.array();
            buffer.readerIndex(buffer.readerIndex() + bufferSize);
        } else {
            if (reusableBytes == null || reusableBytes.length != bufferSize) {
                reusableBytes = new byte[bufferSize];
            }
            newbuf = reusableBytes;
            buffer.readBytes(newbuf);
        }
        try {
            fileOutputStream.write(newbuf, start, bufferSize);
        } catch (IOException e2) {
            logger.error("Error during write:", e2);
            try {
                closeFile();
            } catch (CommandAbstractException e1) {
            }
            // NO this.realFile.delete(); NO DELETE SINCE BY BLOCK IT CAN BE
            // REDO
            throw new FileTransferException("Internal error, file is not ready");
        }
        position += bufferSize;
    }

    /**
     * End the Write of the current FileInterface with the given ByteBuf. The file is not
     * limited to 2^32 bytes since this write operation is in add mode.
     * 
     * @param buffer
     *            added to the file
     * @throws FileTransferException
     */
    private void writeBlockEnd(ByteBuf buffer) throws FileTransferException {
        writeBlock(buffer);
        try {
            closeFile();
        } catch (CommandAbstractException e) {
            throw new FileTransferException("Close in error", e);
        }
    }

    /**
     * Get the current block ByteBuf of the current FileInterface. There is therefore no
     * limitation of the file size to 2^32 bytes.
     * 
     * The returned block is limited to sizeblock. If the returned block is less than sizeblock
     * length, it is the last block to read.
     * 
     * @param sizeblock
     *            is the limit size for the block array
     * @return the resulting block ByteBuf (even empty)
     * @throws FileTransferException
     * @throws FileEndOfTransferException
     */
    private ByteBuf getBlock(int sizeblock) throws FileTransferException, FileEndOfTransferException {
        if (!isReady) {
            throw new FileTransferException("No file is ready");
        }
        if (bfileChannelIn == null) {
            bfileChannelIn = getFileChannel();
            if (bfileChannelIn != null) {
                if (bbyteBuffer != null) {
                    if (bbyteBuffer.capacity() != sizeblock) {
                        bbyteBuffer = null;
                        bbyteBuffer = ByteBuffer.allocateDirect(sizeblock);
                    }
                } else {
                    bbyteBuffer = ByteBuffer.allocateDirect(sizeblock);
                }
            }
        }
        if (bfileChannelIn == null) {
            throw new FileTransferException("Internal error, file is not ready");
        }
        int sizeout = 0;
        while (sizeout < sizeblock) {
            try {
                int sizeread = bfileChannelIn.read(bbyteBuffer);
                if (sizeread <= 0) {
                    break;
                }
                sizeout += sizeread;
            } catch (IOException e) {
                logger.error("Error during get:", e);
                try {
                    closeFile();
                } catch (CommandAbstractException e1) {
                }
                throw new FileTransferException("Internal error, file is not ready");
            }
        }
        if (sizeout <= 0) {
            try {
                closeFile();
            } catch (CommandAbstractException e1) {
            }
            isReady = false;
            throw new FileEndOfTransferException("End of file");
        }
        bbyteBuffer.flip();
        position += sizeout;
        ByteBuf buffer = Unpooled.wrappedBuffer(bbyteBuffer);
        bbyteBuffer.clear();
        if (sizeout < sizeblock) {// last block
            try {
                closeFile();
            } catch (CommandAbstractException e1) {
            }
            isReady = false;
        }
        return buffer;
    }

    /**
     * Write the FileInterface to the fileChannelOut, thus bypassing the limitation of the file size
     * to 2^32 bytes.
     * 
     * This call closes the fileChannelOut with fileChannelOut.close() if the operation is in
     * success.
     * 
     * @param fileChannelOut
     * @return True if OK, False in error.
     */
    protected boolean get(FileChannel fileChannelOut) {
        if (!isReady) {
            return false;
        }
        FileChannel fileChannelIn = getFileChannel();
        if (fileChannelIn == null) {
            return false;
        }
        long size = 0;
        long transfert = 0;
        try {
            size = fileChannelIn.size();
            if (size < 0) {
                try {
                    size = length();
                } catch (CommandAbstractException e) {
                    logger.error("Error during get:", e);
                    return false;
                }
                if (size < 0) {
                    logger.error("Error during get, wrong size: " + size);
                    return false;
                }
            }
            int chunkSize = this.session.getBlockSize();
            if (chunkSize < 0) {
                chunkSize = 8192;
            }
            while (transfert < size) {
                transfert += fileChannelOut.transferFrom(fileChannelIn, transfert, chunkSize);
            }
            fileChannelOut.force(true);
        } catch (IOException e) {
            logger.error("Error during get:", e);
            return false;
        } finally {
            try {
                fileChannelOut.close();
            } catch (IOException e) {
            }
            try {
                fileChannelIn.close();
            } catch (IOException e) {
            }
            fileChannelIn = null;
        }
        if (transfert == size) {
            position += size;
        }
        return transfert == size;
    }

    /**
     * Returns the FileChannel in In mode associated with the current file.
     * 
     * @return the FileChannel (IN mode)
     */
    protected FileChannel getFileChannel() {
        if (!isReady) {
            return null;
        }
        File trueFile;
        try {
            trueFile = getFileFromPath(currentFile);
        } catch (CommandAbstractException e1) {
            return null;
        }
        FileChannel fileChannel = null;
        try {
            @SuppressWarnings("resource")
            FileInputStream fileInputStream = new FileInputStream(trueFile);
            fileChannel = fileInputStream.getChannel();
            if (position != 0) {
                fileChannel = fileChannel.position(position);
            }
        } catch (FileNotFoundException e) {
            logger.error("File not found in getFileChannel:", e);
            return null;
        } catch (IOException e) {
            if (fileChannel != null) {
                try {
                    fileChannel.close();
                } catch (IOException e1) {
                }
            }
            logger.error("Change position in getFileChannel:", e);
            return null;
        }
        return fileChannel;
    }

    /**
     * Returns the RandomAccessFile in Out mode associated with the current file.
     * 
     * @return the RandomAccessFile (OUT="rw")
     */
    protected RandomAccessFile getRandomFile() {
        if (!isReady) {
            return null;
        }
        File trueFile;
        try {
            trueFile = getFileFromPath(currentFile);
        } catch (CommandAbstractException e1) {
            return null;
        }
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(trueFile, "rw");
            raf.seek(position);
        } 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 (!isReady) {
            return null;
        }
        File trueFile;
        try {
            trueFile = getFileFromPath(currentFile);
        } catch (CommandAbstractException e1) {
            return null;
        }
        if (position > 0) {
            if (trueFile.length() < position) {
                logger.error(
                        "Cannot Change position in getFileOutputStream: file is smaller than required position");
                return null;
            }
            RandomAccessFile raf = getRandomFile();
            try {
                raf.setLength(position);
                raf.close();
            } catch (IOException e) {
                logger.error("Change position in getFileOutputStream:", e);
                return null;
            }
            logger.debug("New size: " + trueFile.length() + " : " + position);
        }
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(trueFile, append);
        } catch (FileNotFoundException e) {
            logger.error("File not found in getRandomFile:", e);
            return null;
        }
        return fos;
    }
}