From c2b016d7d0f056ae90ac02037885eca722bf9c3a Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 13 Jun 2016 18:28:56 +0200 Subject: [PATCH] Gets local package installation 'working', now needs a little UI work. --- .../views/install-local.controller.js | 130 +++++++++++++++-- .../views/packager/views/install-local.html | 56 +++++-- .../src/views/packager/views/repo.html | 8 +- src/Umbraco.Web/Editors/MediaController.cs | 20 +-- .../Editors/PackageInstallController.cs | 138 +++++++++++++++++- .../Models/ContentEditing/PostedFiles.cs | 23 +++ .../Models/LocalPackageInstallModel.cs | 75 ++++++++++ src/Umbraco.Web/Models/PackageInstallModel.cs | 5 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 + 9 files changed, 405 insertions(+), 52 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs create mode 100644 src/Umbraco.Web/Models/LocalPackageInstallModel.cs diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index 39e4f47236..2777303aa0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -1,29 +1,127 @@ (function () { "use strict"; - function PackagesInstallLocalController($scope, $route, $location) { + function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, $cookieStore) { var vm = this; vm.state = "upload"; - vm.localPackage = { - "icon":"https://our.umbraco.org/media/wiki/154472/635997115126742822_logopng.png?bgcolor=fff&height=154&width=281&format=png", - "name": "SvgIconPicker Version: 0.1.0", - "author": "Søren Kottal", - "authorLink": "https://github.com/skttl/", - "info": "https://github.com/skttl/Umbraco.SvgIconPicker", - "licens": "GPLv3", - "licensLink": "http://www.gnu.org/licenses/quick-guide-gplv3.en.html", - "licensAccept": false, - "readme": "Color Palettes is a simple property editor that let you define different color palettes (or get them from Adobe Kuler or COLOURlovers) and present them to the editor as a list of radio buttons.", - "filePath": "", - "riskAccept": false + vm.localPackage = {}; + vm.loadPackage = loadPackage; + vm.installPackage = installPackage; + vm.installState = { + status: "" + }; + vm.zipFile = { + uploadStatus: "idle", + uploadProgress: 0, + serverErrorMessage: null }; - vm.loadPackage = loadPackage; + $scope.handleFiles = function (files, event) { + for (var i = 0; i < files.length; i++) { + upload(files[i]); + } + }; - function loadPackage(){ - vm.state = "packageDetails"; + function upload(file) { + + Upload.upload({ + url: umbRequestHelper.getApiUrl("packageInstallApiBaseUrl", "UploadLocalPackage"), + fields: {}, + file: file + }).progress(function (evt) { + + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + + // set percentage property on file + vm.zipFile.uploadProgress = progressPercentage; + + // set uploading status on file + vm.zipFile.uploadStatus = "uploading"; + + }).success(function (data, status, headers, config) { + + if (data.notifications && data.notifications.length > 0) { + + // set error status on file + vm.zipFile.uploadStatus = "error"; + + // Throw message back to user with the cause of the error + vm.zipFile.serverErrorMessage = data.notifications[0].message; + + //TODO: Handle the error in UI + + } else { + + // set done status on file + vm.zipFile.uploadStatus = "done"; + + vm.localPackage = data; + } + + }).error(function (evt, status, headers, config) { + + //TODO: Handle the error in UI + + // set status done + vm.zipFile.uploadStatus = "error"; + + //if the service returns a detailed error + if (evt.InnerException) { + vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + vm.zipFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.zipFile.serverErrorMessage = "File not found"; + } + + }); + } + + function loadPackage() { + if (vm.zipFile.uploadStatus === "done") { + vm.state = "packageDetails"; + } + } + + function installPackage() { + vm.installState.status = "Installing"; + + //TODO: If any of these fail, will they keep calling the next one? + packageResource + .installFiles(vm.localPackage) + .then(function(pack) { + vm.installState.status = "Restarting, please hold..."; + return packageResource.installData(pack); + }, + installError) + .then(function(pack) { + vm.installState.status = "All done, your browser will now refresh"; + return packageResource.cleanUp(pack); + }, + installError) + .then(installComplete, installError); + } + + function installComplete() { + var url = window.location.href + "?installed=" + vm.localPackage.packageGuid; + $cookieStore.put("umbPackageInstallId", vm.localPackage.packageGuid); + window.location.reload(true); + } + + function installError() { + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html index 3b3c247aa2..c1ff80bad6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -6,10 +6,43 @@
- - Drop to upload -
+ + + +
+ +
+ + + + Drop to upload + + +
+ - or click here to choose files +
+
+
+ + +

Upload package

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam @@ -18,6 +51,8 @@ @@ -49,7 +84,7 @@

License - {{ vm.localPackage.licens }} + {{ vm.localPackage.license }}
@@ -58,18 +93,21 @@ {{ vm.localPackage.readme }}
-
+
-
+
+

{{vm.installState.status}}

diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html index 506ee3efca..7a0abd6dc5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -12,7 +12,7 @@
-
+
{{ package.name }}
-
{{ package.description | limitTo: 40 }}...
+
{{ package.excerpt | limitTo: 40 }}...
@@ -68,7 +68,7 @@
{{ package.name }}
-
{{ package.description | limitTo: 40 }}...
+
{{ package.excerpt | limitTo: 40 }}...
@@ -86,7 +86,7 @@
-
+
- /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the - /// temporary files that were created. - /// - [DataContract] - private class PostedFiles : IHaveUploadedFiles, INotificationModel - { - public PostedFiles() - { - UploadedFiles = new List(); - Notifications = new List(); - } - public List UploadedFiles { get; private set; } - - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } - + /// /// Ensures the item can be moved/copied to the new location /// diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 0b1c1a56dd..96068d859d 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -13,11 +14,16 @@ using System.Xml.Linq; using Umbraco.Core.Auditing; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.packager.repositories; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; using Umbraco.Web.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.UI; +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors @@ -26,7 +32,117 @@ namespace Umbraco.Web.Editors [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] public class PackageInstallController : UmbracoAuthorizedJsonController { + [HttpPost] + [FileUploadCleanupFilter(false)] + public async Task UploadLocalPackage() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + //TODO: App/Tree Permissions? + var model = new LocalPackageInstallModel + { + PackageGuid = Guid.NewGuid() + }; + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + + //TODO: Only allow .zip + if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb")) + { + //TODO: Currently it has to be here, it's not ideal but that's the way it is right now + var packageTempDir = IOHelper.MapPath(SystemDirectories.Data); + + //ensure it's there + Directory.CreateDirectory(packageTempDir); + + //copy it - must always be '.umb' for the installer thing to work + //the file name must be a GUID - this is what the packager expects (strange yes) + //because essentially this is creating a temporary package Id that will be used + //for unpacking/installing/etc... + var packageTempFileName = model.PackageGuid + ".umb"; + var packageTempFileLocation = Path.Combine(packageTempDir, packageTempFileName); + File.Copy(file.LocalFileName, packageTempFileLocation, true); + + try + { + var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); + //this will load in all the metadata too + var tempDir = ins.Import(packageTempFileName); + model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempDir); + model.Id = ins.CreateManifest( + IOHelper.MapPath(model.TemporaryDirectoryPath), + model.PackageGuid.ToString(), + //TODO: Does this matter? we're installing a local package + string.Empty); + + model.Name = ins.Name; + model.Author = ins.Author; + model.AuthorUrl = ins.AuthorUrl; + model.License = ins.License; + model.LicenseUrl = ins.LicenseUrl; + model.ReadMe = ins.ReadMe; + model.ConflictingMacroAliases = ins.ConflictingMacroAliases; + model.ConflictingStyleSheetNames = ins.ConflictingStyleSheetNames; + model.ConflictingTemplateAliases = ins.ConflictingTemplateAliases; + model.ContainsBinaryFileErrors = ins.ContainsBinaryFileErrors; + model.ContainsLegacyPropertyEditors = ins.ContainsLegacyPropertyEditors; + model.ContainsMacroConflict = ins.ContainsMacroConflict; + model.ContainsStyleSheetConflicts = ins.ContainsStyleSheeConflicts; + model.ContainsTemplateConflicts = ins.ContainsTemplateConflicts; + model.ContainsUnsecureFiles = ins.ContainsUnsecureFiles; + + model.Url = ins.Url; + model.Version = ins.Version; + //TODO: We need to add the 'strict' requirement to the installer + } + finally + { + //Cleanup file + if (File.Exists(packageTempFileLocation)) + { + File.Delete(packageTempFileLocation); + } + } + } + else + { + model.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("media/disallowedFileType"), + SpeechBubbleIcon.Warning)); + } + + } + + return model; + + } + + /// + /// Gets the package from Our to install + /// + /// + /// [HttpGet] public PackageInstallModel Fetch(string packageGuid) { @@ -50,6 +166,11 @@ namespace Umbraco.Web.Editors return p; } + /// + /// Extracts the package zip and gets the packages information + /// + /// + /// [HttpPost] public PackageInstallModel Import(PackageInstallModel model) { @@ -60,6 +181,11 @@ namespace Umbraco.Web.Editors return model; } + /// + /// Installs the package files + /// + /// + /// [HttpPost] public PackageInstallModel InstallFiles(PackageInstallModel model) { @@ -69,7 +195,11 @@ namespace Umbraco.Web.Editors return model; } - + /// + /// Installs the packages data/business logic + /// + /// + /// [HttpPost] public PackageInstallModel InstallData(PackageInstallModel model) { @@ -79,7 +209,11 @@ namespace Umbraco.Web.Editors return model; } - + /// + /// Cleans up the package installation + /// + /// + /// [HttpPost] public PackageInstallModel CleanUp(PackageInstallModel model) { diff --git a/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs new file mode 100644 index 0000000000..7793787670 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the + /// temporary files that were created. + /// + [DataContract] + internal class PostedFiles : IHaveUploadedFiles, INotificationModel + { + public PostedFiles() + { + UploadedFiles = new List(); + Notifications = new List(); + } + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/LocalPackageInstallModel.cs b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs new file mode 100644 index 0000000000..4f076675f4 --- /dev/null +++ b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models +{ + /// + /// A model that represents uploading a local package + /// + [DataContract(Name = "localPackageInstallModel")] + public class LocalPackageInstallModel : PackageInstallModel, IHaveUploadedFiles, INotificationModel + { + public LocalPackageInstallModel() + { + UploadedFiles = new List(); + Notifications = new List(); + } + + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + + [DataMember(Name = "version")] + public string Version { get; set; } + + [DataMember(Name = "containsUnsecureFiles")] + public bool ContainsUnsecureFiles { get; set; } + + [DataMember(Name = "containsTemplateConflicts")] + public bool ContainsTemplateConflicts { get; set; } + + [DataMember(Name = "containsStyleSheetConflicts")] + public bool ContainsStyleSheetConflicts { get; set; } + + [DataMember(Name = "containsMacroConflict")] + public bool ContainsMacroConflict { get; set; } + + [DataMember(Name = "containsLegacyPropertyEditors")] + public bool ContainsLegacyPropertyEditors { get; set; } + + [DataMember(Name = "containsBinaryFileErrors")] + public bool ContainsBinaryFileErrors { get; set; } + + [DataMember(Name = "conflictingTemplateAliases")] + public IDictionary ConflictingTemplateAliases { get; set; } + + [DataMember(Name = "conflictingStyleSheetNames")] + public IDictionary ConflictingStyleSheetNames { get; set; } + + [DataMember(Name = "conflictingMacroAliases")] + public IDictionary ConflictingMacroAliases { get; set; } + + [DataMember(Name = "readMe")] + public string ReadMe { get; set; } + + [DataMember(Name = "licenseUrl")] + public string LicenseUrl { get; set; } + + [DataMember(Name = "license")] + public string License { get; set; } + + [DataMember(Name = "authorUrl")] + public string AuthorUrl { get; set; } + + [DataMember(Name = "author")] + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/PackageInstallModel.cs b/src/Umbraco.Web/Models/PackageInstallModel.cs index 34b28843b6..f903fc1880 100644 --- a/src/Umbraco.Web/Models/PackageInstallModel.cs +++ b/src/Umbraco.Web/Models/PackageInstallModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; @@ -10,7 +9,7 @@ namespace Umbraco.Web.Models [DataContract(Name = "packageInstallModel")] public class PackageInstallModel { - [DataMember(Name="id")] + [DataMember(Name = "id")] public int Id { get; set; } [DataMember(Name = "packageGuid")] @@ -24,5 +23,7 @@ namespace Umbraco.Web.Models [DataMember(Name = "zipFilePath")] public string ZipFilePath { get; set; } + + } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f0e4b82aa3..285c076baa 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -336,9 +336,11 @@ + +