View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.ftp.filesystembased;
21  
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelFuture;
24  import org.waarp.common.command.exception.CommandAbstractException;
25  import org.waarp.common.exception.FileEndOfTransferException;
26  import org.waarp.common.exception.FileTransferException;
27  import org.waarp.common.file.DataBlock;
28  import org.waarp.common.file.filesystembased.FilesystemBasedFileImpl;
29  import org.waarp.common.logging.WaarpLogger;
30  import org.waarp.common.logging.WaarpLoggerFactory;
31  import org.waarp.common.utility.WaarpNettyUtil;
32  import org.waarp.ftp.core.exception.FtpNoConnectionException;
33  import org.waarp.ftp.core.file.FtpFile;
34  import org.waarp.ftp.core.session.FtpSession;
35  
36  import java.util.concurrent.locks.ReentrantLock;
37  
38  /**
39   * Filesystem implementation of a FtpFile
40   */
41  public abstract class FilesystemBasedFtpFile extends FilesystemBasedFileImpl
42      implements FtpFile {
43    /**
44     * Internal Logger
45     */
46    private static final WaarpLogger logger =
47        WaarpLoggerFactory.getLogger(FilesystemBasedFtpFile.class);
48  
49    /**
50     * Retrieve lock to ensure only one call at a time for one file
51     */
52    private final ReentrantLock retrieveLock = new ReentrantLock();
53  
54    /**
55     * @param session
56     * @param dir It is not necessary the directory that owns this file.
57     * @param path
58     * @param append
59     *
60     * @throws CommandAbstractException
61     */
62    protected FilesystemBasedFtpFile(final FtpSession session,
63                                     final FilesystemBasedFtpDir dir,
64                                     final String path, final boolean append)
65        throws CommandAbstractException {
66      super(session, dir, path, append);
67    }
68  
69    @Override
70    public final long length() throws CommandAbstractException {
71      long length = super.length();
72      if (((FtpSession) getSession()).getDataConn()
73                                     .isFileStreamBlockAsciiImage()) {
74        final long block = (long) Math.ceil(
75            (double) length / (double) getSession().getBlockSize());
76        length += (block + 3) * 3;
77      }
78      return length;
79    }
80  
81    /**
82     * Launch retrieve operation (internal method, should not be called
83     * directly)
84     */
85    @Override
86    public final void trueRetrieve() {
87      retrieveLock.lock();
88      try {
89        if (!isReady) {
90          return;
91        }
92        // First check if ready to run from Control
93        try {
94          ((FtpSession) session).getDataConn().getFtpTransferControl()
95                                .waitForDataNetworkHandlerReady();
96        } catch (final InterruptedException e) {//NOSONAR
97          // bad thing
98          logger.warn("DataNetworkHandler was not ready", e);
99          if (isInReading()) {
100           logger.error("Should not be", e);
101           ((FtpSession) session).getDataConn().getFtpTransferControl()
102                                 .setTransferAbortedFromInternal(true);
103         }
104         logger.debug(
105             "Possible call while channel was on going to be closed once transfer was done",
106             e);
107         closeFile();
108         ((FtpSession) session).getDataConn().getFtpTransferControl()
109                               .setPreEndOfTransfer();
110         return;
111       }
112       final Channel channel;
113       try {
114         channel = ((FtpSession) session).getDataConn().getCurrentDataChannel();
115       } catch (final FtpNoConnectionException e) {
116         if (isInReading()) {
117           logger.error("Should not be", e);
118           ((FtpSession) session).getDataConn().getFtpTransferControl()
119                                 .setTransferAbortedFromInternal(true);
120         }
121         logger.debug(
122             "Possible call while channel was on going to be closed once transfer was done",
123             e);
124         closeFile();
125         ((FtpSession) session).getDataConn().getFtpTransferControl()
126                               .setPreEndOfTransfer();
127         return;
128       }
129       DataBlock block;
130       try {
131         block = readDataBlock();
132       } catch (final FileEndOfTransferException e) {
133         // Last block (in fact, previous block was the last one,
134         // but it could be aligned with the block size so not
135         // detected)
136         closeFile();
137         ((FtpSession) session).getDataConn().getFtpTransferControl()
138                               .setPreEndOfTransfer();
139         return;
140       }
141       if (block == null) {
142         // Last block (in fact, previous block was the last one,
143         // but it could be aligned with the block size so not
144         // detected)
145         closeFile();
146         ((FtpSession) session).getDataConn().getFtpTransferControl()
147                               .setPreEndOfTransfer();
148         return;
149       }
150       // While not last block
151       ChannelFuture future = null;
152       while (block != null && !block.isEOF()) {
153         future = channel.writeAndFlush(block);
154         WaarpNettyUtil.awaitOrInterrupted(future);
155         if (!future.isSuccess()) {
156           closeFile();
157           throw new FileTransferException("File transfer in error");
158         }
159         try {
160           block = readDataBlock();
161         } catch (final FileEndOfTransferException e) {
162           closeFile();
163           // Wait for last write
164           if (future.isSuccess()) {
165             ((FtpSession) session).getDataConn().getFtpTransferControl()
166                                   .setPreEndOfTransfer();
167           } else {
168             throw new FileTransferException("File transfer in error");
169           }
170           return;
171         }
172       }
173       // Last block
174       closeFile();
175       if (block != null) {
176         logger.debug("Write {}", block.getByteCount());
177         future = channel.writeAndFlush(block);
178       }
179       // Wait for last write
180       if (future != null) {
181         WaarpNettyUtil.awaitOrInterrupted(future);
182         if (future.isSuccess()) {
183           ((FtpSession) session).getDataConn().getFtpTransferControl()
184                                 .setPreEndOfTransfer();
185         } else {
186           throw new FileTransferException("LAST Write is not successful",
187                                           future.cause());
188         }
189       }
190     } catch (final FileTransferException e) {
191       // An error occurs!
192       logger.error("File Transfer Exception: {}", e.getMessage(), e);
193       ((FtpSession) session).getDataConn().getFtpTransferControl()
194                             .setTransferAbortedFromInternal(true);
195     } catch (final CommandAbstractException e) {
196       logger.error("Should not be", e);
197       ((FtpSession) session).getDataConn().getFtpTransferControl()
198                             .setTransferAbortedFromInternal(true);
199     } finally {
200       retrieveLock.unlock();
201     }
202   }
203 }