Gets local package installation 'working', now needs a little UI work.

This commit is contained in:
Shannon
2016-06-13 18:28:56 +02:00
parent f5d3f9087f
commit c2b016d7d0
9 changed files with 405 additions and 52 deletions

View File

@@ -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() {
}
}

View File

@@ -6,10 +6,43 @@
<div class="umb-upload-field">
<i class="icon-box"></i>
<small class="faded"><strong>Drop to upload</strong></small>
</div>
<!-- Drag and drop files area -->
<div ngf-drop
ng-hide="hideDropzone"
ng-model="filesHolder"
ngf-change="handleFiles($files, $event)"
class="dropzone"
ngf-drag-over-class="drag-over"
ngf-multiple="false"
ngf-allow-dir="false"
ngf-pattern="*.zip"
ngf-max-size="{{ maxFileSize }}"
ng-class="{'is-small': compact!=='false' || (done.length+queue.length) > 0 }">
<div class="content">
<!-- Drag and drop illustration -->
<i class="icon-box" draggable="false"></i>
<small class="faded" draggable="false"><strong>Drop to upload</strong></small>
<!-- Select files -->
<div class="file-select"
ngf-select
ng-model="filesHolder"
ngf-change="handleFiles($files, $event)"
ngf-multiple="true"
ngf-pattern="*.zip"
ngf-max-size="{{ maxFileSize }}">
- <localize key="media_orClickHereToUpload">or click here to choose files</localize>
</div>
</div>
</div>
</div>
<h3><strong>Upload package</strong></h3>
<p class="faded">
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 @@
<button type="button"
class="umb-era-button -blue"
ng-disabled="vm.zipFile.uploadStatus !== 'done'"
ng-class="{'-inactive' : vm.zipFile.uploadStatus !== 'done'}"
ng-click="vm.loadPackage()">Next
</button>
</form>
@@ -49,7 +84,7 @@
<div class="umb-info-local-item">
<strong>License</strong>
<a href="{{ vm.localPackage.licensLink }}">{{ vm.localPackage.licens }}</a>
<a href="{{ vm.localPackage.licenseLink }}">{{ vm.localPackage.license }}</a>
</div>
<div class="umb-info-local-item">
@@ -58,18 +93,21 @@
<small> {{ vm.localPackage.readme }} </small>
</div>
<div class="umb-info-local-item mt-3">
<div class="umb-info-local-item mt-3" ng-if="vm.installState.status == ''">
<button type="button"
ng-class="{'-inactive' : localPackageForm.$invalid}"
ng-disabled="localPackageForm.$invalid"
class="umb-era-button -blue inline-flex">
class="umb-era-button -blue inline-flex"
ng-click="vm.installPackage()">
Install package
</button>
<label for="licens-accept" class="umb-package-installer-label">
<input type="checkbox" id="licens-accept" ng-model="vm.localPackage.packageLicensAccept" required>
<label for="license-accept" class="umb-package-installer-label">
<input type="checkbox" id="license-accept" ng-model="vm.localPackage.packageLicenseAccept" required>
<strong class="label-text">I accept <a href="#">terms of use</a></strong>
</label>
</div>
<div class="umb-info-local-item">
<p>{{vm.installState.status}}</p>
</div>
</div>

View File

@@ -12,7 +12,7 @@
</div>
<div ng-show="vm.loading === false">
<div class="umb-packages-section">
<div class="umb-packages-section" ng-if="vm.searchQuery == ''">
<div class="umb-packages-categories">
<a href=""
class="umb-packages-category"
@@ -37,7 +37,7 @@
<div class="umb-package-info">
<div class="umb-package-name">{{ package.name }}</div>
<div class="umb-package-description">{{ package.description | limitTo: 40 }}<span ng-if="package.description > (package.description | limitTo: 40)">...</span></div>
<div class="umb-package-description">{{ package.excerpt | limitTo: 40 }}<span ng-if="package.excerpt > (package.excerpt | limitTo: 40)">...</span></div>
<div class="umb-package-numbers">
<small class="umb-package-downloads">
@@ -68,7 +68,7 @@
<div class="umb-package-info">
<div class="umb-package-name">{{ package.name }}</div>
<div class="umb-package-description">{{ package.description | limitTo: 40 }}<span ng-if="package.description > (package.description | limitTo: 40)">...</span></div>
<div class="umb-package-description">{{ package.excerpt | limitTo: 40 }}<span ng-if="package.excerpt > (package.excerpt | limitTo: 40)">...</span></div>
<div class="umb-package-numbers">
<small class="umb-package-downloads">
@@ -86,7 +86,7 @@
</div> <!-- end packages -->
</div>
<div class="umb-packages__pagination" ng-if="vm.pagination.totalPages > 1">
<div class="umb-packages__pagination" ng-if="vm.pagination.totalPages > 1 && vm.loading === false">
<umb-pagination page-number="vm.pagination.pageNumber"
total-pages="vm.pagination.totalPages"

View File

@@ -594,25 +594,7 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}
/// <summary>
/// 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.
/// </summary>
[DataContract]
private class PostedFiles : IHaveUploadedFiles, INotificationModel
{
public PostedFiles()
{
UploadedFiles = new List<ContentItemFile>();
Notifications = new List<Notification>();
}
public List<ContentItemFile> UploadedFiles { get; private set; }
[DataMember(Name = "notifications")]
public List<Notification> Notifications { get; private set; }
}
/// <summary>
/// Ensures the item can be moved/copied to the new location
/// </summary>

View File

@@ -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<LocalPackageInstallModel> 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;
}
/// <summary>
/// Gets the package from Our to install
/// </summary>
/// <param name="packageGuid"></param>
/// <returns></returns>
[HttpGet]
public PackageInstallModel Fetch(string packageGuid)
{
@@ -50,6 +166,11 @@ namespace Umbraco.Web.Editors
return p;
}
/// <summary>
/// Extracts the package zip and gets the packages information
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
public PackageInstallModel Import(PackageInstallModel model)
{
@@ -60,6 +181,11 @@ namespace Umbraco.Web.Editors
return model;
}
/// <summary>
/// Installs the package files
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
public PackageInstallModel InstallFiles(PackageInstallModel model)
{
@@ -69,7 +195,11 @@ namespace Umbraco.Web.Editors
return model;
}
/// <summary>
/// Installs the packages data/business logic
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
public PackageInstallModel InstallData(PackageInstallModel model)
{
@@ -79,7 +209,11 @@ namespace Umbraco.Web.Editors
return model;
}
/// <summary>
/// Cleans up the package installation
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
public PackageInstallModel CleanUp(PackageInstallModel model)
{

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// 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.
/// </summary>
[DataContract]
internal class PostedFiles : IHaveUploadedFiles, INotificationModel
{
public PostedFiles()
{
UploadedFiles = new List<ContentItemFile>();
Notifications = new List<Notification>();
}
public List<ContentItemFile> UploadedFiles { get; private set; }
[DataMember(Name = "notifications")]
public List<Notification> Notifications { get; private set; }
}
}

View File

@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models
{
/// <summary>
/// A model that represents uploading a local package
/// </summary>
[DataContract(Name = "localPackageInstallModel")]
public class LocalPackageInstallModel : PackageInstallModel, IHaveUploadedFiles, INotificationModel
{
public LocalPackageInstallModel()
{
UploadedFiles = new List<ContentItemFile>();
Notifications = new List<Notification>();
}
public List<ContentItemFile> UploadedFiles { get; private set; }
[DataMember(Name = "notifications")]
public List<Notification> 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<string, string> ConflictingTemplateAliases { get; set; }
[DataMember(Name = "conflictingStyleSheetNames")]
public IDictionary<string, string> ConflictingStyleSheetNames { get; set; }
[DataMember(Name = "conflictingMacroAliases")]
public IDictionary<string, string> 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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -336,9 +336,11 @@
<Compile Include="Models\ContentEditing\MemberPropertyTypeDisplay.cs" />
<Compile Include="Models\ContentEditing\MemberTypeDisplay.cs" />
<Compile Include="Models\ContentEditing\MemberTypeSave.cs" />
<Compile Include="Models\ContentEditing\PostedFiles.cs" />
<Compile Include="Models\ContentEditing\PropertyGroupBasic.cs" />
<Compile Include="Models\ContentEditing\PropertyTypeBasic.cs" />
<Compile Include="Models\ContentEditing\SimpleNotificationModel.cs" />
<Compile Include="Models\LocalPackageInstallModel.cs" />
<Compile Include="Models\Mapping\ContentTypeModelMapperExtensions.cs" />
<Compile Include="Models\Mapping\LockedCompositionsResolver.cs" />
<Compile Include="Models\Mapping\PropertyGroupDisplayResolver.cs" />