diff --git a/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs b/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs new file mode 100644 index 0000000000..31bf2e34f5 --- /dev/null +++ b/Intersect.Editor/Forms/FrmUploadToServer.Designer.cs @@ -0,0 +1,229 @@ +using System.ComponentModel; +using DarkUI.Controls; + +namespace Intersect.Editor.Forms +{ + partial class FrmUploadToServer + { + /// + /// Required designer variable. + /// + private 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.lblServerUrl = new DarkUI.Controls.DarkLabel(); + this.txtServerUrl = new DarkUI.Controls.DarkTextBox(); + this.lblUploadType = new DarkUI.Controls.DarkLabel(); + this.rbClientAssets = new DarkUI.Controls.DarkRadioButton(); + this.rbEditorAssets = new DarkUI.Controls.DarkRadioButton(); + this.lblDirectory = new DarkUI.Controls.DarkLabel(); + this.txtDirectory = new DarkUI.Controls.DarkTextBox(); + this.btnBrowse = new DarkUI.Controls.DarkButton(); + this.btnUpload = new DarkUI.Controls.DarkButton(); + this.btnClose = new DarkUI.Controls.DarkButton(); + this.progressBar = new DarkUI.Controls.DarkProgressBar(); + this.lblStatus = new DarkUI.Controls.DarkLabel(); + this.grpUploadType = new DarkUI.Controls.DarkGroupBox(); + this.grpUploadType.SuspendLayout(); + this.SuspendLayout(); + // + // lblServerUrl + // + this.lblServerUrl.AutoSize = true; + this.lblServerUrl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblServerUrl.Location = new System.Drawing.Point(12, 15); + this.lblServerUrl.Name = "lblServerUrl"; + this.lblServerUrl.Size = new System.Drawing.Size(63, 13); + this.lblServerUrl.TabIndex = 0; + this.lblServerUrl.Text = "Server URL:"; + // + // txtServerUrl + // + this.txtServerUrl.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(69)))), ((int)(((byte)(73)))), ((int)(((byte)(74))))); + this.txtServerUrl.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.txtServerUrl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.txtServerUrl.Location = new System.Drawing.Point(15, 31); + this.txtServerUrl.Name = "txtServerUrl"; + this.txtServerUrl.Size = new System.Drawing.Size(457, 20); + this.txtServerUrl.TabIndex = 1; + // + // lblUploadType + // + this.lblUploadType.AutoSize = true; + this.lblUploadType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblUploadType.Location = new System.Drawing.Point(12, 64); + this.lblUploadType.Name = "lblUploadType"; + this.lblUploadType.Size = new System.Drawing.Size(71, 13); + this.lblUploadType.TabIndex = 2; + this.lblUploadType.Text = "Upload Type:"; + // + // grpUploadType + // + this.grpUploadType.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(51)))), ((int)(((byte)(51)))), ((int)(((byte)(51))))); + this.grpUploadType.Controls.Add(this.rbClientAssets); + this.grpUploadType.Controls.Add(this.rbEditorAssets); + this.grpUploadType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.grpUploadType.Location = new System.Drawing.Point(15, 80); + this.grpUploadType.Name = "grpUploadType"; + this.grpUploadType.Size = new System.Drawing.Size(457, 60); + this.grpUploadType.TabIndex = 3; + this.grpUploadType.TabStop = false; + // + // rbClientAssets + // + this.rbClientAssets.AutoSize = true; + this.rbClientAssets.Checked = true; + this.rbClientAssets.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.rbClientAssets.Location = new System.Drawing.Point(15, 20); + this.rbClientAssets.Name = "rbClientAssets"; + this.rbClientAssets.Size = new System.Drawing.Size(86, 17); + this.rbClientAssets.TabIndex = 0; + this.rbClientAssets.TabStop = true; + this.rbClientAssets.Text = "Client Assets"; + this.rbClientAssets.UseVisualStyleBackColor = true; + // + // rbEditorAssets + // + this.rbEditorAssets.AutoSize = true; + this.rbEditorAssets.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.rbEditorAssets.Location = new System.Drawing.Point(150, 20); + this.rbEditorAssets.Name = "rbEditorAssets"; + this.rbEditorAssets.Size = new System.Drawing.Size(86, 17); + this.rbEditorAssets.TabIndex = 1; + this.rbEditorAssets.Text = "Editor Assets"; + this.rbEditorAssets.UseVisualStyleBackColor = true; + // + // lblDirectory + // + this.lblDirectory.AutoSize = true; + this.lblDirectory.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblDirectory.Location = new System.Drawing.Point(12, 153); + this.lblDirectory.Name = "lblDirectory"; + this.lblDirectory.Size = new System.Drawing.Size(92, 13); + this.lblDirectory.TabIndex = 4; + this.lblDirectory.Text = "Source Directory:"; + // + // txtDirectory + // + this.txtDirectory.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(69)))), ((int)(((byte)(73)))), ((int)(((byte)(74))))); + this.txtDirectory.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.txtDirectory.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.txtDirectory.Location = new System.Drawing.Point(15, 169); + this.txtDirectory.Name = "txtDirectory"; + this.txtDirectory.ReadOnly = true; + this.txtDirectory.Size = new System.Drawing.Size(357, 20); + this.txtDirectory.TabIndex = 5; + // + // btnBrowse + // + this.btnBrowse.Location = new System.Drawing.Point(378, 167); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Padding = new System.Windows.Forms.Padding(5); + this.btnBrowse.Size = new System.Drawing.Size(94, 23); + this.btnBrowse.TabIndex = 6; + this.btnBrowse.Text = "Browse..."; + this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click); + // + // progressBar + // + this.progressBar.Location = new System.Drawing.Point(15, 205); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(457, 23); + this.progressBar.TabIndex = 7; + // + // lblStatus + // + this.lblStatus.AutoSize = true; + this.lblStatus.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblStatus.Location = new System.Drawing.Point(12, 237); + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(0, 13); + this.lblStatus.TabIndex = 8; + // + // btnUpload + // + this.btnUpload.Enabled = false; + this.btnUpload.Location = new System.Drawing.Point(297, 260); + this.btnUpload.Name = "btnUpload"; + this.btnUpload.Padding = new System.Windows.Forms.Padding(5); + this.btnUpload.Size = new System.Drawing.Size(85, 28); + this.btnUpload.TabIndex = 9; + this.btnUpload.Text = "Upload"; + this.btnUpload.Click += new System.EventHandler(this.btnUpload_Click); + // + // btnClose + // + this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnClose.Location = new System.Drawing.Point(388, 260); + this.btnClose.Name = "btnClose"; + this.btnClose.Padding = new System.Windows.Forms.Padding(5); + this.btnClose.Size = new System.Drawing.Size(84, 28); + this.btnClose.TabIndex = 10; + this.btnClose.Text = "Close"; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // FrmUploadToServer + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(484, 300); + this.Controls.Add(this.btnClose); + this.Controls.Add(this.btnUpload); + this.Controls.Add(this.lblStatus); + this.Controls.Add(this.progressBar); + this.Controls.Add(this.btnBrowse); + this.Controls.Add(this.txtDirectory); + this.Controls.Add(this.lblDirectory); + this.Controls.Add(this.grpUploadType); + this.Controls.Add(this.lblUploadType); + this.Controls.Add(this.txtServerUrl); + this.Controls.Add(this.lblServerUrl); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FrmUploadToServer"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Upload to Server"; + this.grpUploadType.ResumeLayout(false); + this.grpUploadType.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + } + + #endregion + + private DarkLabel lblServerUrl; + private DarkTextBox txtServerUrl; + private DarkLabel lblUploadType; + private DarkRadioButton rbClientAssets; + private DarkRadioButton rbEditorAssets; + private DarkLabel lblDirectory; + private DarkTextBox txtDirectory; + private DarkButton btnBrowse; + private DarkButton btnUpload; + private DarkButton btnClose; + private DarkProgressBar progressBar; + private DarkLabel lblStatus; + private DarkGroupBox grpUploadType; + } +} diff --git a/Intersect.Editor/Forms/FrmUploadToServer.cs b/Intersect.Editor/Forms/FrmUploadToServer.cs new file mode 100644 index 0000000000..1267b37f52 --- /dev/null +++ b/Intersect.Editor/Forms/FrmUploadToServer.cs @@ -0,0 +1,227 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Windows.Forms; +using DarkUI.Forms; +using Intersect.Configuration; +using Intersect.Editor.General; +using Intersect.Editor.Localization; +using Intersect.Web; +using Newtonsoft.Json; + +namespace Intersect.Editor.Forms; + +public partial class FrmUploadToServer : DarkDialog +{ + private string? _selectedDirectory; + private TokenResponse? _tokenResponse; + + public FrmUploadToServer() + { + InitializeComponent(); + Icon = Program.Icon; + LoadSettings(); + } + + private void LoadSettings() + { + // Load saved settings + var savedUrl = Preferences.LoadPreference("upload_serverUrl"); + if (!string.IsNullOrWhiteSpace(savedUrl)) + { + txtServerUrl.Text = savedUrl; + } + else if (ClientConfiguration.Instance.UpdateUrl is { } updateUrl && !string.IsNullOrWhiteSpace(updateUrl)) + { + txtServerUrl.Text = updateUrl; + } + + var savedType = Preferences.LoadPreference("upload_type"); + if (savedType == "editor") + { + rbEditorAssets.Checked = true; + } + else + { + rbClientAssets.Checked = true; + } + + // Try to load token from preferences + var rawTokenResponse = Preferences.LoadPreference(nameof(TokenResponse)); + if (!string.IsNullOrWhiteSpace(rawTokenResponse)) + { + try + { + _tokenResponse = JsonConvert.DeserializeObject(rawTokenResponse); + } + catch + { + // Token deserialization failed, will need to authenticate + } + } + } + + private void SaveSettings() + { + Preferences.SavePreference("upload_serverUrl", txtServerUrl.Text); + Preferences.SavePreference("upload_type", rbEditorAssets.Checked ? "editor" : "client"); + if (!string.IsNullOrWhiteSpace(_selectedDirectory)) + { + Preferences.SavePreference("upload_lastDirectory", _selectedDirectory); + } + } + + private void btnBrowse_Click(object sender, EventArgs e) + { + using var folderDialog = new FolderBrowserDialog + { + Description = Strings.UploadToServer.SourceDirectoryPrompt, + ShowNewFolderButton = false + }; + + var lastDir = Preferences.LoadPreference("upload_lastDirectory"); + if (!string.IsNullOrWhiteSpace(lastDir) && Directory.Exists(lastDir)) + { + folderDialog.SelectedPath = lastDir; + } + + if (folderDialog.ShowDialog() == DialogResult.OK) + { + _selectedDirectory = folderDialog.SelectedPath; + txtDirectory.Text = _selectedDirectory; + btnUpload.Enabled = true; + } + } + + private async void btnUpload_Click(object sender, EventArgs e) + { + // Validate inputs + if (string.IsNullOrWhiteSpace(txtServerUrl.Text)) + { + DarkMessageBox.ShowError( + Strings.UploadToServer.InvalidUrl, + Strings.UploadToServer.Title, + DarkUI.Controls.DarkDialogButton.Ok, + Icon + ); + return; + } + + if (string.IsNullOrWhiteSpace(_selectedDirectory) || !Directory.Exists(_selectedDirectory)) + { + DarkMessageBox.ShowError( + Strings.UploadToServer.InvalidDirectory, + Strings.UploadToServer.Title, + DarkUI.Controls.DarkDialogButton.Ok, + Icon + ); + return; + } + + SaveSettings(); + + // Disable controls during upload + btnUpload.Enabled = false; + btnBrowse.Enabled = false; + txtServerUrl.Enabled = false; + rbClientAssets.Enabled = false; + rbEditorAssets.Enabled = false; + progressBar.Value = 0; + lblStatus.Text = Strings.UploadToServer.Uploading.ToString(0); + + try + { + await PerformUpload(); + } + catch (Exception ex) + { + lblStatus.Text = Strings.UploadToServer.Error.ToString(ex.Message); + DarkMessageBox.ShowError( + Strings.UploadToServer.Error.ToString(ex.Message), + Strings.UploadToServer.Failed, + DarkUI.Controls.DarkDialogButton.Ok, + Icon + ); + } + finally + { + // Re-enable controls + btnUpload.Enabled = true; + btnBrowse.Enabled = true; + txtServerUrl.Enabled = true; + rbClientAssets.Enabled = true; + rbEditorAssets.Enabled = true; + } + } + + private async Task PerformUpload() + { + var uploadType = rbEditorAssets.Checked ? "editor" : "client"; + var serverUrl = txtServerUrl.Text.TrimEnd('/'); + var endpoint = $"{serverUrl}/api/v1/editor/updates/{uploadType}"; + + // Get all files in the directory + var files = Directory.GetFiles(_selectedDirectory!, "*.*", SearchOption.AllDirectories); + var totalFiles = files.Length; + var uploadedFiles = 0; + + using var httpClient = new HttpClient(); + httpClient.Timeout = TimeSpan.FromMinutes(30); // Long timeout for large uploads + + // Add authorization header if we have a token + if (_tokenResponse != null) + { + httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _tokenResponse.AccessToken); + } + + // Upload files in batches + const int batchSize = 10; + for (var i = 0; i < files.Length; i += batchSize) + { + var batch = files.Skip(i).Take(batchSize).ToArray(); + + using var content = new MultipartFormDataContent(); + + foreach (var filePath in batch) + { + var relativePath = Path.GetRelativePath(_selectedDirectory!, filePath); + var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(filePath)); + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); + content.Add(fileContent, "files", relativePath.Replace('\\', '/')); + } + + var response = await httpClient.PostAsync(endpoint, content); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception($"Upload failed with status {response.StatusCode}: {errorContent}"); + } + + uploadedFiles += batch.Length; + var progress = (int)((uploadedFiles / (float)totalFiles) * 100); + progressBar.Value = Math.Min(progress, 100); + lblStatus.Text = Strings.UploadToServer.FilesUploaded.ToString(uploadedFiles, totalFiles); + Application.DoEvents(); + } + + progressBar.Value = 100; + lblStatus.Text = Strings.UploadToServer.Success; + + DarkMessageBox.ShowInformation( + Strings.UploadToServer.Success, + Strings.UploadToServer.Completed, + DarkUI.Controls.DarkDialogButton.Ok, + Icon + ); + } + + private void btnClose_Click(object sender, EventArgs e) + { + Close(); + } +} diff --git a/Intersect.Editor/Forms/frmMain.Designer.cs b/Intersect.Editor/Forms/frmMain.Designer.cs index 99e61e602c..536f1a8d64 100644 --- a/Intersect.Editor/Forms/frmMain.Designer.cs +++ b/Intersect.Editor/Forms/frmMain.Designer.cs @@ -123,6 +123,7 @@ private void InitializeComponent() this.menuStrip = new DarkUI.Controls.DarkMenuStrip(); this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.packageUpdateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.uploadToServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.statusStrip1.SuspendLayout(); this.toolStrip1.SuspendLayout(); this.menuStrip.SuspendLayout(); @@ -989,21 +990,30 @@ private void InitializeComponent() this.menuStrip.Text = "menuStrip1"; // // toolsToolStripMenuItem - // + // this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.packageUpdateToolStripMenuItem}); + this.packageUpdateToolStripMenuItem, + this.uploadToServerToolStripMenuItem}); this.toolsToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; this.toolsToolStripMenuItem.Size = new System.Drawing.Size(46, 20); this.toolsToolStripMenuItem.Text = "Tools"; - // + // // packageUpdateToolStripMenuItem - // + // this.packageUpdateToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.packageUpdateToolStripMenuItem.Name = "packageUpdateToolStripMenuItem"; - this.packageUpdateToolStripMenuItem.Size = new System.Drawing.Size(159, 22); + this.packageUpdateToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.packageUpdateToolStripMenuItem.Text = "Package Update"; this.packageUpdateToolStripMenuItem.Click += new System.EventHandler(this.packageUpdateToolStripMenuItem_Click); + // + // uploadToServerToolStripMenuItem + // + this.uploadToServerToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.uploadToServerToolStripMenuItem.Name = "uploadToServerToolStripMenuItem"; + this.uploadToServerToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.uploadToServerToolStripMenuItem.Text = "Upload to Server"; + this.uploadToServerToolStripMenuItem.Click += new System.EventHandler(this.uploadToServerToolStripMenuItem_Click); // // FrmMain // @@ -1121,6 +1131,7 @@ private void InitializeComponent() private ToolStripSeparator toolStripSeparator13; private ToolStripMenuItem craftsEditorToolStripMenuItem; private ToolStripMenuItem packageUpdateToolStripMenuItem; + private ToolStripMenuItem uploadToServerToolStripMenuItem; private ToolStripMenuItem layersToolStripMenuItem; private ToolStripMenuItem hideEventsToolStripMenuItem; } diff --git a/Intersect.Editor/Forms/frmMain.cs b/Intersect.Editor/Forms/frmMain.cs index 3186bcbaab..d9f6b7025d 100644 --- a/Intersect.Editor/Forms/frmMain.cs +++ b/Intersect.Editor/Forms/frmMain.cs @@ -199,6 +199,7 @@ private void InitLocalizationMenuTools() { toolsToolStripMenuItem.Text = Strings.MainForm.tools; packageUpdateToolStripMenuItem.Text = Strings.MainForm.MenuToolsPackageUpdate; + uploadToServerToolStripMenuItem.Text = Strings.MainForm.MenuToolsUploadToServer; } private void InitLocalizationMenuHelp() @@ -2011,6 +2012,12 @@ private string SelectDirectoryWithRetry(string description, string initialPath, private void packageUpdateToolStripMenuItem_Click(object sender, EventArgs e) => PackageUpdate(); + private void uploadToServerToolStripMenuItem_Click(object sender, EventArgs e) + { + var uploadForm = new FrmUploadToServer(); + uploadForm.ShowDialog(this); + } + private void PackageUpdate() { var lastSourceDirectory = Preferences.LoadPreference("update_sourceDirectory"); diff --git a/Intersect.Editor/Localization/Strings.cs b/Intersect.Editor/Localization/Strings.cs index 9321a0d29d..aab58afd41 100644 --- a/Intersect.Editor/Localization/Strings.cs +++ b/Intersect.Editor/Localization/Strings.cs @@ -4191,6 +4191,8 @@ public partial struct MainForm public static LocalizedString MenuToolsPackageUpdate = @"Package Update"; + public static LocalizedString MenuToolsUploadToServer = @"Upload to Server"; + public static LocalizedString toolsdir = @"tools"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] @@ -5720,6 +5722,42 @@ public partial struct UpdatePacking } + public partial struct UploadToServer + { + public static LocalizedString Title = @"Upload to Server"; + + public static LocalizedString SelectDirectory = @"Select Directory to Upload"; + + public static LocalizedString SourceDirectoryPrompt = @"Pick the directory containing files to upload"; + + public static LocalizedString ServerUrl = @"Server URL:"; + + public static LocalizedString UploadType = @"Upload Type:"; + + public static LocalizedString ClientAssets = @"Client Assets"; + + public static LocalizedString EditorAssets = @"Editor Assets"; + + public static LocalizedString Uploading = @"Uploading files... {00}%"; + + public static LocalizedString Success = @"Upload completed successfully!"; + + public static LocalizedString Error = @"Upload failed: {00}"; + + public static LocalizedString InvalidUrl = @"Please enter a valid server URL"; + + public static LocalizedString InvalidDirectory = @"Please select a valid directory"; + + public static LocalizedString Completed = @"Upload Complete"; + + public static LocalizedString Failed = @"Upload Failed"; + + public static LocalizedString FilesUploaded = @"{00} of {01} files uploaded"; + + public static LocalizedString AuthenticationRequired = @"Authentication required. Please log in to the server first."; + + } + public partial struct Warping {