1 using Microsoft.Xna.Framework;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
7 using System.Threading;
100 public readonly Dictionary<int, byte[]>
DataBuffer =
new Dictionary<int, byte[]>();
112 LastOffsetAckTime = DateTime.Now -
new TimeSpan(days: 0, hours: 0, minutes: 5, seconds: 0);
146 float psec = passed / 1000.0f;
151 foreach (
int key
in outdatedKeys)
159 private bool disposed =
false;
163 if (disposed) {
return; }
176 private static int GetMaxFileSizeInBytes(
FileTransferType fileTransferType) =>
177 fileTransferType
switch
180 _ => 50 * 1024 * 1024
187 private readonly List<FileTransferIn> activeTransfers;
188 private readonly List<(
int transferId,
double finishedTime)> finishedTransfers;
190 private readonly ImmutableDictionary<FileTransferType, string> downloadFolders =
new Dictionary<FileTransferType, string>()
195 }.ToImmutableDictionary();
202 activeTransfers =
new List<FileTransferIn>();
203 finishedTransfers =
new List<(int transferId, double finishedTime)>();
208 System.Diagnostics.Debug.Assert(!activeTransfers.Any(t =>
211 t.Status ==
FileTransferStatus.Finished),
"List of active file transfers contains entires that should have been removed");
213 byte transferMessageType = inc.
ReadByte();
215 switch (transferMessageType)
220 var existingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.Endpoint) && t.ID == transferId);
221 finishedTransfers.RemoveAll(t => t.transferId == transferId);
227 if (existingTransfer !=
null)
229 if (fileType != (
byte)existingTransfer.FileType ||
230 fileSize != existingTransfer.FileSize ||
231 fileName != existingTransfer.FileName)
234 DebugConsole.AddWarning(
"File transfer error: file transfer initiated with an ID that's already in use");
243 if (!ValidateInitialData(fileType, fileName, fileSize, out
string errorMsg))
246 DebugConsole.ThrowError(
"File transfer failed (" + errorMsg +
")");
250 if (GameSettings.CurrentConfig.VerboseLogging)
252 DebugConsole.Log(
"Received file transfer initiation message: ");
253 DebugConsole.Log(
" File: " + fileName);
254 DebugConsole.Log(
" Size: " + fileSize);
255 DebugConsole.Log(
" ID: " + transferId);
259 if (!Directory.Exists(downloadFolder))
263 Directory.CreateDirectory(downloadFolder, catchUnauthorizedAccessExceptions:
false);
267 DebugConsole.ThrowError(
"Could not start a file transfer: failed to create the folder \"" + downloadFolder +
"\".", e);
280 for (
int i = 0; i <= maxRetries; i++)
286 catch (System.IO.IOException e)
290 DebugConsole.NewMessage(
"Failed to initiate a file transfer {" + e.Message +
"}, retrying in 250 ms...", Color.Red);
295 DebugConsole.NewMessage(
"Failed to initiate a file transfer {" + e.Message +
"}", Color.Red);
303 activeTransfers.Add(newTransfer);
314 if (GameSettings.CurrentConfig.VerboseLogging)
316 DebugConsole.Log(
"Received file transfer message on the same machine: ");
317 DebugConsole.Log(
" File: " + filePath);
318 DebugConsole.Log(
" ID: " + transferId);
321 if (!File.Exists(filePath))
323 DebugConsole.ThrowError(
"File transfer on the same machine failed, file \"" + filePath +
"\" not found.");
342 var activeTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.Endpoint) && t.ID == transferId);
343 if (activeTransfer ==
null)
348 finishedTransfers.RemoveAll(t => t.finishedTime + 5.0 < Timing.TotalTime);
349 if (!finishedTransfers.Any(t => t.transferId == transferId))
352 DebugConsole.AddWarning(
"File transfer error: received data without a transfer initiation message");
359 if (offset != activeTransfer.Received)
361 activeTransfer.LastSeen = Math.Max(offset, activeTransfer.LastSeen);
364 activeTransfer.DataBuffer.Add(offset, inc.
ReadBytes(bytesToRead));
366 DebugConsole.Log($
"Received {bytesToRead} bytes of the file {activeTransfer.FileName} (ignoring: offset {offset}, waiting for {activeTransfer.Received})");
370 activeTransfer.LastSeen = offset;
372 if (activeTransfer.Received + bytesToRead > activeTransfer.FileSize)
375 DebugConsole.ThrowError(
"File transfer error: Received more data than expected (total received: " + activeTransfer.Received +
379 ", filesize: " + activeTransfer.FileSize);
387 activeTransfer.ReadBytes(inc, bytesToRead);
388 if (GameSettings.CurrentConfig.VerboseLogging)
390 DebugConsole.Log($
"Received {bytesToRead} bytes of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
392 while (activeTransfer.DataBuffer.TryGetValue(activeTransfer.Received, out
byte[] data))
394 activeTransfer.ReadBytes(data);
395 DebugConsole.Log($
"Read {data.Length} bytes of buffer data of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
401 DebugConsole.ThrowError(
"File transfer error: " + e.Message);
410 activeTransfer.Dispose();
412 if (ValidateReceivedData(activeTransfer, out
string errorMessage))
414 finishedTransfers.Add((transferId, Timing.TotalTime));
431 var matchingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.Endpoint) && t.ID == transferId);
432 if (matchingTransfer !=
null)
434 new GUIMessageBox(
"File transfer cancelled",
"The server has cancelled the transfer of the file \"" + matchingTransfer.FileName +
"\".");
442 private bool ValidateInitialData(
byte type,
string fileName,
int fileSize, out
string errorMessage)
448 errorMessage =
"Unknown file type";
454 errorMessage = $
"File too large ({MathUtils.GetBytesReadable(fileSize)} > {MathUtils.GetBytesReadable(GetMaxFileSizeInBytes((FileTransferType)type))})";
458 if (
string.IsNullOrEmpty(fileName) ||
459 fileName.IndexOfAny(Path.GetInvalidFileNameCharsCrossPlatform().ToArray()) > -1)
461 errorMessage =
"Illegal characters in file name ''" + fileName +
"''";
468 if (Path.GetExtension(fileName) !=
".sub")
470 errorMessage =
"Wrong file extension ''" + Path.GetExtension(fileName) +
"''! (Expected .sub)";
475 if (Path.GetExtension(fileName) !=
".save")
477 errorMessage =
"Wrong file extension ''" + Path.GetExtension(fileName) +
"''! (Expected .save)";
486 private bool ValidateReceivedData(FileTransferIn fileTransfer, out
string ErrorMessage)
489 switch (fileTransfer.FileType)
492 System.IO.Stream stream;
495 stream = SaveUtil.DecompressFileToStream(fileTransfer.FilePath);
499 ErrorMessage =
"Loading received submarine \"" + fileTransfer.FileName +
"\" failed! {" + e.Message +
"}";
505 ErrorMessage =
"Decompressing received submarine file \"" + fileTransfer.FilePath +
"\" failed!";
513 XmlReaderSettings settings =
new XmlReaderSettings
515 DtdProcessing = DtdProcessing.Prohibit,
516 IgnoreProcessingInstructions =
true
519 using (var reader = XmlReader.Create(stream, settings))
521 while (reader.Read());
527 ErrorMessage =
"Parsing file \"" + fileTransfer.FilePath +
"\" failed! The file may not be a valid submarine file.";
536 var files = SaveUtil.EnumerateContainedFiles(fileTransfer.FilePath);
537 foreach (var file
in files)
539 string extension = Path.GetExtension(file);
540 if ((!extension.Equals(
".sub", StringComparison.OrdinalIgnoreCase)
541 && !file.Equals(SaveUtil.GameSessionFileName))
542 || file.CleanUpPathCrossPlatform(correctFilenameCase:
false).Contains(
'/'))
544 ErrorMessage = $
"Found unexpected file in \"{fileTransfer.FileName}\"! ({file})";
551 ErrorMessage = $
"Loading received campaign save \"{fileTransfer.FileName}\" failed! {{{e.Message}}}";
568 if (activeTransfers.Contains(transfer)) { activeTransfers.Remove(transfer); }
571 if (deleteFile && File.Exists(transfer.
FilePath))
575 File.Delete(transfer.
FilePath, catchUnauthorizedAccessExceptions:
false);
579 DebugConsole.ThrowError(
"Failed to delete file \"" + transfer.
FilePath +
"\" (" + e.Message +
")");
override void Write(byte[] buffer, int offset, int count)
override void Dispose(bool notCalledByFinalizer)
FileTransferType FileType
FileTransferStatus Status
DateTime LastOffsetAckTime
void ReadBytes(IReadMessage inc, int bytesToRead)
void ReadBytes(byte[] data)
readonly Dictionary< int, byte[]> DataBuffer
Data that we've ignored because we're waiting for some earlier data. Key = byte offset,...
NetworkConnection Connection
void RecordOffsetAckTime()
FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType)
TransferInDelegate OnTransferFailed
delegate void TransferInDelegate(FileTransferIn fileStreamReceiver)
void ReadMessage(IReadMessage inc)
IReadOnlyList< FileTransferIn > ActiveTransfers
void StopTransfer(FileTransferIn transfer, bool deleteFile=false)
TransferInDelegate OnFinished
void CancelFileTransfer(FileReceiver.FileTransferIn transfer)
void UpdateFileTransfer(FileReceiver.FileTransferIn transfer, int expecting, int lastSeen, bool reliable=false)
byte[] ReadBytes(int numberOfBytes)