diff --git a/.hgignore b/.hgignore index 349eab9bb..9f8e4dd39 100644 --- a/.hgignore +++ b/.hgignore @@ -7,6 +7,7 @@ glob:*.user glob:*.patch glob:*.hg glob:build +glob:artifacts glob:*.sln.cache glob:src/Orchard.Web/Media/* glob:desktop.ini diff --git a/AzurePackage.proj b/AzurePackage.proj new file mode 100644 index 000000000..ad02d137d --- /dev/null +++ b/AzurePackage.proj @@ -0,0 +1,137 @@ + + + + + + + $(MSBuildProjectDirectory)\lib + $(MSBuildProjectDirectory)\src + $(MSBuildProjectDirectory)\build + $(MSBuildProjectDirectory)\artifacts\Azure + + $(BuildFolder)\Compile + $(CompileFolder)\Orchard.Azure.CloudService.csx + $(ServiceFolder)\roles\Orchard.Azure.Web\approot + $(CompileFolder)\_PublishedWebsites + $(BuildFolder)\Stage + + x64 + x86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.cmd b/build.cmd index 8e320d6af..1e1be68bc 100644 --- a/build.cmd +++ b/build.cmd @@ -1,2 +1,3 @@ if "%~1"=="" build Build -msbuild /t:%~1 +msbuild /t:%~1 Orchard.proj +msbuild /t:%~1 AzurePackage.proj diff --git a/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs b/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs index 405569136..ccd2a82cb 100644 --- a/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs +++ b/src/Orchard.Azure.Tests/Environment/Configuration/AzureShellSettingsManagerTests.cs @@ -24,6 +24,12 @@ namespace Orchard.Azure.Tests.Environment.Configuration { DeleteAllBlobs( ((AzureShellSettingsManager)Loader).Container); } + [TearDown] + public void TearDown() { + // ensure default container is empty after running tests + DeleteAllBlobs(( (AzureShellSettingsManager)Loader ).Container); + } + [Test] public void SingleSettingsFileShouldComeBackAsExpected() { diff --git a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs index 5be4b3e8b..8c43b9a15 100644 --- a/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs +++ b/src/Orchard.Azure.Tests/Storage/AzureBlobStorageProviderTests.cs @@ -1,13 +1,9 @@ using System; -using System.Configuration; using System.IO; using NUnit.Framework; -using System.Diagnostics; using Orchard.Azure.Storage; using Microsoft.WindowsAzure; using System.Linq; -using Microsoft.WindowsAzure.StorageClient; -using System.Text; namespace Orchard.Azure.Tests.Storage { [TestFixture] diff --git a/src/Orchard.Azure/AzureFileSystem.cs b/src/Orchard.Azure/AzureFileSystem.cs new file mode 100644 index 000000000..4f66b1bc6 --- /dev/null +++ b/src/Orchard.Azure/AzureFileSystem.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.WindowsAzure; +using Microsoft.WindowsAzure.StorageClient; +using System.IO; +using Orchard.Storage; + +namespace Orchard.Azure { + public class AzureFileSystem { + public string ContainerName { get; protected set; } + + private readonly CloudStorageAccount _storageAccount; + private readonly string _shellName; + public CloudBlobClient BlobClient { get; private set; } + public CloudBlobContainer Container { get; private set; } + + public AzureFileSystem(string containerName, string shellName, bool isPrivate) + : this(containerName, shellName, isPrivate, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) { + } + + public AzureFileSystem(string containerName, string shellName, bool isPrivate, CloudStorageAccount storageAccount) { + // Setup the connection to custom storage accountm, e.g. Development Storage + _storageAccount = storageAccount; + ContainerName = containerName; + _shellName = shellName; + + BlobClient = _storageAccount.CreateCloudBlobClient(); + // Get and create the container if it does not exist + // The container is named with DNS naming restrictions (i.e. all lower case) + Container = BlobClient.GetContainerReference(ContainerName); + Container.CreateIfNotExist(); + + if ( isPrivate ) { + Container.SetPermissions(new BlobContainerPermissions + {PublicAccess = BlobContainerPublicAccessType.Off}); + } + else { + Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); + } + } + + private static void EnsurePathIsRelative(string path) { + if ( path.StartsWith("/") || path.StartsWith("http://")) + throw new ArgumentException("Path must be relative"); + } + + public IStorageFile GetFile(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + Container.EnsureBlobExists(path); + return new AzureBlobFileStorage(Container.GetBlockBlobReference(path)); + } + + public bool FileExists(string path) { + path = String.Concat(_shellName, "/", path); + return Container.BlobExists(path); + } + + public IEnumerable ListFiles(string path) { + EnsurePathIsRelative(path); + + string prefix = String.Concat(Container.Name, "/", _shellName, "/", path); + if ( !prefix.EndsWith("/") ) + prefix += "/"; + + foreach ( var blobItem in BlobClient.ListBlobsWithPrefix(prefix).OfType() ) { + yield return new AzureBlobFileStorage(blobItem); + } + } + + public IEnumerable ListFolders(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + if ( !Container.DirectoryExists(path) ) { + try { + CreateFolder(path); + } + catch ( Exception ex ) { + throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); + } + } + + return Container.GetDirectoryReference(path) + .ListBlobs() + .OfType() + .Select(d => new AzureBlobFolderStorage(d)) + .ToList(); + } + + public void CreateFolder(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + Container.EnsureDirectoryDoesNotExist(path); + Container.GetDirectoryReference(path); + } + + public void DeleteFolder(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + Container.EnsureDirectoryExists(path); + foreach ( var blob in Container.GetDirectoryReference(path).ListBlobs() ) { + if ( blob is CloudBlob ) + ( (CloudBlob)blob ).Delete(); + + if ( blob is CloudBlobDirectory ) + DeleteFolder(blob.Uri.ToString().Substring(Container.Uri.ToString().Length + 2 + _shellName.Length)); + } + } + + public void RenameFolder(string path, string newPath) { + EnsurePathIsRelative(path); + + EnsurePathIsRelative(newPath); + + if ( !path.EndsWith("/") ) + path += "/"; + + if ( !newPath.EndsWith("/") ) + newPath += "/"; + + foreach ( var blob in Container.GetDirectoryReference(_shellName + "/" + path).ListBlobs() ) { + if ( blob is CloudBlob ) { + string filename = Path.GetFileName(blob.Uri.ToString()); + string source = String.Concat(path, filename); + string destination = String.Concat(newPath, filename); + RenameFile(source, destination); + } + + if ( blob is CloudBlobDirectory ) { + string foldername = blob.Uri.Segments.Last(); + string source = String.Concat(path, foldername); + string destination = String.Concat(newPath, foldername); + RenameFolder(source, destination); + } + } + + } + + public void DeleteFile(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + Container.EnsureBlobExists(path); + var blob = Container.GetBlockBlobReference(path); + blob.Delete(); + } + + public void RenameFile(string path, string newPath) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + EnsurePathIsRelative(newPath); + newPath = String.Concat(_shellName, "/", newPath); + + Container.EnsureBlobExists(path); + Container.EnsureBlobDoesNotExist(newPath); + + var blob = Container.GetBlockBlobReference(path); + var newBlob = Container.GetBlockBlobReference(newPath); + newBlob.CopyFromBlob(blob); + blob.Delete(); + } + + public IStorageFile CreateFile(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + + if ( Container.BlobExists(path) ) { + throw new ArgumentException("File " + path + " already exists"); + } + + var blob = Container.GetBlockBlobReference(path); + blob.OpenWrite().Dispose(); // force file creation + return new AzureBlobFileStorage(blob); + } + + public string GetPublicUrl(string path) { + EnsurePathIsRelative(path); + path = String.Concat(_shellName, "/", path); + Container.EnsureBlobExists(path); + return Container.GetBlockBlobReference(path).Uri.ToString(); + } + + private class AzureBlobFileStorage : IStorageFile { + private readonly CloudBlockBlob _blob; + + public AzureBlobFileStorage(CloudBlockBlob blob) { + _blob = blob; + } + + public string GetPath() { + return _blob.Uri.ToString(); + } + + public string GetName() { + return Path.GetFileName(GetPath()); + } + + public long GetSize() { + return _blob.Properties.Length; + } + + public DateTime GetLastUpdated() { + return _blob.Properties.LastModifiedUtc; + } + + public string GetFileType() { + return Path.GetExtension(GetPath()); + } + + public Stream OpenRead() { + return _blob.OpenRead(); + } + + public Stream OpenWrite() { + return _blob.OpenWrite(); + } + + } + + private class AzureBlobFolderStorage : IStorageFolder { + private readonly CloudBlobDirectory _blob; + + public AzureBlobFolderStorage(CloudBlobDirectory blob) { + _blob = blob; + } + + public string GetName() { + return Path.GetDirectoryName(_blob.Uri.ToString()); + } + + public string GetPath() { + return _blob.Uri.ToString(); + } + + public long GetSize() { + return GetDirectorySize(_blob); + } + + public DateTime GetLastUpdated() { + return DateTime.MinValue; + } + + public IStorageFolder GetParent() { + if ( _blob.Parent != null ) { + return new AzureBlobFolderStorage(_blob.Parent); + } + throw new ArgumentException("Directory " + _blob.Uri + " does not have a parent directory"); + } + + private static long GetDirectorySize(CloudBlobDirectory directoryBlob) { + long size = 0; + + foreach ( var blobItem in directoryBlob.ListBlobs() ) { + if ( blobItem is CloudBlob ) + size += ( (CloudBlob)blobItem ).Properties.Length; + + if ( blobItem is CloudBlobDirectory ) + size += GetDirectorySize((CloudBlobDirectory)blobItem); + } + + return size; + } + } + + } +} diff --git a/src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs b/src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs new file mode 100644 index 000000000..e108a5f05 --- /dev/null +++ b/src/Orchard.Azure/Environment/Configuration/AzureAppDataFolder.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Orchard.Environment.Configuration; + +namespace Orchard.Azure.Environment.Configuration { + public class AzureAppDataFolder : IAppDataFolder { + + private readonly AzureFileSystem _fs; + + public AzureAppDataFolder(string shellName) { + _fs = new AzureFileSystem("appdata", shellName, true); + } + + public void CreateFile(string path, string content) { + if(_fs.FileExists(path)) { + DeleteFile(path); + } + + using (var stream = _fs.CreateFile(path).OpenWrite()) { + using(var writer = new StreamWriter(stream)) { + writer.Write(content); + } + } + } + + public string ReadFile(string path) { + using ( var stream = _fs.GetFile(path).OpenRead() ) { + using ( var reader = new StreamReader(stream) ) { + return reader.ReadToEnd(); + } + } + } + + public void DeleteFile(string path) { + _fs.DeleteFile(path); + } + + public bool FileExists(string path) { + return _fs.FileExists(path); + } + + public IEnumerable ListFiles(string path) { + return _fs.ListFiles(path).Select(sf => sf.GetPath()); + } + + public IEnumerable ListDirectories(string path) { + return _fs.ListFolders(path).Select(sf => sf.GetName()); + } + + public string CreateDirectory(string path) { + _fs.CreateFolder(path); + return Path.GetDirectoryName(path); + } + + public void SetBasePath(string basePath) { + throw new NotImplementedException(); + } + + public string MapPath(string path) { + throw new NotImplementedException(); + } + } +} diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService.sln b/src/Orchard.Azure/Orchard.Azure.CloudService.sln new file mode 100644 index 000000000..8f2b2423a --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}") = "Orchard.Azure.CloudService", "Orchard.Azure.CloudService\Orchard.Azure.CloudService.ccproj", "{03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.Web", "Orchard.Azure.Web\Orchard.Azure.Web.csproj", "{0DF8F426-9F30-4918-8F64-A5B40BA12D10}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure", "Orchard.Azure.csproj", "{2505AA84-65A6-43D0-9C27-4F44FD576284}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Framework", "..\Orchard\Orchard.Framework.csproj", "{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03C5327D-4E8E-45A7-ACD1-E18E7CAA3C4A}.Release|Any CPU.Build.0 = Release|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DF8F426-9F30-4918-8F64-A5B40BA12D10}.Release|Any CPU.Build.0 = Release|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2505AA84-65A6-43D0-9C27-4F44FD576284}.Release|Any CPU.Build.0 = Release|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj b/src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj new file mode 100644 index 000000000..22ee25d64 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService/Orchard.Azure.CloudService.ccproj @@ -0,0 +1,51 @@ + + + + Debug + AnyCPU + 1.0.0 + {03c5327d-4e8e-45a7-acd1-e18e7caa3c4a} + Library + Properties + Orchard.Azure.CloudService + Orchard.Azure.CloudService + True + OrchardCloudService + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + Orchard.Azure.Web + {0df8f426-9f30-4918-8f64-a5b40ba12d10} + True + Web + Orchard.Azure.Web + + + + + $(MSBuildExtensionsPath)\Microsoft\Cloud Service\v1.0\ + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg new file mode 100644 index 000000000..7e0691f74 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceConfiguration.cscfg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef new file mode 100644 index 000000000..c1f66c33a --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.CloudService/ServiceDefinition.csdef @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config b/src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config new file mode 100644 index 000000000..124536a7d --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Config/Diagnostics.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config b/src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config new file mode 100644 index 000000000..b56b3523b --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Config/Host.config @@ -0,0 +1,37 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Global.asax b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax new file mode 100644 index 000000000..8d7ecf448 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Orchard.Azure.Web.MvcApplication" Language="C#" %> diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs new file mode 100644 index 000000000..d7ee97dbf --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Global.asax.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Autofac; +using Orchard.Environment; + +namespace Orchard.Azure.Web { + // Note: For instructions on enabling IIS6 or IIS7 classic mode, + // visit http://go.microsoft.com/?LinkId=9394801 + + public class MvcApplication : HttpApplication { + private static IOrchardHost _host; + + public static void RegisterRoutes(RouteCollection routes) { + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + } + + protected void Application_Start() { + // This is temporary until MVC2 is officially released. + // We want to avoid running against an outdated preview installed in the GAC + CheckMvcVersion( + new Version("2.0.50217.0")/*MVC2 RTM file version #*/, + new Version("2.0.50129.0")/*MVC2 RC2 file version #*/, + new Version("2.0.41211.0")/*MVC2 RC file version #*/); + + RegisterRoutes(RouteTable.Routes); + + _host = OrchardStarter.CreateHost(MvcSingletons); + _host.Initialize(); + + //TODO: what's the failed initialization story - IoC failure in app start can leave you with a zombie appdomain + } + + protected void Application_BeginRequest() { + Context.Items["originalHttpContext"] = Context; + + _host.BeginRequest(); + } + + protected void Application_EndRequest() { + _host.EndRequest(); + } + + private void CheckMvcVersion(params Version[] allowedVersions) { + Assembly loadedMvcAssembly = typeof(System.Web.Mvc.Controller).Assembly; + Version loadedMvcVersion = ReadAssemblyFileVersion(loadedMvcAssembly); + + if ( allowedVersions.All(allowed => loadedMvcVersion != allowed) ) { + string message; + if ( loadedMvcAssembly.GlobalAssemblyCache ) { + message = string.Format( + "Orchard has been deployed with a version of {0} that has a different file version ({1}) " + + "than the version installed in the GAC ({2}).\r\n" + + "This implies that Orchard will not be able to run properly in this machine configuration.\r\n" + + "Please un-install MVC from the GAC or install a more recent version.", + loadedMvcAssembly.GetName().Name, + allowedVersions.First(), + loadedMvcVersion); + } + else { + message = string.Format( + "Orchard has been configured to run with a file version {1} of \"{0}\" " + + "but the version deployed with the application is {2}.\r\n" + + "This probably implies that Orchard is deployed with a newer version " + + "and the source code hasn't been updated accordingly.\r\n" + + "Update the Orchard.Web application source code (look for \"CheckMvcVersion\") to " + + "specify the correct file version number.\r\n", + loadedMvcAssembly.GetName().Name, + allowedVersions.First(), + loadedMvcVersion); + } + + throw new HttpException(500, message); + } + } + + private Version ReadAssemblyFileVersion(Assembly assembly) { + object[] attributes = assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true); + if ( attributes == null || attributes.Length != 1 ) { + string message = string.Format("Assembly \"{0}\" doesn't have a \"{1}\" attribute", + assembly.GetName().Name, typeof(AssemblyFileVersionAttribute).FullName); + throw new FileLoadException(message); + } + + var attribute = (AssemblyFileVersionAttribute)attributes[0]; + return new Version(attribute.Version); + } + + + protected void MvcSingletons(ContainerBuilder builder) { + builder.RegisterInstance(ControllerBuilder.Current); + builder.RegisterInstance(RouteTable.Routes); + builder.RegisterInstance(ModelBinders.Binders); + builder.RegisterInstance(ModelMetadataProviders.Current); + builder.RegisterInstance(ViewEngines.Engines); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj new file mode 100644 index 000000000..0f11fc754 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj @@ -0,0 +1,157 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {0DF8F426-9F30-4918-8F64-A5B40BA12D10} + {F85E285D-A4E0-4152-9332-AB1D724D3325};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.Azure.Web + Orchard.Azure.Web + v3.5 + false + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + False + ..\..\lib\autofac\Autofac.dll + + + False + ..\..\..\lib\autofac\Autofac.Configuration.dll + + + False + ..\..\lib\autofac\Autofac.Integration.Web.dll + + + False + ..\..\lib\autofac\Autofac.Integration.Web.Mvc.dll + + + False + ..\..\..\lib\Castle Windsor 2.0\bin\Castle.Core.dll + + + False + ..\..\..\lib\Castle Windsor 2.0\bin\Castle.DynamicProxy2.dll + + + + + + False + ..\..\lib\fluentnhibernate\NHibernate.ByteCode.Castle.dll + + + + + 3.5 + + + 3.5 + + + 3.5 + + + False + ..\..\lib\sqlite\x64\System.Data.SQLite.DLL + + + False + ..\..\..\..\..\..\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 2\\Assemblies\System.Web.Mvc.dll + True + + + 3.5 + + + + + + + + + + + + + False + bin\Orchard.Framework.dll + + + False + bin\Orchard.Azure.dll + + + + + Global.asax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + 60453 + / + + + False + False + + + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs b/src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c02dd9f71 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.Azure.Web")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.Azure.Web")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("76077e05-d95f-49c7-b11a-2b0fc9176e7c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Web.config b/src/Orchard.Azure/Orchard.Azure.Web/Web.config new file mode 100644 index 000000000..b1de2f004 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/Web.config @@ -0,0 +1,158 @@ + + + + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs b/src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs new file mode 100644 index 000000000..e5b12b130 --- /dev/null +++ b/src/Orchard.Azure/Orchard.Azure.Web/WebRole.cs @@ -0,0 +1,52 @@ +using System.Linq; +using Microsoft.WindowsAzure; +using Microsoft.WindowsAzure.Diagnostics; +using Microsoft.WindowsAzure.ServiceRuntime; + +namespace Orchard.Azure.Web { + public class WebRole : RoleEntryPoint { + public override bool OnStart() { + DiagnosticMonitor.Start("DiagnosticsConnectionString"); + + #region Setup CloudStorageAccount Configuration Setting Publisher + + // This code sets up a handler to update CloudStorageAccount instances when their corresponding + // configuration settings change in the service configuration file. + CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => { + // Provide the configSetter with the initial value + configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)); + + RoleEnvironment.Changed += (sender, arg) => { + if ( arg.Changes.OfType() + .Any(change => ( change.ConfigurationSettingName == configName )) ) { + // The corresponding configuration setting has changed, propagate the value + if ( !configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)) ) { + // In this case, the change to the storage account credentials in the + // service configuration is significant enough that the role needs to be + // recycled in order to use the latest settings. (for example, the + // endpoint has changed) + RoleEnvironment.RequestRecycle(); + } + } + }; + }); + #endregion + + + // For information on handling configuration changes + // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357. + RoleEnvironment.Changing += (sender, e) => { + // If a configuration setting is changing + if ( + e.Changes.Any( + change => change is RoleEnvironmentConfigurationSettingChange) ) { + // Set e.Cancel to true to restart this role instance + e.Cancel = true; + } + }; + + return base.OnStart(); + } + + } +} diff --git a/src/Orchard.Azure/Orchard.Azure.csproj b/src/Orchard.Azure/Orchard.Azure.csproj index f23dc2e67..21becee28 100644 --- a/src/Orchard.Azure/Orchard.Azure.csproj +++ b/src/Orchard.Azure/Orchard.Azure.csproj @@ -51,9 +51,11 @@ + + diff --git a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs index 2181919c8..938a16602 100644 --- a/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs +++ b/src/Orchard.Azure/Storage/AzureBlobStorageProvider.cs @@ -1,250 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.WindowsAzure; -using Microsoft.WindowsAzure.StorageClient; -using System.IO; +using Microsoft.WindowsAzure; using Orchard.Storage; namespace Orchard.Azure.Storage { - public interface IDependency { } - public class AzureBlobStorageProvider : IStorageProvider { - public const string ContainerName = "media"; // container names must be lower cased - - private readonly CloudStorageAccount _storageAccount; - private string _shellName; - public CloudBlobClient BlobClient { get; private set; } - public CloudBlobContainer Container { get; private set; } + public class AzureBlobStorageProvider : AzureFileSystem, IStorageProvider { public AzureBlobStorageProvider(string shellName) : this(shellName, CloudStorageAccount.FromConfigurationSetting("DataConnectionString")) { } - public AzureBlobStorageProvider(string shellName, CloudStorageAccount storageAccount) { - // Setup the connection to custom storage accountm, e.g. Development Storage - _storageAccount = storageAccount; - - _shellName = shellName; - - BlobClient = _storageAccount.CreateCloudBlobClient(); - // Get and create the container if it does not exist - // The container is named with DNS naming restrictions (i.e. all lower case) - Container = BlobClient.GetContainerReference(ContainerName); - Container.CreateIfNotExist(); - - Container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Container }); - } - - private static void EnsurePathIsRelative(string path) { - if ( path.StartsWith("/") || path.StartsWith("http://")) - throw new ArgumentException("Path must be relative"); - } - - public IStorageFile GetFile(string path) { - EnsurePathIsRelative(path); - Container.EnsureBlobExists(path); - return new AzureBlobFileStorage(Container.GetBlockBlobReference(path)); - } - - public IEnumerable ListFiles(string path) { - EnsurePathIsRelative(path); - - string prefix = String.Concat(Container.Name, "/", _shellName, "/", path); - if ( !prefix.EndsWith("/") ) - prefix += "/"; - - foreach ( var blobItem in BlobClient.ListBlobsWithPrefix(prefix).OfType() ) { - yield return new AzureBlobFileStorage(blobItem); - } - } - - public IEnumerable ListFolders(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - if ( !Container.DirectoryExists(path) ) { - try { - CreateFolder(path); - } - catch ( Exception ex ) { - throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); - } - } - - return Container.GetDirectoryReference(path) - .ListBlobs() - .OfType() - .Select(d => new AzureBlobFolderStorage(d)) - .ToList(); - } - - public void CreateFolder(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - Container.EnsureDirectoryDoesNotExist(path); - Container.GetDirectoryReference(path); - } - - public void DeleteFolder(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - Container.EnsureDirectoryExists(path); - foreach ( var blob in Container.GetDirectoryReference(path).ListBlobs() ) { - if ( blob is CloudBlob ) - ( (CloudBlob)blob ).Delete(); - - if ( blob is CloudBlobDirectory ) - DeleteFolder(blob.Uri.ToString().Substring(Container.Uri.ToString().Length + 2 + _shellName.Length)); - } - } - - public void RenameFolder(string path, string newPath) { - EnsurePathIsRelative(path); - - EnsurePathIsRelative(newPath); - - if ( !path.EndsWith("/") ) - path += "/"; - - if ( !newPath.EndsWith("/") ) - newPath += "/"; - - foreach ( var blob in Container.GetDirectoryReference(_shellName + "/" + path).ListBlobs() ) { - if ( blob is CloudBlob ) { - string filename = Path.GetFileName(blob.Uri.ToString()); - string source = String.Concat(path, filename); - string destination = String.Concat(newPath, filename); - RenameFile(source, destination); - } - - if ( blob is CloudBlobDirectory ) { - string foldername = blob.Uri.Segments.Last(); - string source = String.Concat(path, foldername); - string destination = String.Concat(newPath, foldername); - RenameFolder(source, destination); - } - } - - } - - public void DeleteFile(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - Container.EnsureBlobExists(path); - var blob = Container.GetBlockBlobReference(path); - blob.Delete(); - } - - public void RenameFile(string path, string newPath) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - EnsurePathIsRelative(newPath); - newPath = String.Concat(_shellName, "/", newPath); - - Container.EnsureBlobExists(path); - Container.EnsureBlobDoesNotExist(newPath); - - var blob = Container.GetBlockBlobReference(path); - var newBlob = Container.GetBlockBlobReference(newPath); - newBlob.CopyFromBlob(blob); - blob.Delete(); - } - - public IStorageFile CreateFile(string path) { - EnsurePathIsRelative(path); - path = String.Concat(_shellName, "/", path); - - if ( Container.BlobExists(path) ) { - throw new ArgumentException("File " + path + " already exists"); - } - - var blob = Container.GetBlockBlobReference(path); - blob.OpenWrite().Dispose(); // force file creation - return new AzureBlobFileStorage(blob); - } - - private class AzureBlobFileStorage : IStorageFile { - private readonly CloudBlockBlob _blob; - - public AzureBlobFileStorage(CloudBlockBlob blob) { - _blob = blob; - } - - public string GetPath() { - return _blob.Uri.ToString(); - } - - public string GetName() { - return Path.GetFileName(GetPath()); - } - - public long GetSize() { - return _blob.Properties.Length; - } - - public DateTime GetLastUpdated() { - return _blob.Properties.LastModifiedUtc; - } - - public string GetFileType() { - return Path.GetExtension(GetPath()); - } - - public Stream OpenRead() { - return _blob.OpenRead(); - } - - public Stream OpenWrite() { - return _blob.OpenWrite(); - } - - } - - private class AzureBlobFolderStorage : IStorageFolder { - private readonly CloudBlobDirectory _blob; - - public AzureBlobFolderStorage(CloudBlobDirectory blob) { - _blob = blob; - } - - public string GetName() { - return _blob.Uri.ToString(); - } - - public long GetSize() { - return GetDirectorySize(_blob); - } - - public DateTime GetLastUpdated() { - return DateTime.MinValue; - } - - public IStorageFolder GetParent() { - if ( _blob.Parent != null ) { - return new AzureBlobFolderStorage(_blob.Parent); - } - throw new ArgumentException("Directory " + _blob.Uri + " does not have a parent directory"); - } - - private static long GetDirectorySize(CloudBlobDirectory directoryBlob) { - long size = 0; - - foreach ( var blobItem in directoryBlob.ListBlobs() ) { - if ( blobItem is CloudBlob ) - size += ( (CloudBlob)blobItem ).Properties.Length; - - if ( blobItem is CloudBlobDirectory ) - size += GetDirectorySize((CloudBlobDirectory)blobItem); - } - - return size; - } - } - + public AzureBlobStorageProvider(string shellName, CloudStorageAccount storageAccount) : base("media", shellName, false, storageAccount) { } } } + +