package de.tum.in.www1.jReto.connectivity; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.UUID; import de.tum.in.www1.jReto.Connection.DataProvider; import de.tum.in.www1.jReto.connectivity.packet.CancelledTransferPacket; import de.tum.in.www1.jReto.connectivity.packet.DataPacket; import de.tum.in.www1.jReto.connectivity.packet.ProgressInformationPacket; import de.tum.in.www1.jReto.connectivity.packet.StartedTransferPacket; import de.tum.in.www1.jReto.connectivity.packet.ProgressInformationPacket.TransferProgressInformation; import de.tum.in.www1.jReto.packet.PacketType; /** * The TransferManager class is responsible to perform data transfers. It supports resuming transfers after a connection failed during a transfer, * and the cancellation of transfers. */ public class TransferProcessor implements PacketConnection.Handler, TransferManager { public static interface TransferProcessorHandler { /** Called when an incoming transfer starts. */ void notifyTransferStarted(InTransfer transfer); } /** The TransferManager's delegate.*/ private TransferProcessorHandler handler; /** The packetConnection used to send and receive packets. */ private final PacketConnection packetConnection; /** Whether all transfers are currently interrupted. This is the case when a packet connection's underlying connection fails. */ private boolean isInterrupted; /** The transfer that is currently being received. */ private InTransfer currentInTransfer; /** The transfer that is currently being sent. */ private OutTransfer currentOutTransfer; /** A queue of transfers that will be sent next. */ private Queue<OutTransfer> outTransferQueue; /** * Constructs a new TransferManager. * * @param packetConnection The PacketConnection used to send and receive data transfers. */ public TransferProcessor(PacketConnection packetConnection) { this.outTransferQueue = new LinkedList<OutTransfer>(); this.isInterrupted = false; this.packetConnection = packetConnection; this.packetConnection.addDelegate(this); } /** * Starts a transfer. * * @param dataLength The length of the transfer in bytes. * @param dataProvider A function that returns data for a given range. * @return An OutTransfer object. */ public OutTransfer startTransfer(int transferLength, DataProvider dataProvider) { OutTransfer transfer = new OutTransfer(this, transferLength, dataProvider, UUID.randomUUID()); this.outTransferQueue.add(transfer); this.packetConnection.write(); return transfer; } /** Cancels an incoming transfer. */ public void cancelTransfer(InTransfer transfer) { if (transfer == null) throw new IllegalArgumentException("transfer may not be null."); if (transfer == this.currentInTransfer) { this.packetConnection.writePacket(new CancelledTransferPacket(this.currentInTransfer.getIdentifier())); } else { throw new IllegalArgumentException("Transfer is not the current in transfger"); } this.packetConnection.write(); } /** Cancels an outgoing transfer. */ public void cancelTransfer(OutTransfer transfer) { if (transfer == null) throw new IllegalArgumentException("transfer may not be null."); if (this.outTransferQueue.contains(transfer)) { this.outTransferQueue.remove(transfer); transfer.confirmCancel(); return; } else if (transfer == this.currentOutTransfer) { this.packetConnection.writePacket(new CancelledTransferPacket(this.currentOutTransfer.getIdentifier())); this.currentOutTransfer.confirmCancel(); this.currentOutTransfer = null; } this.packetConnection.write(); } /** Called when progress information about a transfer is received. The affected transfer's progress is set according to the information received. */ private void handleProgressInformation(ProgressInformationPacket progressInformation) { if (progressInformation == null) { System.err.println("Received invalid packet."); return; } // TODO: current out transfer might be null for (ProgressInformationPacket.TransferProgressInformation information : progressInformation.progressInformation) { if (information.transferIdentifier.equals(this.currentOutTransfer.getIdentifier())) { this.currentOutTransfer.setProgress(information.progress); this.currentOutTransfer.setInterrupted(false); } else { throw new Error("Received progress information for unrecognized transfer."); } } if (this.currentOutTransfer != null && this.currentOutTransfer.getIsInterrupted()) { this.currentOutTransfer.setProgress(0); this.currentOutTransfer.setInterrupted(false); this.packetConnection.writePacket(new StartedTransferPacket(this.currentOutTransfer.getIdentifier(), this.currentOutTransfer.getLength())); } this.isInterrupted = false; this.packetConnection.write(); } /** Called when a transfer is started. */ private void handleStartedTransfer(StartedTransferPacket startedTransfer) { if (startedTransfer == null) { System.err.println("Received invalid packet."); return; } if (this.currentInTransfer != null) throw new Error("Attempted to start in transfer, but there is still an in transfer active."); this.currentInTransfer = new InTransfer(this, startedTransfer.transferLength, startedTransfer.transferIdentifier); this.handler.notifyTransferStarted(this.currentInTransfer); this.currentInTransfer.confirmStart(); } /** Handles a cancelled transfer packet. */ private void handleCancelledTransfer(CancelledTransferPacket cancelledTransferPacket) { if (cancelledTransferPacket == null) { System.err.println("Received invalid packet."); return; } if (this.currentOutTransfer != null && cancelledTransferPacket.transferIdentifier.equals(this.currentOutTransfer.getIdentifier())) { this.cancelTransfer(this.currentOutTransfer); } else if (this.currentInTransfer != null && cancelledTransferPacket.transferIdentifier.equals(this.currentInTransfer.getIdentifier())) { this.currentInTransfer.confirmCancel(); this.currentInTransfer = null; } else { System.out.println("Received cancel request for an unknown transfer. The transfer was probably finnished before the cancel request was received."); } } /** Handles a data packet. */ private void handleData(DataPacket dataPacket) { if (this.currentInTransfer == null) throw new Error("Received data, but there is no active in transfer."); this.currentInTransfer.updateWithReceivedData(dataPacket.data); if (this.currentInTransfer.getIsCompleted()) this.currentInTransfer = null; } @Override public Set<PacketType> getHandledPacketTypes() { Set<PacketType> types = new HashSet<>(); types.add(PacketType.PROGRESS_INFORMATION); types.add(PacketType.TRANSFER_STARTED); types.add(PacketType.CANCELLED_TRANSFER); types.add(PacketType.DATA_PACKET); return types; } @Override public void handlePacket(ByteBuffer packet, PacketType type) { switch (type) { case PROGRESS_INFORMATION: handleProgressInformation(ProgressInformationPacket.deserialize(packet)); break; case TRANSFER_STARTED: handleStartedTransfer(StartedTransferPacket.deserialize(packet)); break; case CANCELLED_TRANSFER: handleCancelledTransfer(CancelledTransferPacket.deserialize(packet)); break; case DATA_PACKET: handleData(DataPacket.deserialize(packet)); break; default: throw new IllegalArgumentException("Invalid type: "+type); } } @Override public void onUnderlyingConnectionClose(PacketConnection connection) {} @Override public void onWillSwapUnderlyingConnection(PacketConnection connection) { if (this.isInterrupted) return; this.isInterrupted = true; if (this.currentInTransfer != null) this.currentInTransfer.setInterrupted(true); if (this.currentOutTransfer != null) this.currentOutTransfer.setInterrupted(true); } @Override public void onUnderlyingConnectionConnected(PacketConnection connection) { if (!this.isInterrupted) return; List<TransferProgressInformation> progressInformation = new ArrayList<TransferProgressInformation>(); if (this.currentInTransfer != null) { progressInformation.add(new TransferProgressInformation(this.currentInTransfer.getIdentifier(), this.currentInTransfer.getProgress())); this.currentInTransfer.setInterrupted(false); } this.packetConnection.writePacket(new ProgressInformationPacket(progressInformation)); this.packetConnection.write(); } @Override public void onNoPacketsLeft(PacketConnection connection) { if (this.isInterrupted) return; int packetLength = 1024; if (this.packetConnection.getUnderlyingConnection() != null) { packetLength = this.packetConnection.getUnderlyingConnection().getRecommendedPacketSize(); } if (currentOutTransfer != null) { this.packetConnection.writePacket(currentOutTransfer.nextPacket(packetLength)); if (this.currentOutTransfer.getIsCompleted()) { this.currentOutTransfer = null; } } else { this.currentOutTransfer = this.outTransferQueue.poll(); if (this.currentOutTransfer != null) { this.currentOutTransfer.confirmStart(); this.packetConnection.writePacket(new StartedTransferPacket(this.currentOutTransfer.getIdentifier(), this.currentOutTransfer.getLength())); } } } public TransferProcessorHandler getHandler() { return this.handler; } public void setHandler(TransferProcessorHandler handler) { this.handler = handler; } }