Flush writer.
[xudocci.git] / src / main / java / net / pterodactylus / irc / DccReceiver.java
1 /*
2  * XdccDownloader - DccReceiver.java - Copyright © 2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.irc;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.net.InetAddress;
24 import java.net.Socket;
25 import java.util.concurrent.TimeUnit;
26 import java.util.logging.Level;
27 import java.util.logging.Logger;
28
29 import net.pterodactylus.irc.event.DccDownloadFailed;
30 import net.pterodactylus.irc.event.DccDownloadFinished;
31 import net.pterodactylus.irc.event.DccSendReceived;
32 import net.pterodactylus.xdcc.util.io.BandwidthCountingInputStream;
33
34 import com.google.common.eventbus.EventBus;
35 import com.google.common.io.Closeables;
36 import com.google.common.util.concurrent.AbstractExecutionThreadService;
37
38 /**
39  * Service that receives a file offered by a {@link DccSendReceived}.
40  *
41  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
42  */
43 public class DccReceiver extends AbstractExecutionThreadService {
44
45         /** The logger. */
46         private static final Logger logger = Logger.getLogger(DccReceiver.class.getName());
47
48         /** The event bus. */
49         private final EventBus eventBus;
50
51         /** The address to connect to. */
52         private final InetAddress inetAddress;
53
54         /** The port number to connect to. */
55         private final int port;
56
57         /** The name of the file being offered. */
58         private final String filename;
59
60         /** The size of the file being offered. */
61         private final long size;
62
63         /** The output stream to write the file to. */
64         private final OutputStream outputStream;
65
66         /** The number of bytes already written. */
67         private long progress;
68
69         /** The bandwidth-measuring input stream. */
70         private BandwidthCountingInputStream inputStream;
71
72         /**
73          * Creates a new DCC receiver.
74          *
75          * @param inetAddress
76          *              The address to connect to
77          * @param port
78          *              The port number to connect to
79          * @param filename
80          *              The name of the file being downloaded
81          * @param size
82          *              The size of the file being downloaded, or {@code -1} if the size is not
83          *              known
84          * @param outputStream
85          *              The output stream to write the file to
86          */
87         public DccReceiver(EventBus eventBus, InetAddress inetAddress, int port, String filename, long size, OutputStream outputStream) {
88                 this(eventBus, inetAddress, port, filename, 0, size, outputStream);
89         }
90
91         /**
92          * Creates a new DCC receiver.
93          *
94          * @param inetAddress
95          *              The address to connect to
96          * @param port
97          *              The port number to connect to
98          * @param filename
99          *              The name of the file being downloaded
100          * @param startOffset
101          *              The offset at which the download starts in case of a resume
102          * @param size
103          *              The size of the file being downloaded, or {@code -1} if the size is not
104          *              known
105          * @param outputStream
106          *              The output stream to write the file to
107          */
108         public DccReceiver(EventBus eventBus, InetAddress inetAddress, int port, String filename, long startOffset, long size, OutputStream outputStream) {
109                 this.eventBus = eventBus;
110                 this.inetAddress = inetAddress;
111                 this.port = port;
112                 this.filename = filename;
113                 this.progress = startOffset;
114                 this.size = size;
115                 this.outputStream = outputStream;
116         }
117
118         //
119         // ACCESSORS
120         //
121
122         /**
123          * Returns the name of the file being downloaded. The name is not used by the
124          * DCC receiver, it only serves as a kind of identifier.
125          *
126          * @return The name of the file being downloaded
127          */
128         public String filename() {
129                 return filename;
130         }
131
132         /**
133          * Returns the size of the file being downloaded. If the size of the file is
134          * not known, {@code -1} is returned.
135          *
136          * @return The size of the file being downloaded, or {@code -1} if the size is
137          *         not known
138          */
139         public long size() {
140                 return size;
141         }
142
143         /**
144          * Returns the number of bytes that have already been downloaded.
145          *
146          * @return The number of bytes that have already been downloaded
147          */
148         public long progress() {
149                 return progress;
150         }
151
152         /**
153          * Returns the current rate of the download.
154          *
155          * @return The current rate of the download, in bytes/second
156          */
157         public long currentRate() {
158                 return (inputStream != null) ? inputStream.getCurrentRate() : 0;
159         }
160
161         /**
162          * Returns the overall rate of the download.
163          *
164          * @return The overall rate of the download, in bytes/second
165          */
166         public long overallRate() {
167                 return (inputStream != null) ? inputStream.getOverallRate() : 0;
168         }
169
170         //
171         // ABSTRACTEXECUTIONTHREADSERVICE METHODS
172         //
173
174         @Override
175         protected void run() throws IOException {
176                 Socket socket = null;
177                 try {
178                         socket = new Socket(inetAddress, port);
179                         socket.setSoTimeout((int) TimeUnit.MINUTES.toMillis(3));
180                         InputStream socketInputStream = socket.getInputStream();
181                         inputStream = new BandwidthCountingInputStream(socketInputStream, 5, TimeUnit.SECONDS);
182                         byte[] buffer = new byte[65536];
183                         while (isRunning() && ((size == -1) || (progress < size))) {
184                                 int r = inputStream.read(buffer);
185                                 if (r == -1) {
186                                         /* yay, eof! */
187                                         break;
188                                 }
189                                 outputStream.write(buffer, 0, r);
190                                 progress += r;
191                         }
192                         outputStream.flush();
193                         if ((size == -1) || (progress == size)) {
194                                 eventBus.post(new DccDownloadFinished(this));
195                         } else {
196                                 eventBus.post(new DccDownloadFailed(this, new IOException("Download aborted.")));
197                         }
198                 } catch (IOException ioe1) {
199                         logger.log(Level.WARNING, "I/O error while receiving DCC!", ioe1);
200                         eventBus.post(new DccDownloadFailed(this, ioe1));
201                 } finally {
202                         Closeables.close(inputStream, true);
203                         if (socket != null) {
204                                 socket.close();
205                         }
206                 }
207         }
208
209 }