Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DCC SEND & RECEIVE Updates #379

Merged
merged 8 commits into from
Jul 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions src/main/java/org/pircbotx/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.pircbotx.dcc.ReceiveFileTransfer;
import org.pircbotx.dcc.SendChat;
import org.pircbotx.dcc.SendFileTransfer;
import org.pircbotx.dcc.DccHandler.PendingFileTransfer;
import org.pircbotx.delay.Delay;
import org.pircbotx.delay.StaticDelay;
import org.pircbotx.exception.IrcException;
Expand Down Expand Up @@ -95,7 +96,6 @@ public class Configuration {
protected final InetAddress dccPublicAddress;
protected final int dccAcceptTimeout;
protected final int dccResumeAcceptTimeout;
protected final int dccReceiveTransferBufferSize;
protected final boolean dccPassiveRequest;
//Connect information
protected final ImmutableList<ServerEntry> servers;
Expand Down Expand Up @@ -154,7 +154,6 @@ protected Configuration(Builder builder) {
checkNotNull(builder.getDccPorts(), "DCC ports list cannot be null");
checkArgument(builder.getDccAcceptTimeout() > 0, "dccAcceptTimeout must be positive");
checkArgument(builder.getDccResumeAcceptTimeout() > 0, "dccResumeAcceptTimeout must be positive");
checkArgument(builder.getDccReceiveTransferBufferSize() > 0, "dccReceiveTransferBufferSize must be positive");
checkNotNull(builder.getServers(), "Servers list cannot be null");
checkArgument(!builder.getServers().isEmpty(), "Must specify servers to connect to");
for (ServerEntry serverEntry : builder.getServers()) {
Expand Down Expand Up @@ -204,7 +203,6 @@ protected Configuration(Builder builder) {
this.dccPublicAddress = builder.getDccPublicAddress();
this.dccAcceptTimeout = builder.getDccAcceptTimeout();
this.dccResumeAcceptTimeout = builder.getDccResumeAcceptTimeout();
this.dccReceiveTransferBufferSize = builder.getDccReceiveTransferBufferSize();
this.dccPassiveRequest = builder.isDccPassiveRequest();
this.servers = ImmutableList.copyOf(builder.getServers());
this.serverPassword = builder.getServerPassword();
Expand Down Expand Up @@ -352,10 +350,6 @@ public static class Builder {
* }
*/
protected int dccResumeAcceptTimeout = -1;
/**
* Size of the DCC Receive file transfer buffer, default 1024 bytes
*/
protected int dccReceiveTransferBufferSize = 1024;
/**
* Send DCC requests as passive/reverse requests if not specified
* otherwise, default false
Expand Down Expand Up @@ -560,7 +554,6 @@ public Builder(Configuration configuration) {
this.dccPublicAddress = configuration.getDccPublicAddress();
this.dccAcceptTimeout = configuration.getDccAcceptTimeout();
this.dccResumeAcceptTimeout = configuration.getDccResumeAcceptTimeout();
this.dccReceiveTransferBufferSize = configuration.getDccReceiveTransferBufferSize();
this.dccPassiveRequest = configuration.isDccPassiveRequest();
this.servers.clear();
this.servers.addAll(configuration.getServers());
Expand Down Expand Up @@ -624,7 +617,6 @@ public Builder(Builder otherBuilder) {
this.dccPublicAddress = otherBuilder.getDccPublicAddress();
this.dccAcceptTimeout = otherBuilder.getDccAcceptTimeout();
this.dccResumeAcceptTimeout = otherBuilder.getDccResumeAcceptTimeout();
this.dccReceiveTransferBufferSize = otherBuilder.getDccReceiveTransferBufferSize();
this.dccPassiveRequest = otherBuilder.isDccPassiveRequest();
this.servers.clear();
this.servers.addAll(otherBuilder.getServers());
Expand Down Expand Up @@ -982,12 +974,14 @@ public ReceiveChat createReceiveChat(PircBotX bot, User user, Socket socket) thr
return new ReceiveChat(user, socket, bot.getConfiguration().getEncoding());
}

public SendFileTransfer createSendFileTransfer(PircBotX bot, Socket socket, User user, File file, long startPosition) {
return new SendFileTransfer(bot.getConfiguration(), socket, user, file, startPosition);
public SendFileTransfer createSendFileTransfer(PircBotX bot, DccHandler dccHandler,
PendingFileTransfer pendingFileTransfer, File file) {
return new SendFileTransfer(bot, dccHandler, pendingFileTransfer, file);
}

public ReceiveFileTransfer createReceiveFileTransfer(PircBotX bot, Socket socket, User user, File file, long startPosition, long fileSize) {
return new ReceiveFileTransfer(bot.getConfiguration(), socket, user, file, startPosition, fileSize);
public ReceiveFileTransfer createReceiveFileTransfer(PircBotX bot, DccHandler dccHandler,
PendingFileTransfer pendingFileTransfer, File file) {
return new ReceiveFileTransfer(bot, dccHandler, pendingFileTransfer, file);
}

public ServerInfo createServerInfo(PircBotX bot) {
Expand Down
391 changes: 291 additions & 100 deletions src/main/java/org/pircbotx/dcc/DccHandler.java

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/main/java/org/pircbotx/dcc/DccState.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
* @author Leon Blakey
*/
public enum DccState {
INIT, RUNNING, DONE;
INIT, CONNECTING, RUNNING, WAITING, DONE, SHUTDOWN, ERROR;
}
107 changes: 63 additions & 44 deletions src/main/java/org/pircbotx/dcc/FileTransfer.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,88 +20,107 @@
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;

import lombok.Getter;
import lombok.NonNull;

import org.pircbotx.Configuration;
import org.pircbotx.PircBotX;
import org.pircbotx.User;
import org.pircbotx.dcc.DccHandler.PendingFileTransfer;
import org.pircbotx.exception.DccException;
import org.pircbotx.exception.DccException.Reason;
import org.pircbotx.hooks.events.FileTransferCompleteEvent;

/**
* A general active DCC file transfer
*
* @author Leon Blakey
*/
public abstract class FileTransfer {
@NonNull
protected final PircBotX bot;
@NonNull
protected final Configuration configuration;
@NonNull
protected final Socket socket;
protected final DccHandler dccHandler;
@NonNull
protected Socket socket;
@NonNull
@Getter
protected final User user;
@NonNull
@Getter
protected final File file;
@Getter
protected final long startPosition;
@Getter
protected final long fileSize;
@Getter
protected long bytesTransfered;
@Getter
protected DccState state = DccState.INIT;
protected FileTransferStatus fileTransferStatus;

protected PendingFileTransfer pendingFileTransfer;

protected final Object stateLock = new Object();

public FileTransfer(Configuration configuration, Socket socket, User user, File file, long startPosition, long fileSize) {
this.configuration = configuration;
this.socket = socket;
this.user = user;
public FileTransfer(PircBotX bot, DccHandler dccHandler, PendingFileTransfer pendingFileTransfer, File file) {
this.bot = bot;
this.configuration = bot.getConfiguration();
this.pendingFileTransfer = pendingFileTransfer;
this.user = pendingFileTransfer.user;
this.file = file;
this.startPosition = startPosition;
this.fileSize = fileSize;
this.dccHandler = dccHandler;
fileTransferStatus = new FileTransferStatus(pendingFileTransfer.fileSize, pendingFileTransfer.position);
}

private void connectSocket() throws IOException {
socket = dccHandler.establishSocketConnection(pendingFileTransfer);
}

//Clients use bytesTransferred to see where we are in the file
this.bytesTransfered = startPosition;
public void shutdown() {
fileTransferStatus.dccState = DccState.SHUTDOWN;
}

/**
* Transfer the file to the user
*
* @throws IOException If an error occurred during transfer
*/
public void transfer() throws IOException {
//Prevent being called multiple times
if (state != DccState.INIT)
public void transfer() {

// Prevent being called multiple times
if (fileTransferStatus.dccState != DccState.INIT) {
synchronized (stateLock) {
if (state != DccState.INIT)
throw new RuntimeException("Cannot receive file twice (Current state: " + state + ")");
if (fileTransferStatus.dccState != DccState.INIT) {
throw new RuntimeException(
"Cannot receive file twice (Current state: " + fileTransferStatus.dccState + ")");
}
}
state = DccState.RUNNING;
}

transferFile();
fileTransferStatus.dccState = DccState.CONNECTING;

state = DccState.DONE;
}
try {
connectSocket();

protected abstract void transferFile() throws IOException;
fileTransferStatus.dccState = DccState.RUNNING;

/**
* Callback at end of read/write loop:
* <p>
* Receive: Socket read -> file write -> socket write (bytes transferred) ->
* callback -> repeat
* <p>
* Send: File read -> socket write -> socket read (bytes transferred) ->
* callback -> repeat
*/
protected void onAfterSend() {
}
transferFile();

} catch (SocketTimeoutException e) {
fileTransferStatus.dccState = DccState.ERROR;
fileTransferStatus.exception = new DccException(Reason.FILE_TRANSFER_TIMEOUT, user, "Socket connection timeout", e);
} catch (IOException e) {
fileTransferStatus.dccState = DccState.ERROR;
fileTransferStatus.exception = new DccException(Reason.FILE_TRANSFER_TIMEOUT, user, "General IOException", e);
} finally {

bot.getConfiguration().getListenerManager()
.onEvent(new FileTransferCompleteEvent(bot, fileTransferStatus, user, this.getFile().getName(),
(socket != null) ? socket.getInetAddress() : null,
(socket != null) ? socket.getLocalPort() : 0, fileTransferStatus.fileSize,
pendingFileTransfer.passive, true));
}

/**
* Is the transfer finished?
*
* @return True if its finished
*/
public boolean isFinished() {
return state == DccState.DONE;
}

protected abstract void transferFile();

}
113 changes: 113 additions & 0 deletions src/main/java/org/pircbotx/dcc/FileTransferStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com>
*
* This file is part of PircBotX.
*
* PircBotX 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.
*
* PircBotX 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
* PircBotX. If not, see <http://www.gnu.org/licenses/>.
*/
package org.pircbotx.dcc;

import java.util.concurrent.TimeUnit;

import org.pircbotx.exception.DccException;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
* Information about a file transfer This is kept in sync by the instances of
* SendFileTransfer and ReceiveFileTransfer
*
* @author Rob
*/
@Slf4j
public class FileTransferStatus extends Thread {

@Getter
protected DccState dccState = DccState.INIT;
@Getter
protected long startPosition = 0;
@Getter
protected long fileSize = 0;
@Getter
protected long bytesTransfered = 0;
@Getter
protected long bytesAcknowledged = 0;
@Getter
protected long bytesPerSecond = 0;
@Getter
protected long averageBytesPerSecond = 0;
@Getter
protected DccException exception;

public FileTransferStatus(long fileSize, long startPosition) {
this.fileSize = fileSize;
this.startPosition = startPosition;
}

/**
* Is the transfer finished?
*
* @return True if its finished
*/
public boolean isFinished() {
return (dccState == DccState.DONE || dccState == DccState.ERROR);
}

/**
* Was the transfer successful
*
* @return True if done and bytes acknowledged match file size
*/
public boolean isSuccessful() {
return (dccState == DccState.DONE && bytesAcknowledged == fileSize);
}

/**
* Get percentage of file transfer. This is calculated based on the bytes
* received by the transfer requester
*
* @return integer
*/
public double getPercentageComplete() {
return (100 * ((double) bytesAcknowledged / fileSize));
}

@Override
public void run() {
int counter = 0;
long myBytesAcknowleged = startPosition;
long myBytesAveraged = 0;
long myBytesPerSecond = 0;
while (dccState == DccState.RUNNING) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
log.error("Speed calculation has interrupted?", e);
}
bytesPerSecond = bytesAcknowledged - myBytesAcknowleged;
if (bytesPerSecond < 0) {
continue;
}
myBytesPerSecond = bytesPerSecond;
// Attempt to make this a smooth average. Is there some calculus way to do this?
myBytesAveraged = (myBytesPerSecond + myBytesAveraged) / 2;
counter++;
if (counter >= 3) {
averageBytesPerSecond = (myBytesAveraged + averageBytesPerSecond) / 2;
counter = 0;
}
myBytesAcknowleged = bytesAcknowledged;
}
}
}
Loading