/** * 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.passthrough; import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; import io.netty.buffer.ByteBuf; import org.waarp.common.command.exception.CommandAbstractException; import org.waarp.common.command.exception.Reply450Exception; 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 Passthrough Based. It is just an empty shell since in pass through mode, * no directories or files really exist. * * If one wants to implement special actions, he/she just has to extend this class and override the * default empty implementation. * * @author Frederic Bregier * */ public abstract class PassthroughBasedFileImpl extends AbstractFile { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(PassthroughBasedFileImpl.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 PassthroughBasedDirImpl dir; /** * {@link PassthroughBasedAuthImpl} */ private final PassthroughBasedAuthImpl auth; /** * Current file if any */ protected String currentFile = null; /** * Is this file in append mode */ protected boolean isAppend = false; /** * Passthrough object */ protected PassthroughFile pfile = null; /** * Factory for PassthroughFile */ protected static PassthroughFileFactory factory = null; /** * @param session * @param dir * It is not necessary the directory that owns this file. * @param path * @param append * @throws CommandAbstractException * @throws PassthroughException */ public PassthroughBasedFileImpl(SessionInterface session, PassthroughBasedDirImpl dir, String path, boolean append) throws CommandAbstractException { this.session = session; auth = (PassthroughBasedAuthImpl) session.getAuth(); this.dir = dir; currentFile = path; isReady = true; isAppend = append; File file = getFileFromPath(path); try { pfile = factory.create(this); } catch (PassthroughException e1) { throw new Reply450Exception(e1.getMessage()); } if (append) { try { setPosition(file.length()); } catch (IOException e) { logger.error("Error during position:", e); } } else { try { setPosition(0); } catch (IOException e) { } } } 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); return new File(truedir); } /** * Get the relative path (without mount point) * * @param file * @return the relative path */ protected String getRelativePath(File file) { return auth.getRelativePath(PassthroughBasedDirImpl.normalizePath(file .getAbsolutePath())); } public boolean isDirectory() throws CommandAbstractException { checkIdentify(); return pfile.isDirectory(); } public boolean isFile() throws CommandAbstractException { checkIdentify(); return pfile.isFile(); } public String getFile() throws CommandAbstractException { checkIdentify(); return currentFile; } public boolean closeFile() throws CommandAbstractException { try { pfile.close(); } catch (PassthroughException e) { throw new Reply450Exception(e.getMessage()); } position = 0; isReady = false; // Do not clear the filename itself return true; } public boolean abortFile() throws CommandAbstractException { if (isInWriting() && ((PassthroughBasedFileParameterImpl) getSession() .getFileParameter()).deleteOnAbort) { delete(); } closeFile(); return true; } public long length() throws CommandAbstractException { checkIdentify(); if (!isReady) { return -1; } if (!exists()) { return -1; } return pfile.length(); } public boolean isInReading() throws CommandAbstractException { if (!isReady) { return false; } return pfile.isInReading(); } public boolean isInWriting() throws CommandAbstractException { if (!isReady) { return false; } return pfile.isInWriting(); } public boolean canRead() throws CommandAbstractException { checkIdentify(); if (!isReady) { return false; } return pfile.canRead(); } public boolean canWrite() throws CommandAbstractException { checkIdentify(); if (!isReady) { return false; } return pfile.canWrite(); } public boolean exists() throws CommandAbstractException { checkIdentify(); if (!isReady) { return false; } return pfile.exists(); } public boolean delete() throws CommandAbstractException { checkIdentify(); if (!isReady) { return false; } if (!exists()) { return true; } closeFile(); try { return pfile.delete(); } catch (PassthroughException e) { throw new Reply550Exception(e.getMessage()); } } public boolean renameTo(String path) throws CommandAbstractException { checkIdentify(); if (!isReady) { return false; } try { return pfile.renameTo(path); } catch (PassthroughException e) { throw new Reply550Exception(e.getMessage()); } } 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"); } /** * Valid Position of this file */ private long position = 0; /** * 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 { try { pfile.position(position); } catch (PassthroughException e) { throw new IOException(e); } this.position = position; } /** * Try to flush written data if possible */ public void flush() { if (isReady) { try { pfile.flush(); } catch (PassthroughException e) { } } } /** * 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 ? } int bufferSize = buffer.readableBytes(); int size; try { size = pfile.write(buffer); } catch (PassthroughException e) { throw new FileTransferException("Cannot write to file"); } boolean result = size == bufferSize; if (!result) { try { pfile.close(); } catch (PassthroughException e) { } // NO this.realFile.delete(); NO DELETE SINCE BY BLOCK IT CAN BE // REDO throw new FileTransferException("Internal error, file is not ready"); } position += size; } /** * 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) { } } /** * 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"); } ByteBuf buffer; try { buffer = pfile.read(sizeblock); } catch (PassthroughException e) { throw new FileEndOfTransferException("Cannot read the file"); } int sizeout = buffer.readableBytes(); if (sizeout < sizeblock) {// last block try { pfile.close(); } catch (PassthroughException e) { } isReady = false; } if (sizeout <= 0) { throw new FileEndOfTransferException("End of file"); } position += sizeout; 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; } long size = pfile.length(); long transfert; try { transfert = pfile.transferTo(fileChannelOut); } catch (PassthroughException e) { return false; } if (transfert == size) { position += size; } return transfert == size; } }