From d81ccecd10d8acd36741aa85c0492176aa59e4ca Mon Sep 17 00:00:00 2001 From: perploug Date: Tue, 27 Aug 2013 10:05:16 +0200 Subject: [PATCH] Media service --- .../Views/common/dialogs/mediapicker.html | 9 +- .../Editors/BackOfficeController.cs | 1 + .../Editors/MediaUploadController.cs | 260 ++++++++++++++++++ 3 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web/Editors/MediaUploadController.cs diff --git a/src/Umbraco.Web.UI/umbraco/Views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI/umbraco/Views/common/dialogs/mediapicker.html index 2112ead1f2..ff8fb83842 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/common/dialogs/mediapicker.html +++ b/src/Umbraco.Web.UI/umbraco/Views/common/dialogs/mediapicker.html @@ -17,6 +17,9 @@ data-file-upload="options" data-ng-class="{'fileupload-processing': processing()
+ + {{progress | json}} + @@ -33,9 +36,13 @@ data-file-upload="options" data-ng-class="{'fileupload-processing': processing() + {{queue | json}} +
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index ad20aa592a..124d4f6824 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -55,6 +55,7 @@ namespace Umbraco.Web.Editors //API URLs {"contentApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl("PostSave")}, {"mediaApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl("GetRootMedia")}, + {"mediaUploadApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl("Post")}, {"sectionApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl("GetSections")}, {"treeApplicationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl("GetApplicationTrees")}, {"contentTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl("GetAllowedChildren")}, diff --git a/src/Umbraco.Web/Editors/MediaUploadController.cs b/src/Umbraco.Web/Editors/MediaUploadController.cs new file mode 100644 index 0000000000..a49a8e396b --- /dev/null +++ b/src/Umbraco.Web/Editors/MediaUploadController.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Web; +using System.Web.Http; +using System.Web.Script.Serialization; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Editors +{ + /* + * WebApi Implementation: Luke Baughan https://github.com/bUKaneer/jQuery-File-Upload + * based on HttpHandler Implementation: Max Pavlov https://github.com/maxpavlov/jQuery-File-Upload.MVC3 + */ + + [PluginController("UmbracoApi")] + public class MediaUploadController : UmbracoApiController + { + private readonly JavaScriptSerializer _js = new JavaScriptSerializer { MaxJsonLength = 41943040 }; + private readonly string _storageRoot = Core.IO.IOHelper.MapPath( Core.IO.SystemDirectories.Media ); + public bool _isReusable { get { return false; } } + + + public string Meh() + { + return "wo"; + } + + + #region Get + private HttpResponseMessage DownloadFileContent() + { + var filename = HttpContext.Current.Request["f"]; + var filePath = _storageRoot + filename; + if (File.Exists(filePath)) + { + HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); + response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read)); + response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") + { + FileName = filename + }; + return response; + } + return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, ""); + } + + private HttpResponseMessage DownloadFileList() + { + var files = + new DirectoryInfo(_storageRoot) + .GetFiles("*", SearchOption.TopDirectoryOnly) + .Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden)) + .Select(f => new FilesStatus(f)) + .ToArray(); + HttpContext.Current.Response.AppendHeader("Content-Disposition", "inline; filename=\"files.json\""); + return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, _js.Serialize(files)); + } + + + public HttpResponseMessage Get() + { + if (!string.IsNullOrEmpty(HttpContext.Current.Request["f"])) + { + return DownloadFileContent(); + } + return DownloadFileList(); + } + #endregion + + #region Post & Put + public HttpResponseMessage Post() + { + return UploadFile(HttpContext.Current); + } + + public HttpResponseMessage Put() + { + return UploadFile(HttpContext.Current); + } + + private HttpResponseMessage UploadFile(HttpContext context) + { + var statuses = new List(); + var headers = context.Request.Headers; + + if (string.IsNullOrEmpty(headers["X-File-Name"])) + { + UploadWholeFile(context, statuses); + } + else + { + UploadPartialFile(headers["X-File-Name"], context, statuses); + } + + return WriteJsonIframeSafe(context, statuses); + } + + private HttpResponseMessage WriteJsonIframeSafe(HttpContext context, List statuses) + { + context.Response.AddHeader("Vary", "Accept"); + var response = new HttpResponseMessage() + { + Content = new StringContent(_js.Serialize(statuses.ToArray())) + }; + if (context.Request["HTTP_ACCEPT"].Contains("application/json")) + { + response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + else + { + response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); + } + return response; + } + + // Upload partial file + private void UploadPartialFile(string fileName, HttpContext context, List statuses) + { + if (context.Request.Files.Count != 1) throw new HttpRequestValidationException("Attempt to upload chunked file containing more than one fragment per request"); + var inputStream = context.Request.Files[0].InputStream; + var fullName = _storageRoot + Path.GetFileName(fileName); + + using (var fs = new FileStream(fullName, FileMode.Append, FileAccess.Write)) + { + var buffer = new byte[1024]; + + var l = inputStream.Read(buffer, 0, 1024); + while (l > 0) + { + fs.Write(buffer, 0, l); + l = inputStream.Read(buffer, 0, 1024); + } + fs.Flush(); + fs.Close(); + } + statuses.Add(new FilesStatus(new FileInfo(fullName))); + } + + // Upload entire file + private void UploadWholeFile(HttpContext context, List statuses) + { + for (int i = 0; i < context.Request.Files.Count; i++) + { + var file = context.Request.Files[i]; + string fullPath = _storageRoot + Path.GetFileName(file.FileName); + Directory.CreateDirectory(_storageRoot); + + file.SaveAs(fullPath); + string fullName = Path.GetFileName(file.FileName); + statuses.Add(new FilesStatus(fullName, file.ContentLength, fullPath)); + } + } + #endregion + + #region Delete + public HttpResponseMessage Delete() + { + var filePath = _storageRoot + HttpContext.Current.Request["f"]; + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, ""); + } + #endregion + } + + #region FileStatus + public class FilesStatus + { + public const string HandlerPath = "/"; + + public string group { get; set; } + public string name { get; set; } + public string type { get; set; } + public int size { get; set; } + public string progress { get; set; } + public string url { get; set; } + public string thumbnail_url { get; set; } + public string delete_url { get; set; } + public string delete_type { get; set; } + public string error { get; set; } + + public FilesStatus() + { + } + + public FilesStatus(FileInfo fileInfo) + { + SetValues(fileInfo.Name, (int)fileInfo.Length, fileInfo.FullName); + } + + public FilesStatus(string fileName, int fileLength, string fullPath) + { + SetValues(fileName, fileLength, fullPath); + } + + private void SetValues(string fileName, int fileLength, string fullPath) + { + name = fileName; + type = "image/png"; + size = fileLength; + progress = "1.0"; + url = HandlerPath + "api/Upload?f=" + fileName; + delete_url = HandlerPath + "api/Upload?f=" + fileName; + delete_type = "DELETE"; + var ext = Path.GetExtension(fullPath); + var fileSize = ConvertBytesToMegabytes(new FileInfo(fullPath).Length); + if (fileSize > 3 || !IsImage(ext)) + { + thumbnail_url = "/Content/img/generalFile.png"; + } + else + { + thumbnail_url = @"data:image/png;base64," + EncodeFile(fullPath); + } + } + + private bool IsImage(string ext) + { + return ext == ".gif" || ext == ".jpg" || ext == ".png"; + } + + private string EncodeFile(string fileName) + { + byte[] bytes; + using (Image image = Image.FromFile(fileName)) + { + var ratioX = (double)80 / image.Width; + var ratioY = (double)80 / image.Height; + var ratio = Math.Min(ratioX, ratioY); + var newWidth = (int)(image.Width * ratio); + var newHeight = (int)(image.Height * ratio); + var newImage = new Bitmap(newWidth, newHeight); + Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight); + ImageConverter converter = new ImageConverter(); + bytes = (byte[])converter.ConvertTo(newImage, typeof(byte[])); + newImage.Dispose(); + } + return Convert.ToBase64String(bytes); + } + + private static double ConvertBytesToMegabytes(long bytes) + { + return (bytes / 1024f) / 1024f; + } + } + #endregion + +}