Files
LuaCsForBarotraumaEP/Subsurface/Source/Networking/FileTransfer/FileSender.cs
Regalis e406b76cd5 Some more logic to handle missing sub files and active file transfers when starting a round:
- server waits for transfers to finish before starting the round (up to a max 20 seconds, can be skipped by the host)
- clients enable the spectate button when the round starts (in case they fail to start the round due to a missing sub file or an error)
- clients notify the server if a transfer is cancelled

+ FileReceivers can't be instantiated if a server is running
2017-03-09 19:56:27 +02:00

266 lines
8.3 KiB
C#

using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
enum FileTransferStatus
{
NotStarted, Sending, Receiving, Finished, Canceled, Error
}
enum FileTransferMessageType
{
Unknown, Initiate, Data, Cancel
}
enum FileTransferType
{
Submarine
}
class FileSender
{
public class FileTransferOut
{
private byte[] data;
private DateTime startingTime;
private NetConnection connection;
public FileTransferStatus Status;
public string FileName
{
get;
private set;
}
public string FilePath
{
get;
private set;
}
public FileTransferType FileType
{
get;
private set;
}
public float Progress
{
get { return SentOffset / (float)Data.Length; }
}
public float WaitTimer
{
get;
set;
}
public byte[] Data
{
get { return data; }
}
public int SentOffset
{
get;
set;
}
public NetConnection Connection
{
get { return connection; }
}
public int SequenceChannel;
public FileTransferOut(NetConnection recipient, FileTransferType fileType, string filePath)
{
connection = recipient;
FileType = fileType;
FilePath = filePath;
FileName = Path.GetFileName(filePath);
Status = FileTransferStatus.NotStarted;
startingTime = DateTime.Now;
data = File.ReadAllBytes(filePath);
}
}
const int MaxTransferCount = 16;
const int MaxTransferCountPerRecipient = 5;
public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0);
private List<FileTransferOut> activeTransfers;
private int chunkLen;
private NetPeer peer;
public List<FileTransferOut> ActiveTransfers
{
get { return activeTransfers; }
}
public FileSender(NetworkMember networkMember)
{
peer = networkMember.netPeer;
chunkLen = peer.Configuration.MaximumTransmissionUnit - 100;
activeTransfers = new List<FileTransferOut>();
}
public FileTransferOut StartTransfer(NetConnection recipient, FileTransferType fileType, string filePath)
{
if (activeTransfers.Count >= MaxTransferCount)
{
return null;
}
if (activeTransfers.Count(t => t.Connection == recipient) > MaxTransferCountPerRecipient)
{
return null;
}
if (!File.Exists(filePath))
{
DebugConsole.ThrowError("Failed to initiate file transfer (file \""+filePath+"\" not found.");
return null;
}
FileTransferOut transfer = null;
try
{
transfer = new FileTransferOut(recipient, fileType, filePath);
transfer.SequenceChannel = 1;
while (activeTransfers.Any(t => t.Connection == recipient && t.SequenceChannel == transfer.SequenceChannel))
{
transfer.SequenceChannel++;
}
activeTransfers.Add(transfer);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to initiate file transfer", e);
}
return transfer;
}
public void Update(float deltaTime)
{
activeTransfers.RemoveAll(t => t.Connection.Status != NetConnectionStatus.Connected);
foreach (FileTransferOut transfer in activeTransfers)
{
transfer.WaitTimer -= deltaTime;
if (transfer.WaitTimer > 0.0f) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue;
transfer.WaitTimer = transfer.Connection.AverageRoundtripTime;
// send another part of the file
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
NetOutgoingMessage message;
//first message; send length, chunk length, file name etc
if (transfer.SentOffset == 0)
{
message = peer.CreateMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.FileType);
message.Write((ushort)chunkLen);
message.Write((ulong)transfer.Data.Length);
message.Write(transfer.FileName);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
DebugConsole.Log(" Size: " + transfer.Data.Length);
DebugConsole.Log(" Sequence channel: " + transfer.SequenceChannel);
}
}
message = peer.CreateMessage(1 + 1 + sendByteCount);
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write(sendBytes);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.SentOffset += sendByteCount;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
}
if (remaining - sendByteCount <= 0)
{
transfer.Status = FileTransferStatus.Finished;
}
}
activeTransfers.RemoveAll(t => t.Status == FileTransferStatus.Finished);
}
public void CancelTransfer(FileTransferOut transfer)
{
transfer.Status = FileTransferStatus.Canceled;
activeTransfers.Remove(transfer);
}
public void ReadFileRequest(NetIncomingMessage inc)
{
byte messageType = inc.ReadByte();
if (messageType == (byte)FileTransferMessageType.Cancel)
{
byte sequenceChannel = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
return;
}
byte fileType = inc.ReadByte();
switch (fileType)
{
case (byte)FileTransferType.Submarine:
string fileName = inc.ReadString();
var requestedSubmarine = Submarine.SavedSubmarines.Find(s => s.Name == fileName);
if (requestedSubmarine != null)
{
StartTransfer(inc.SenderConnection, FileTransferType.Submarine, requestedSubmarine.FilePath);
}
break;
}
}
}
}