diff --git a/ARKBreedingStats/ARKBreedingStats.csproj b/ARKBreedingStats/ARKBreedingStats.csproj
index b670c328..1b45828a 100644
--- a/ARKBreedingStats/ARKBreedingStats.csproj
+++ b/ARKBreedingStats/ARKBreedingStats.csproj
@@ -56,6 +56,7 @@
+
@@ -121,6 +122,8 @@
True
strings.resx
+
+
@@ -151,8 +154,20 @@
ATImportFileLocationDialog.cs
+
+ Form
+
+
+ FtpProgress.cs
+
+
+ Form
+
+
+ FtpCredentials.cs
+
UserControl
@@ -591,6 +606,12 @@
customSoundChooser.cs
+
+ FtpProgress.cs
+
+
+ FtpCredentials.cs
+
SpeciesSelector.cs
@@ -756,6 +777,9 @@
+
+ 27.1.2
+
11.0.2
diff --git a/ARKBreedingStats/Form1.cs b/ARKBreedingStats/Form1.cs
index 24b5c034..c2a759cb 100644
--- a/ARKBreedingStats/Form1.cs
+++ b/ARKBreedingStats/Form1.cs
@@ -1,20 +1,26 @@
using ARKBreedingStats.duplicates;
using ARKBreedingStats.Library;
+using ARKBreedingStats.miscClasses;
using ARKBreedingStats.ocr;
using ARKBreedingStats.settings;
using ARKBreedingStats.species;
using ARKBreedingStats.uiControls;
using ARKBreedingStats.values;
+using FluentFTP;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
+using System.IO.Compression;
using System.Linq;
+using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
+using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
@@ -806,9 +812,27 @@ private async void RunSavegameImport(object sender, EventArgs e)
{
workingCopyfilename = Path.GetTempPath();
}
- workingCopyfilename = Path.Combine(workingCopyfilename, Path.GetFileName(atImportFileLocation.FileLocation));
- File.Copy(atImportFileLocation.FileLocation, workingCopyfilename, true);
+
+ if (Uri.TryCreate(atImportFileLocation.FileLocation, UriKind.Absolute, out var uri))
+ {
+ switch(uri.Scheme)
+ {
+ case "ftp":
+ workingCopyfilename = await CopyFtpFileAsync(uri, atImportFileLocation.ConvenientName, workingCopyfilename);
+ if (workingCopyfilename == null)
+ // the user didn't enter credentials
+ return;
+ break;
+ default:
+ throw new Exception($"Unsuppoerted uri scheme: {uri.Scheme}");
+ }
+ }
+ else
+ {
+ workingCopyfilename = Path.Combine(workingCopyfilename, Path.GetFileName(atImportFileLocation.FileLocation));
+ File.Copy(atImportFileLocation.FileLocation, workingCopyfilename, true);
+ }
await ImportSavegame.ImportCollectionFromSavegame(creatureCollection, workingCopyfilename, atImportFileLocation.ServerName);
@@ -845,6 +869,156 @@ private async void RunSavegameImport(object sender, EventArgs e)
}
}
+ private async Task CopyFtpFileAsync(Uri ftpUri, string serverName, string workingCopyFolder)
+ {
+ var credentialsByServerName = LoadSavedCredentials();
+ credentialsByServerName.TryGetValue(serverName, out var credentials);
+
+ var dialogText = $"Ftp Credentials for {serverName}";
+
+ while (true)
+ {
+ if (credentials == null)
+ {
+ // get new credentials
+ using (var dialog = new FtpCredentialsForm { Text = dialogText })
+ {
+ if (dialog.ShowDialog(this) == DialogResult.Cancel)
+ {
+ return null;
+ }
+
+ credentials = dialog.Credentials;
+
+ if (dialog.SaveCredentials)
+ {
+ credentialsByServerName[serverName] = credentials;
+ Properties.Settings.Default.SavedFtpCredentials = Encryption.Protect(JsonConvert.SerializeObject(credentialsByServerName));
+ Properties.Settings.Default.Save();
+ }
+ }
+ }
+
+ var client = new FtpClient(ftpUri.Host, ftpUri.Port, credentials.Username, credentials.Password);
+
+ var cancellationTokenSource = new CancellationTokenSource();
+ using (var progressDialog = new FtpProgressForm(cancellationTokenSource))
+ {
+ try
+ {
+ progressDialog.StatusText = $"Authenticating";
+ progressDialog.Show(this);
+
+ await client.ConnectAsync();
+
+ progressDialog.StatusText = $"Finding most recent file";
+ await Task.Yield();
+
+ var ftpPath = ftpUri.AbsolutePath;
+ var lastSegment = ftpUri.Segments.Last();
+ if (lastSegment.Contains("*"))
+ {
+ var mostRecentlyModifiedMatch = await GetLastModifiedFileAsync(client, ftpUri, cancellationTokenSource.Token);
+ if (mostRecentlyModifiedMatch == null)
+ {
+ throw new Exception($"No file found matching pattern '{lastSegment}'");
+ }
+
+ ftpPath = mostRecentlyModifiedMatch.FullName;
+ }
+
+ var fileName = Path.GetFileName(ftpPath);
+
+ progressDialog.FileName = fileName;
+ progressDialog.StatusText = $"Downloading {fileName}";
+ await Task.Yield();
+
+ var filePath = Path.Combine(workingCopyFolder, Path.GetFileName(ftpPath));
+ await client.DownloadFileAsync(filePath, ftpPath, FtpLocalExists.Overwrite, FtpVerify.Retry, progressDialog, token: cancellationTokenSource.Token);
+ await Task.Delay(500);
+
+ if (filePath.EndsWith(".gz"))
+ {
+ progressDialog.StatusText = $"Decompressing {fileName}";
+ await Task.Yield();
+
+ filePath = await DecompressGZippedFileAsync(filePath, cancellationTokenSource.Token);
+ }
+
+ return filePath;
+ }
+ catch (FtpAuthenticationException ex)
+ {
+ // if auth fails, clear credentials, alert the user and loop until the either auth succeeds or the user cancels
+ progressDialog.StatusText = $"Authentication failed: {ex.Message}";
+ credentials = null;
+ await Task.Delay(1000);
+ }
+ catch(OperationCanceledException)
+ {
+ return null;
+ }
+ catch(Exception ex)
+ {
+ progressDialog.StatusText = $"Unexpected error: {ex.Message}";
+ }
+ }
+ }
+ }
+
+ ///
+ /// Loads the encrypted ftp crednetials from settings, decrypts them, then returns them as a hostname to credentials dictionary
+ ///
+ private static Dictionary LoadSavedCredentials()
+ {
+ try
+ {
+ var savedCredentials = Encryption.Unprotect(Properties.Settings.Default.SavedFtpCredentials);
+
+ if (!string.IsNullOrEmpty(savedCredentials))
+ {
+ var savedDictionary = JsonConvert.DeserializeObject>(savedCredentials);
+
+ // Ensure that the resulting dictionary is case insensitive on hostname
+ return new Dictionary(savedDictionary, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"An error occured while loading saved ftp credentials. Message: \n\n{ex.Message}",
+ "Settings Error", MessageBoxButtons.OK);
+ }
+
+ return new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
+ private async Task DecompressGZippedFileAsync(string filePath, CancellationToken cancellationToken)
+ {
+ var newFileName = filePath.Remove(filePath.Length - 3);
+
+ using (var originalFileStream = File.OpenRead(filePath))
+ using (var decompressedFileStream = File.Create(newFileName))
+ using (var decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress))
+ {
+ await decompressionStream.CopyToAsync(decompressedFileStream, 81920, cancellationToken);
+ }
+
+ return newFileName;
+ }
+
+ public async Task GetLastModifiedFileAsync(FtpClient client, Uri ftpUri, CancellationToken cancellationToken)
+ {
+ var folderUri = new Uri(ftpUri, ".");
+ var listItems = await client.GetListingAsync(folderUri.AbsolutePath, cancellationToken);
+
+ // Turn the wildcard into a regex pattern "super*.foo" -> "^super.*?\.foo$"
+ var nameRegex = new Regex("^" + Regex.Escape(ftpUri.Segments.Last()).Replace(@"\*", ".*?") + "$");
+
+ return listItems
+ .OrderByDescending(x => x.Modified)
+ .FirstOrDefault(x => nameRegex.IsMatch(x.Name));
+ }
+
///
/// Checks each creature for tags and saves them in a list.
///
diff --git a/ARKBreedingStats/ImportSavegame.cs b/ARKBreedingStats/ImportSavegame.cs
index a8252fa4..c39f6316 100644
--- a/ARKBreedingStats/ImportSavegame.cs
+++ b/ARKBreedingStats/ImportSavegame.cs
@@ -116,7 +116,7 @@ private static void importCollection(CreatureCollection creatureCollection, List
if (creatureCollection.changeCreatureStatusOnSavegameImport)
{
// mark creatures that are no longer present as unavailable
- var removedCreatures = creatureCollection.creatures.Where(c => c.status == CreatureStatus.Available).Except(newCreatures);
+ var removedCreatures = creatureCollection.creatures.Where(c => c.status == CreatureStatus.Available && c.server == serverName).Except(newCreatures);
foreach (var c in removedCreatures)
c.status = CreatureStatus.Unavailable;
@@ -130,6 +130,7 @@ private static void importCollection(CreatureCollection creatureCollection, List
{
creature.server = serverName;
});
+
creatureCollection.mergeCreatureList(newCreatures, true);
}
diff --git a/ARKBreedingStats/Properties/Settings.Designer.cs b/ARKBreedingStats/Properties/Settings.Designer.cs
index efd8cad3..96cb8002 100644
--- a/ARKBreedingStats/Properties/Settings.Designer.cs
+++ b/ARKBreedingStats/Properties/Settings.Designer.cs
@@ -676,6 +676,18 @@ public bool IncludeCooldownsInBreedingPlan {
}
}
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string SavedFtpCredentials {
+ get {
+ return ((string)(this["SavedFtpCredentials"]));
+ }
+ set {
+ this["SavedFtpCredentials"] = value;
+ }
+ }
+
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("50")]
@@ -697,6 +709,7 @@ public bool prettifyCollectionJson {
}
set {
this["prettifyCollectionJson"] = value;
+
}
}
}
diff --git a/ARKBreedingStats/Properties/Settings.settings b/ARKBreedingStats/Properties/Settings.settings
index bee06cfa..2acdd81c 100644
--- a/ARKBreedingStats/Properties/Settings.settings
+++ b/ARKBreedingStats/Properties/Settings.settings
@@ -169,6 +169,8 @@
50
+
+
False
diff --git a/ARKBreedingStats/library/CreatureCollection.cs b/ARKBreedingStats/library/CreatureCollection.cs
index 176ceed1..0effa626 100644
--- a/ARKBreedingStats/library/CreatureCollection.cs
+++ b/ARKBreedingStats/library/CreatureCollection.cs
@@ -218,6 +218,12 @@ public bool mergeCreatureList(List creaturesToMerge, bool update = fal
creaturesWereAdded = true;
}
+ if (old.server != creature.server)
+ {
+ old.server = creature.server;
+ creaturesWereAdded = true;
+ }
+
if (!old.levelsWild.SequenceEqual(creature.levelsWild))
{
old.levelsWild = creature.levelsWild;
diff --git a/ARKBreedingStats/miscClasses/Encryption.cs b/ARKBreedingStats/miscClasses/Encryption.cs
new file mode 100644
index 00000000..b57dba85
--- /dev/null
+++ b/ARKBreedingStats/miscClasses/Encryption.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ARKBreedingStats.miscClasses
+{
+ class Encryption
+ {
+ private static byte[] additionalEntropy = Encoding.UTF8.GetBytes("Additional Entropy");
+
+ ///
+ /// Converts a string into a base64 encoded ProdectedData encrypted byte array
+ ///
+ /// The string to encrypt
+ public static string Protect(string value)
+ {
+ try
+ {
+ var decryptedBytes = Encoding.UTF8.GetBytes(value);
+
+ // Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted
+ // only by the same current user.
+ var encryptedBytes = ProtectedData.Protect(decryptedBytes, additionalEntropy, DataProtectionScope.CurrentUser);
+
+ return Convert.ToBase64String(encryptedBytes);
+ }
+ catch (CryptographicException e)
+ {
+ Console.WriteLine("Data was not encrypted. An error occurred.");
+ Console.WriteLine(e.ToString());
+ return null;
+ }
+ }
+
+ ///
+ /// Converts base64 encoded ProdectedData encrypted byte array into a string
+ ///
+ /// The string to decrypt
+ public static string Unprotect(string value)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ var encryptedBytes = Convert.FromBase64String(value);
+
+ //Decrypt the data using DataProtectionScope.CurrentUser.
+ var decryptedBytes = ProtectedData.Unprotect(encryptedBytes, additionalEntropy, DataProtectionScope.CurrentUser);
+
+ return Encoding.UTF8.GetString(decryptedBytes);
+ }
+ catch (CryptographicException e)
+ {
+ Console.WriteLine("Data was not decrypted. An error occurred.");
+ Console.WriteLine(e.ToString());
+ return null;
+ }
+ }
+ }
+}
diff --git a/ARKBreedingStats/miscClasses/FtpCredentials.cs b/ARKBreedingStats/miscClasses/FtpCredentials.cs
new file mode 100644
index 00000000..693c24fe
--- /dev/null
+++ b/ARKBreedingStats/miscClasses/FtpCredentials.cs
@@ -0,0 +1,9 @@
+namespace ARKBreedingStats.miscClasses
+{
+ public class FtpCredentials
+ {
+ public string Username { get; set; }
+
+ public string Password { get; set; }
+ }
+}
diff --git a/ARKBreedingStats/settings/FtpCredentials.Designer.cs b/ARKBreedingStats/settings/FtpCredentials.Designer.cs
new file mode 100644
index 00000000..f86ad7ef
--- /dev/null
+++ b/ARKBreedingStats/settings/FtpCredentials.Designer.cs
@@ -0,0 +1,142 @@
+namespace ARKBreedingStats.settings
+{
+ partial class FtpCredentialsForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.textBox_Username = new System.Windows.Forms.TextBox();
+ this.label_Username = new System.Windows.Forms.Label();
+ this.textBox_Password = new System.Windows.Forms.TextBox();
+ this.label_Password = new System.Windows.Forms.Label();
+ this.button_Ok = new System.Windows.Forms.Button();
+ this.button_Cancel = new System.Windows.Forms.Button();
+ this.checkBox_SaveCredentials = new System.Windows.Forms.CheckBox();
+ this.SuspendLayout();
+ //
+ // textBox_Username
+ //
+ this.textBox_Username.Location = new System.Drawing.Point(12, 25);
+ this.textBox_Username.Name = "textBox_Username";
+ this.textBox_Username.Size = new System.Drawing.Size(203, 20);
+ this.textBox_Username.TabIndex = 3;
+ //
+ // label_Username
+ //
+ this.label_Username.AutoSize = true;
+ this.label_Username.Location = new System.Drawing.Point(12, 9);
+ this.label_Username.Name = "label_Username";
+ this.label_Username.Size = new System.Drawing.Size(55, 13);
+ this.label_Username.TabIndex = 2;
+ this.label_Username.Text = "Username";
+ //
+ // textBox_Password
+ //
+ this.textBox_Password.Location = new System.Drawing.Point(12, 64);
+ this.textBox_Password.Name = "textBox_Password";
+ this.textBox_Password.Size = new System.Drawing.Size(203, 20);
+ this.textBox_Password.TabIndex = 5;
+ this.textBox_Password.UseSystemPasswordChar = true;
+ //
+ // label_Password
+ //
+ this.label_Password.AutoSize = true;
+ this.label_Password.Location = new System.Drawing.Point(12, 48);
+ this.label_Password.Name = "label_Password";
+ this.label_Password.Size = new System.Drawing.Size(53, 13);
+ this.label_Password.TabIndex = 4;
+ this.label_Password.Text = "Password";
+ //
+ // button_Ok
+ //
+ this.button_Ok.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.button_Ok.DialogResult = System.Windows.Forms.DialogResult.OK;
+ this.button_Ok.Location = new System.Drawing.Point(268, 103);
+ this.button_Ok.Name = "button_Ok";
+ this.button_Ok.Size = new System.Drawing.Size(75, 23);
+ this.button_Ok.TabIndex = 10;
+ this.button_Ok.Text = "OK";
+ this.button_Ok.UseVisualStyleBackColor = true;
+ //
+ // button_Cancel
+ //
+ this.button_Cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.button_Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.button_Cancel.Location = new System.Drawing.Point(187, 103);
+ this.button_Cancel.Name = "button_Cancel";
+ this.button_Cancel.Size = new System.Drawing.Size(75, 23);
+ this.button_Cancel.TabIndex = 9;
+ this.button_Cancel.Text = "Cancel";
+ this.button_Cancel.UseVisualStyleBackColor = true;
+ //
+ // checkBox_SaveCredentials
+ //
+ this.checkBox_SaveCredentials.AutoSize = true;
+ this.checkBox_SaveCredentials.Location = new System.Drawing.Point(15, 90);
+ this.checkBox_SaveCredentials.Name = "checkBox_SaveCredentials";
+ this.checkBox_SaveCredentials.Size = new System.Drawing.Size(106, 17);
+ this.checkBox_SaveCredentials.TabIndex = 11;
+ this.checkBox_SaveCredentials.Text = "Save Credentials";
+ this.checkBox_SaveCredentials.UseVisualStyleBackColor = true;
+ //
+ // FtpCredentialsForm
+ //
+ this.AcceptButton = this.button_Ok;
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.CancelButton = this.button_Cancel;
+ this.ClientSize = new System.Drawing.Size(355, 138);
+ this.Controls.Add(this.checkBox_SaveCredentials);
+ this.Controls.Add(this.button_Ok);
+ this.Controls.Add(this.button_Cancel);
+ this.Controls.Add(this.textBox_Password);
+ this.Controls.Add(this.label_Password);
+ this.Controls.Add(this.textBox_Username);
+ this.Controls.Add(this.label_Username);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "FtpCredentialsForm";
+ this.ShowInTaskbar = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "Ftp Credentials";
+ this.TopMost = true;
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.TextBox textBox_Username;
+ private System.Windows.Forms.Label label_Username;
+ private System.Windows.Forms.TextBox textBox_Password;
+ private System.Windows.Forms.Label label_Password;
+ private System.Windows.Forms.Button button_Ok;
+ private System.Windows.Forms.Button button_Cancel;
+ private System.Windows.Forms.CheckBox checkBox_SaveCredentials;
+ }
+}
\ No newline at end of file
diff --git a/ARKBreedingStats/settings/FtpCredentials.cs b/ARKBreedingStats/settings/FtpCredentials.cs
new file mode 100644
index 00000000..4b43b3b1
--- /dev/null
+++ b/ARKBreedingStats/settings/FtpCredentials.cs
@@ -0,0 +1,21 @@
+using ARKBreedingStats.miscClasses;
+using System.Windows.Forms;
+
+namespace ARKBreedingStats.settings
+{
+ public partial class FtpCredentialsForm : Form
+ {
+ public FtpCredentialsForm()
+ {
+ InitializeComponent();
+ }
+
+ public FtpCredentials Credentials => new FtpCredentials
+ {
+ Username = textBox_Username.Text,
+ Password = textBox_Password.Text
+ };
+
+ public bool SaveCredentials => checkBox_SaveCredentials.Checked;
+ }
+}
diff --git a/ARKBreedingStats/settings/FtpCredentials.resx b/ARKBreedingStats/settings/FtpCredentials.resx
new file mode 100644
index 00000000..1af7de15
--- /dev/null
+++ b/ARKBreedingStats/settings/FtpCredentials.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/ARKBreedingStats/settings/FtpProgress.Designer.cs b/ARKBreedingStats/settings/FtpProgress.Designer.cs
new file mode 100644
index 00000000..23c9417f
--- /dev/null
+++ b/ARKBreedingStats/settings/FtpProgress.Designer.cs
@@ -0,0 +1,81 @@
+namespace ARKBreedingStats.settings
+{
+ partial class FtpProgressForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.button_Cancel = new System.Windows.Forms.Button();
+ this.StatusLabel = new System.Windows.Forms.Label();
+ this.SuspendLayout();
+ //
+ // button_Cancel
+ //
+ this.button_Cancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
+ this.button_Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.button_Cancel.Location = new System.Drawing.Point(155, 71);
+ this.button_Cancel.Name = "button_Cancel";
+ this.button_Cancel.Size = new System.Drawing.Size(75, 23);
+ this.button_Cancel.TabIndex = 9;
+ this.button_Cancel.Text = "Cancel";
+ this.button_Cancel.UseVisualStyleBackColor = true;
+ //
+ // StatusLabel
+ //
+ this.StatusLabel.Anchor = System.Windows.Forms.AnchorStyles.Top;
+ this.StatusLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.StatusLabel.Location = new System.Drawing.Point(12, 9);
+ this.StatusLabel.Name = "StatusLabel";
+ this.StatusLabel.Size = new System.Drawing.Size(360, 59);
+ this.StatusLabel.TabIndex = 10;
+ this.StatusLabel.Text = "Status Text";
+ this.StatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ //
+ // FtpProgressForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.CancelButton = this.button_Cancel;
+ this.ClientSize = new System.Drawing.Size(384, 106);
+ this.Controls.Add(this.StatusLabel);
+ this.Controls.Add(this.button_Cancel);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "FtpProgressForm";
+ this.ShowInTaskbar = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "Ftp Progress";
+ this.TopMost = true;
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+ private System.Windows.Forms.Button button_Cancel;
+ private System.Windows.Forms.Label StatusLabel;
+ }
+}
\ No newline at end of file
diff --git a/ARKBreedingStats/settings/FtpProgress.cs b/ARKBreedingStats/settings/FtpProgress.cs
new file mode 100644
index 00000000..3391de8c
--- /dev/null
+++ b/ARKBreedingStats/settings/FtpProgress.cs
@@ -0,0 +1,46 @@
+using ARKBreedingStats.miscClasses;
+using FluentFTP;
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Windows.Forms;
+
+namespace ARKBreedingStats.settings
+{
+ public partial class FtpProgressForm : Form, IProgress
+ {
+ public FtpProgressForm(CancellationTokenSource cancellationTokenSource)
+ {
+ InitializeComponent();
+ FormClosing += (sender, args) => cancellationTokenSource.Cancel();
+ }
+
+ public string StatusText
+ {
+ get
+ {
+ return StatusLabel.Text;
+ }
+ set
+ {
+ StatusLabel.Text = value;
+ }
+ }
+
+ public string FileName { get; set; }
+ private Stopwatch stopwatch = new Stopwatch();
+
+ public void Report(FtpProgress value)
+ {
+ if(value.Progress < 100 && stopwatch.IsRunning && stopwatch.ElapsedMilliseconds < 250)
+ {
+ // only update the progress every 250ms unless setting it to 100%
+ return;
+ }
+
+ var statusText = $"Downloading {FileName}\r\n{value.Progress:F0}% complete\r\n{value.TransferSpeedToString()}";
+ StatusLabel.Invoke(new Action(() => StatusLabel.Text = statusText));
+ stopwatch.Restart();
+ }
+ }
+}
diff --git a/ARKBreedingStats/settings/FtpProgress.resx b/ARKBreedingStats/settings/FtpProgress.resx
new file mode 100644
index 00000000..1af7de15
--- /dev/null
+++ b/ARKBreedingStats/settings/FtpProgress.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file