├── .gitignore ├── .nuget ├── NuGet.Config ├── NuGet.exe └── NuGet.targets ├── AzureBlobFileSystem.Test ├── AzureBlobFileSystem.Test.csproj ├── AzureBlobStorageProviderTest.cs ├── FileSystemStorageProviderTest.cs ├── Properties │ └── AssemblyInfo.cs ├── StorageUniversalTest.cs └── packages.config ├── AzureBlobFileSystem.sln ├── AzureBlobFileSystem ├── AzureBlobFileSystem.csproj ├── AzureBlobStorageProvider.cs ├── CloudBlobContainerExtensions.cs ├── FileSystemStorageProvider.cs ├── IStorageFile.cs ├── IStorageFolder.cs ├── IStorageProvider.cs ├── PathValidation.cs ├── Properties │ └── AssemblyInfo.cs ├── SasPermissionFlags.cs └── packages.config ├── BlobFileSystem.Azure.nuspec ├── LICENSE ├── README.md ├── Snk └── snk.snk └── nugetpack.cmd /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | packages/ 127 | ## TODO: If the tool you use requires repositories.config uncomment the next line 128 | #!packages/repositories.config 129 | 130 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 131 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 132 | !packages/build/ 133 | 134 | # Windows Azure Build Output 135 | csx/ 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.dbproj.schemaview 150 | *.pfx 151 | *.publishsettings 152 | node_modules/ 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | *.mdf 166 | *.ldf 167 | 168 | # Business Intelligence projects 169 | *.rdl.data 170 | *.bim.layout 171 | *.bim_*.settings 172 | 173 | # Microsoft Fakes 174 | FakesAssemblies/ 175 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pofider/AzureBlobFileSystem/873e285b3891519ecbf4cfe527f9eaa72fe08174/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 $(NuGetExePath) 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /AzureBlobFileSystem.Test/AzureBlobFileSystem.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {84F6D56B-37F2-4083-A150-EC4ACD9BBA98} 7 | Library 8 | Properties 9 | AzureBlobFileSystem.Test 10 | AzureBlobFileSystem.Test 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\ 20 | true 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | true 41 | 42 | 43 | ../Snk/snk.snk 44 | 45 | 46 | 47 | ..\packages\Microsoft.Data.Edm.5.6.0\lib\net40\Microsoft.Data.Edm.dll 48 | 49 | 50 | ..\packages\Microsoft.Data.OData.5.6.0\lib\net40\Microsoft.Data.OData.dll 51 | 52 | 53 | ..\packages\Microsoft.Data.Services.Client.5.6.0\lib\net40\Microsoft.Data.Services.Client.dll 54 | 55 | 56 | ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll 57 | 58 | 59 | False 60 | ..\packages\WindowsAzure.Storage.3.1.0.1\lib\net40\Microsoft.WindowsAzure.Storage.dll 61 | 62 | 63 | ..\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll 64 | 65 | 66 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 67 | 68 | 69 | 70 | 71 | ..\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | {FA608600-4A63-497E-BB7E-57CE59EF7E61} 100 | AzureBlobFileSystem 101 | 102 | 103 | 104 | 105 | 106 | 107 | False 108 | 109 | 110 | False 111 | 112 | 113 | False 114 | 115 | 116 | False 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 127 | 128 | 129 | 130 | 137 | -------------------------------------------------------------------------------- /AzureBlobFileSystem.Test/AzureBlobStorageProviderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Microsoft.WindowsAzure.Storage; 5 | using NUnit.Framework; 6 | 7 | namespace AzureBlobFileSystem.Test 8 | { 9 | [TestFixture] 10 | public class AzureBlobStorageProviderTest : StorageUniversalTest 11 | { 12 | protected override IStorageProvider CreateStorage() 13 | { 14 | CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount; 15 | return new AzureBlobStorageProvider(storageAccount); 16 | } 17 | 18 | [Test] 19 | [ExpectedException(typeof(ArgumentException))] 20 | public void renameFolder_for_container_should_throw_argument_exception() 21 | { 22 | SUT.RenameFolder(TEST_FOLDER, TEST_FOLDER + "2"); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem.Test/FileSystemStorageProviderTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace AzureBlobFileSystem.Test 6 | { 7 | [TestFixture] 8 | public class FileSystemStorageProviderTest : StorageUniversalTest 9 | { 10 | protected override IStorageProvider CreateStorage() 11 | { 12 | return new FileSystemStorageProvider(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AzureBlobFileSystem.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AzureBlobFileSystem.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6954cf0b-aa79-4b9f-a946-1a1ed54f24e4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AzureBlobFileSystem.Test/StorageUniversalTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using NUnit.Framework; 6 | 7 | namespace AzureBlobFileSystem.Test 8 | { 9 | [TestFixture] 10 | public abstract class StorageUniversalTest 11 | { 12 | protected IStorageProvider SUT; 13 | 14 | protected abstract IStorageProvider CreateStorage(); 15 | 16 | protected string TEST_FOLDER; 17 | 18 | [SetUp] 19 | public void StorageUniversalTest_SetUp() 20 | { 21 | SUT = CreateStorage(); 22 | 23 | TEST_FOLDER = Guid.NewGuid().ToString(); 24 | SUT.TryCreateFolder(TEST_FOLDER); 25 | SUT.ListFiles(TEST_FOLDER).ToList().ForEach(f => SUT.DeleteFile(f.GetPath())); 26 | } 27 | 28 | [TearDown] 29 | public void StorageUniversalTest_TearDown() 30 | { 31 | SUT.ListFiles(TEST_FOLDER).ToList().ForEach(f => SUT.DeleteFile(f.GetPath())); 32 | SUT.DeleteFolder(TEST_FOLDER); 33 | } 34 | 35 | [Test] 36 | public void create_write_read_file_should_equal() 37 | { 38 | using (var stream = SUT.CreateFile(SUT.Combine(TEST_FOLDER, "test.txt")).OpenWrite()) 39 | { 40 | stream.Write(new byte[] { 1 }, 0, 1); 41 | } 42 | 43 | using (var stream = SUT.GetFile(SUT.Combine(TEST_FOLDER, "test.txt")).OpenRead()) 44 | { 45 | var buffer = new byte[1]; 46 | stream.Read(buffer, 0, 1); 47 | 48 | Assert.AreEqual(1, buffer[0]); 49 | } 50 | } 51 | 52 | [Test] 53 | public void getSize_should_return_numOfBytes() 54 | { 55 | using (var stream = SUT.CreateFile(SUT.Combine(TEST_FOLDER, "test.txt")).OpenWrite()) 56 | { 57 | stream.Write(new byte[] { 1 }, 0, 1); 58 | } 59 | 60 | Assert.AreEqual(1, SUT.GetFile(SUT.Combine(TEST_FOLDER, "test.txt")).GetSize()); 61 | } 62 | 63 | 64 | [Test] 65 | public void update_file_content() 66 | { 67 | using (var stream = SUT.CreateFile(SUT.Combine(TEST_FOLDER, "test.txt")).OpenWrite()) 68 | { 69 | stream.Write(new byte[] { 1 }, 0, 1); 70 | } 71 | 72 | using (var stream = SUT.GetFile(SUT.Combine(TEST_FOLDER, "test.txt")).OpenWrite()) 73 | { 74 | stream.Write(new byte[] { 1, 2 }, 0, 2); 75 | } 76 | 77 | using (var stream = SUT.GetFile(SUT.Combine(TEST_FOLDER, "test.txt")).OpenRead()) 78 | { 79 | var buffer = new byte[2]; 80 | stream.Read(buffer, 0, 2); 81 | 82 | Assert.AreEqual(1, buffer[0]); 83 | Assert.AreEqual(2, buffer[1]); 84 | } 85 | } 86 | 87 | [Test] 88 | public void create_delete_file() 89 | { 90 | SUT.CreateFile(SUT.Combine(TEST_FOLDER, "test.txt")); 91 | SUT.DeleteFile(SUT.Combine(TEST_FOLDER, "test.txt")); 92 | 93 | Assert.AreEqual(0, SUT.ListFiles(TEST_FOLDER).Count()); 94 | } 95 | 96 | [Test] 97 | public void fileExists_should_be_false_for_not_existing() 98 | { 99 | Assert.IsFalse(SUT.FileExists(SUT.Combine(TEST_FOLDER, "notexisting.txt"))); 100 | } 101 | 102 | [Test] 103 | public void fileExists_should_be_true_for_existing() 104 | { 105 | SUT.CreateFile(SUT.Combine(TEST_FOLDER, "test.txt")); 106 | Assert.IsTrue(SUT.FileExists(SUT.Combine(TEST_FOLDER, "test.txt"))); 107 | } 108 | 109 | [Test] 110 | public void renameFile() 111 | { 112 | SUT.CreateFile(SUT.Combine(TEST_FOLDER, "old.txt")); 113 | 114 | SUT.RenameFile(SUT.Combine(TEST_FOLDER, "old.txt"), SUT.Combine(TEST_FOLDER, "new.txt")); 115 | 116 | Assert.IsTrue(SUT.FileExists(SUT.Combine(TEST_FOLDER, "new.txt"))); 117 | Assert.IsFalse(SUT.FileExists(SUT.Combine(TEST_FOLDER, "old.txt"))); 118 | } 119 | 120 | [Test] 121 | public void createFolder_and_listFolder_shoud_contain_it() 122 | { 123 | SUT.CreateFolder(SUT.Combine(TEST_FOLDER, "folder")); 124 | 125 | Assert.AreEqual("folder", SUT.ListFolders(TEST_FOLDER).Single().GetName()); 126 | } 127 | 128 | [Test] 129 | public void listFiles_should_contain_files_from_folder() 130 | { 131 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 132 | SUT.CreateFolder(folder); 133 | 134 | SUT.CreateFile(SUT.Combine(folder, "test.txt")); 135 | 136 | Assert.AreEqual("test.txt", SUT.ListFiles(folder).Single().GetName()); 137 | } 138 | 139 | [Test] 140 | public void deleteFolder_should_remove_it_from_folder_list() 141 | { 142 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 143 | SUT.CreateFolder(folder); 144 | SUT.DeleteFolder(folder); 145 | 146 | Assert.AreEqual(0, SUT.ListFolders(TEST_FOLDER).Count()); 147 | } 148 | 149 | [Test] 150 | public void deleteFolder_should_remove_nested_folders() 151 | { 152 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 153 | SUT.CreateFolder(folder); 154 | 155 | var folder2 = SUT.Combine(folder, "folder2"); 156 | SUT.CreateFolder(folder2); 157 | 158 | var folder3 = SUT.Combine(folder2, "folder"); 159 | SUT.CreateFolder(folder3); 160 | 161 | SUT.DeleteFolder(folder); 162 | 163 | Assert.AreEqual(0, SUT.ListFolders(TEST_FOLDER).Count()); 164 | Assert.AreEqual(0, SUT.ListFolders(folder2).Count()); 165 | } 166 | 167 | [Test] 168 | public void deleteFolder_should_remove_nested_files() 169 | { 170 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 171 | SUT.CreateFolder(folder); 172 | 173 | SUT.CreateFile(SUT.Combine(folder, "f1")); 174 | 175 | var folder2 = SUT.Combine(folder, "folder2"); 176 | SUT.CreateFolder(folder2); 177 | 178 | SUT.CreateFile(SUT.Combine(folder2, "f2")); 179 | 180 | SUT.DeleteFolder(folder); 181 | 182 | Assert.AreEqual(0, SUT.ListFolders(TEST_FOLDER).Count()); 183 | Assert.AreEqual(0, SUT.ListFiles(folder).Count()); 184 | Assert.AreEqual(0, SUT.ListFiles(folder2).Count()); 185 | } 186 | 187 | [Test] 188 | public void renameFolder_should_change_path() 189 | { 190 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 191 | SUT.CreateFolder(folder); 192 | 193 | SUT.RenameFolder(folder, folder + "2"); 194 | 195 | Assert.AreEqual("folder2", SUT.ListFolders(TEST_FOLDER).Single().GetName()); 196 | } 197 | 198 | [Test] 199 | public void renameFolder_should_mode_inner_files() 200 | { 201 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 202 | SUT.CreateFolder(folder); 203 | 204 | SUT.CreateFile(SUT.Combine(folder, "f1")); 205 | 206 | SUT.RenameFolder(folder, folder + "2"); 207 | 208 | Assert.AreEqual("f1", SUT.ListFiles(folder + "2").Single().GetName()); 209 | } 210 | 211 | [Test] 212 | public void sharedPath_should_return_when_given_offset() 213 | { 214 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 215 | SUT.CreateFolder(folder); 216 | 217 | SUT.CreateFile(SUT.Combine(folder, "f1")); 218 | 219 | var offset = DateTimeOffset.MaxValue; 220 | 221 | Assert.DoesNotThrow(() => 222 | { 223 | var file = SUT.ListFiles(folder).First(); 224 | var path = file.GetPath(); 225 | var sharedPath = file.GetSharedAccessPath(offset); 226 | Trace.WriteLine(path); 227 | Trace.WriteLine(sharedPath); 228 | }); 229 | } 230 | 231 | [Test] 232 | public void sharedPath_should_return_when_not_given_offset() 233 | { 234 | var folder = SUT.Combine(TEST_FOLDER, "folder"); 235 | SUT.CreateFolder(folder); 236 | 237 | SUT.CreateFile(SUT.Combine(folder, "f1")); 238 | 239 | Assert.DoesNotThrow(() => 240 | { 241 | var file = SUT.ListFiles(folder).First(); 242 | var path = file.GetPath(); 243 | var sharedPath = file.GetSharedAccessPath(); 244 | Trace.WriteLine(path); 245 | Trace.WriteLine(sharedPath); 246 | }); 247 | } 248 | } 249 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AzureBlobFileSystem.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureBlobFileSystem", "AzureBlobFileSystem\AzureBlobFileSystem.csproj", "{FA608600-4A63-497E-BB7E-57CE59EF7E61}" 5 | EndProject 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{1C5F987C-F859-439D-BC11-82DBA4C641CA}" 7 | ProjectSection(SolutionItems) = preProject 8 | .nuget\NuGet.Config = .nuget\NuGet.Config 9 | .nuget\NuGet.exe = .nuget\NuGet.exe 10 | .nuget\NuGet.targets = .nuget\NuGet.targets 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureBlobFileSystem.Test", "AzureBlobFileSystem.Test\AzureBlobFileSystem.Test.csproj", "{84F6D56B-37F2-4083-A150-EC4ACD9BBA98}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AEA43007-A07B-4E1F-9ADD-707A9D1EDBC3}" 16 | ProjectSection(SolutionItems) = preProject 17 | BlobFileSystem.Azure.nuspec = BlobFileSystem.Azure.nuspec 18 | nugetpack.cmd = nugetpack.cmd 19 | EndProjectSection 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {FA608600-4A63-497E-BB7E-57CE59EF7E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {FA608600-4A63-497E-BB7E-57CE59EF7E61}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {FA608600-4A63-497E-BB7E-57CE59EF7E61}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {FA608600-4A63-497E-BB7E-57CE59EF7E61}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {84F6D56B-37F2-4083-A150-EC4ACD9BBA98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {84F6D56B-37F2-4083-A150-EC4ACD9BBA98}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {84F6D56B-37F2-4083-A150-EC4ACD9BBA98}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {84F6D56B-37F2-4083-A150-EC4ACD9BBA98}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /AzureBlobFileSystem/AzureBlobFileSystem.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FA608600-4A63-497E-BB7E-57CE59EF7E61} 8 | Library 9 | Properties 10 | AzureBlobFileSystem 11 | AzureBlobFileSystem 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | true 36 | 37 | 38 | ../Snk/snk.snk 39 | 40 | 41 | 42 | ..\packages\Microsoft.Data.Edm.5.6.0\lib\net40\Microsoft.Data.Edm.dll 43 | 44 | 45 | ..\packages\Microsoft.Data.OData.5.6.0\lib\net40\Microsoft.Data.OData.dll 46 | 47 | 48 | ..\packages\Microsoft.Data.Services.Client.5.6.0\lib\net40\Microsoft.Data.Services.Client.dll 49 | 50 | 51 | ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll 52 | 53 | 54 | False 55 | ..\packages\WindowsAzure.Storage.3.1.0.1\lib\net40\Microsoft.WindowsAzure.Storage.dll 56 | 57 | 58 | ..\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll 59 | 60 | 61 | 62 | 63 | 64 | ..\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 92 | 93 | 94 | 95 | 102 | -------------------------------------------------------------------------------- /AzureBlobFileSystem/AzureBlobStorageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Xml.Linq; 7 | using System.Xml.XPath; 8 | using Microsoft.Win32; 9 | using Microsoft.WindowsAzure.Storage; 10 | using Microsoft.WindowsAzure.Storage.Blob; 11 | 12 | namespace AzureBlobFileSystem 13 | { 14 | public class AzureBlobStorageProvider : IStorageProvider 15 | { 16 | private readonly CloudStorageAccount _storageAccount; 17 | private Object _lock = new Object(); 18 | public CloudBlobClient BlobClient { get; private set; } 19 | public IList Containers { get; private set; } 20 | public Func ContainerFactory; 21 | 22 | public AzureBlobStorageProvider(CloudStorageAccount storageAccount) 23 | { 24 | _storageAccount = storageAccount; 25 | BlobClient = _storageAccount.CreateCloudBlobClient(); 26 | Containers = new List(); 27 | ContainerFactory = CreateContainer; 28 | } 29 | 30 | private CloudBlobContainer EnsurePathIsRelativeAndEnsureContainer(ref string path) 31 | { 32 | var containerName = path.Split('/').First(); 33 | 34 | CloudBlobContainer container; 35 | lock (_lock) 36 | { 37 | container = Containers.SingleOrDefault(c => c.Name == containerName); 38 | 39 | if (container == null) 40 | { 41 | container = BlobClient.GetContainerReference(containerName); 42 | 43 | if (!container.Exists()) 44 | { 45 | container = ContainerFactory(containerName); 46 | } 47 | 48 | Containers.Add(container); 49 | } 50 | } 51 | 52 | if (path.StartsWith("/") || path.StartsWith("http://") || path.StartsWith("https://")) 53 | throw new ArgumentException("Path must be relative"); 54 | 55 | path = string.Join("/", path.Split('/').Skip(1)); 56 | 57 | return container; 58 | } 59 | 60 | public string Combine(string path1, string path2) 61 | { 62 | if (path1 == null) 63 | { 64 | throw new ArgumentNullException("path1"); 65 | } 66 | 67 | if (path2 == null) 68 | { 69 | throw new ArgumentNullException("path2"); 70 | } 71 | 72 | if (String.IsNullOrEmpty(path2)) 73 | { 74 | return path1; 75 | } 76 | 77 | if (String.IsNullOrEmpty(path1)) 78 | { 79 | return path2; 80 | } 81 | 82 | if (path2.StartsWith("http://") || path2.StartsWith("https://")) 83 | { 84 | return path2; 85 | } 86 | 87 | var ch = path1[path1.Length - 1]; 88 | 89 | if (ch != '/') 90 | { 91 | return (path1.TrimEnd('/') + '/' + path2.TrimStart('/')); 92 | } 93 | 94 | return (path1 + path2); 95 | } 96 | 97 | public IStorageFile GetFile(string path) 98 | { 99 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 100 | 101 | container.EnsureBlobExists(path); 102 | return new AzureBlobFileStorage(container.GetBlockBlobReference(path), this); 103 | } 104 | 105 | public bool FileExists(string path) 106 | { 107 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 108 | 109 | return container.BlobExists(path); 110 | } 111 | 112 | public DateTimeOffset? DefaultSharedAccessExpiration { get; set; } 113 | 114 | public IEnumerable ListFiles(string path) 115 | { 116 | path = path ?? String.Empty; 117 | 118 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 119 | 120 | string prefix = Combine(container.Name, path); 121 | 122 | if (!prefix.EndsWith("/")) 123 | prefix += "/"; 124 | 125 | 126 | var result = BlobClient 127 | .ListBlobs(prefix) 128 | .OfType() 129 | .Where(b => !b.Name.EndsWith("/"))//filter out virtual folder files 130 | .Select(blobItem => new AzureBlobFileStorage(blobItem, this)) 131 | .ToArray(); 132 | 133 | return result; 134 | } 135 | 136 | public IEnumerable ListFolders(string path) 137 | { 138 | path = path ?? String.Empty; 139 | 140 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 141 | 142 | // return root folders 143 | if (path == String.Empty) 144 | { 145 | return container.ListBlobs() 146 | .OfType() 147 | .Select(d => new AzureBlobFolderStorage(d, this)) 148 | .ToList(); 149 | } 150 | 151 | return container.GetDirectoryReference(path) 152 | .ListBlobs() 153 | .OfType() 154 | .Select(d => new AzureBlobFolderStorage(d, this)) 155 | .ToList(); 156 | } 157 | 158 | public bool TryCreateFolder(string path) 159 | { 160 | try 161 | { 162 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 163 | 164 | if (!container.DirectoryExists(path)) 165 | { 166 | CreateFolder(path); 167 | return true; 168 | } 169 | 170 | // return false to be consistent with FileSystemProvider's implementation 171 | return false; 172 | } 173 | catch 174 | { 175 | return false; 176 | } 177 | } 178 | 179 | public void CreateFolder(string path) 180 | { 181 | string fullPath = path; 182 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 183 | 184 | container.EnsureDirectoryDoesNotExist(path); 185 | 186 | CreateFile(fullPath.Trim('/') + "/"); 187 | } 188 | 189 | public void DeleteFolder(string path) 190 | { 191 | var fullPath = path; 192 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 193 | 194 | if (path == "") 195 | { 196 | Containers.Remove(container); 197 | container.Delete(); 198 | return; 199 | } 200 | 201 | container.EnsureDirectoryExists(path); 202 | foreach (var blob in container.GetDirectoryReference(path).ListBlobs()) 203 | { 204 | if (blob is CloudBlockBlob) 205 | ((CloudBlockBlob)blob).Delete(); 206 | 207 | if (blob is CloudBlobDirectory) 208 | DeleteFolder(new AzureBlobFolderStorage((CloudBlobDirectory)blob, this).GetPath()); 209 | } 210 | } 211 | 212 | public void RenameFolder(string path, string newPath) 213 | { 214 | var fullNewPath = newPath; 215 | 216 | EnsurePathIsRelativeAndEnsureContainer(ref path); 217 | var container = EnsurePathIsRelativeAndEnsureContainer(ref newPath); 218 | 219 | if (path == "") 220 | throw new ArgumentException("Renaming root folders represented by azure containers is not currently supported", path); 221 | 222 | if (!path.EndsWith("/")) 223 | path += "/"; 224 | 225 | if (!fullNewPath.EndsWith("/")) 226 | fullNewPath += "/"; 227 | 228 | foreach (var blob in container.GetDirectoryReference(path).ListBlobs()) 229 | { 230 | if (blob is CloudBlockBlob) 231 | { 232 | var azureBlob = new AzureBlobFileStorage((CloudBlockBlob)blob, this); 233 | RenameFile(azureBlob.GetPath(), Combine(fullNewPath, azureBlob.GetName())); 234 | } 235 | 236 | if (blob is CloudBlobDirectory) 237 | { 238 | var azureFolder = new AzureBlobFolderStorage((CloudBlobDirectory)blob, this); 239 | RenameFolder(azureFolder.GetPath(), Path.Combine(fullNewPath, azureFolder.GetName())); 240 | } 241 | } 242 | } 243 | 244 | public void DeleteFile(string path) 245 | { 246 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 247 | 248 | container.EnsureBlobExists(path); 249 | var blob = container.GetBlockBlobReference(path); 250 | blob.Delete(); 251 | } 252 | 253 | public void RenameFile(string path, string newPath) 254 | { 255 | EnsurePathIsRelativeAndEnsureContainer(ref path); 256 | var container = EnsurePathIsRelativeAndEnsureContainer(ref newPath); 257 | 258 | container.EnsureBlobExists(path); 259 | container.EnsureBlobDoesNotExist(newPath); 260 | 261 | var blob = container.GetBlockBlobReference(path); 262 | var newBlob = container.GetBlockBlobReference(newPath); 263 | newBlob.StartCopyFromBlob(blob); 264 | blob.Delete(); 265 | } 266 | 267 | public IStorageFile CreateFile(string path) 268 | { 269 | var container = EnsurePathIsRelativeAndEnsureContainer(ref path); 270 | 271 | if (container.BlobExists(path)) 272 | { 273 | throw new ArgumentException("File " + path + " already exists"); 274 | } 275 | 276 | var blob = container.GetBlockBlobReference(path); 277 | var contentType = GetContentType(path); 278 | if (!String.IsNullOrWhiteSpace(contentType)) 279 | { 280 | blob.Properties.ContentType = contentType; 281 | } 282 | 283 | blob.UploadFromByteArray(new byte[0], 0, 0); 284 | return new AzureBlobFileStorage(blob, this); 285 | } 286 | 287 | public bool TrySaveStream(string path, Stream inputStream) 288 | { 289 | try 290 | { 291 | SaveStream(path, inputStream); 292 | } 293 | catch 294 | { 295 | return false; 296 | } 297 | 298 | return true; 299 | } 300 | 301 | public void SaveStream(string path, Stream inputStream) 302 | { 303 | // Create the file. 304 | // The CreateFile method will map the still relative path 305 | var file = CreateFile(path); 306 | 307 | using (var outputStream = file.OpenWrite()) 308 | { 309 | var buffer = new byte[8192]; 310 | for (; ; ) 311 | { 312 | var length = inputStream.Read(buffer, 0, buffer.Length); 313 | if (length <= 0) 314 | break; 315 | outputStream.Write(buffer, 0, length); 316 | } 317 | } 318 | } 319 | 320 | /// 321 | /// Returns the mime-type of the specified file path, looking into IIS configuration and the Registry 322 | /// 323 | private string GetContentType(string path) 324 | { 325 | string extension = Path.GetExtension(path); 326 | if (String.IsNullOrWhiteSpace(extension)) 327 | { 328 | return "application/unknown"; 329 | } 330 | 331 | try 332 | { 333 | string applicationHost = System.Environment.ExpandEnvironmentVariables(@"%windir%\system32\inetsrv\config\applicationHost.config"); 334 | //string webConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("/").FilePath; 335 | 336 | // search for custom mime types in web.config and applicationhost.config 337 | foreach (var configFile in new[] { /*webConfig,*/ applicationHost }) 338 | { 339 | if (File.Exists(configFile)) 340 | { 341 | var xdoc = XDocument.Load(configFile); 342 | var mimeMap = xdoc.XPathSelectElements("//staticContent/mimeMap[@fileExtension='" + extension + "']").FirstOrDefault(); 343 | if (mimeMap != null) 344 | { 345 | var mimeType = mimeMap.Attribute("mimeType"); 346 | if (mimeType != null) 347 | { 348 | return mimeType.Value; 349 | } 350 | } 351 | } 352 | } 353 | 354 | // search into the registry 355 | RegistryKey regKey = Registry.ClassesRoot.OpenSubKey(extension.ToLower()); 356 | if (regKey != null) 357 | { 358 | var contentType = regKey.GetValue("Content Type"); 359 | if (contentType != null) 360 | { 361 | return contentType.ToString(); 362 | } 363 | } 364 | } 365 | catch 366 | { 367 | // if an exception occured return application/unknown 368 | return "application/unknown"; 369 | } 370 | 371 | return "application/unknown"; 372 | } 373 | 374 | private CloudBlobContainer CreateContainer(string name) 375 | { 376 | var container = BlobClient.GetContainerReference(name); 377 | container.CreateIfNotExists(); 378 | return container; 379 | } 380 | 381 | private class AzureBlobFileStorage : IStorageFile 382 | { 383 | private CloudBlockBlob _blob; 384 | private readonly AzureBlobStorageProvider _azureFileSystem; 385 | private bool _hasFetchedAttributes = false; 386 | 387 | public AzureBlobFileStorage(CloudBlockBlob blob, AzureBlobStorageProvider azureFileSystem) 388 | { 389 | _blob = blob; 390 | _azureFileSystem = azureFileSystem; 391 | } 392 | 393 | private void EnsureAttributes() 394 | { 395 | if (_hasFetchedAttributes) 396 | return; 397 | 398 | _blob.FetchAttributes(); 399 | _hasFetchedAttributes = true; 400 | } 401 | 402 | public string GetPath() 403 | { 404 | return _azureFileSystem.Combine(_blob.Container.Name, _blob.Name); 405 | } 406 | 407 | public string GetName() 408 | { 409 | return Path.GetFileName(GetPath()); 410 | } 411 | 412 | public long GetSize() 413 | { 414 | EnsureAttributes(); 415 | return _blob.Properties.Length; 416 | } 417 | 418 | public DateTime GetLastUpdated() 419 | { 420 | EnsureAttributes(); 421 | return _blob.Properties.LastModified == null ? DateTime.MinValue : _blob.Properties.LastModified.Value.UtcDateTime; 422 | } 423 | 424 | public string GetFileType() 425 | { 426 | return Path.GetExtension(GetPath()); 427 | } 428 | 429 | public string GetSharedAccessPath(DateTimeOffset? expiration = null, SasPermissionFlags permissions = SasPermissionFlags.Read) 430 | { 431 | var sasToken = _blob.GetSharedAccessSignature(new SharedAccessBlobPolicy() 432 | { 433 | SharedAccessExpiryTime = expiration ?? _azureFileSystem.DefaultSharedAccessExpiration, 434 | Permissions = GetSharedAccessBlobPermissions(permissions) 435 | }); 436 | 437 | return string.Format(CultureInfo.InvariantCulture, "{0}{1}", _blob.Uri, sasToken); 438 | } 439 | 440 | public Stream OpenRead() 441 | { 442 | return _blob.OpenRead(); 443 | } 444 | 445 | public Stream OpenWrite() 446 | { 447 | return _blob.OpenWrite(); 448 | } 449 | 450 | public Stream CreateFile() 451 | { 452 | // as opposed to the File System implementation, if nothing is done on the stream 453 | // the file will be emptied, because Azure doesn't implement FileMode.Truncate 454 | _blob.DeleteIfExists(); 455 | _blob = _blob.Container.GetBlockBlobReference(_blob.Uri.ToString()); 456 | _blob.OpenWrite().Dispose(); // force file creation 457 | 458 | return OpenWrite(); 459 | } 460 | 461 | private static SharedAccessBlobPermissions GetSharedAccessBlobPermissions(SasPermissionFlags flags) 462 | { 463 | return (SharedAccessBlobPermissions)flags; 464 | } 465 | } 466 | 467 | private class AzureBlobFolderStorage : IStorageFolder 468 | { 469 | private readonly CloudBlobDirectory _blob; 470 | private readonly AzureBlobStorageProvider _azureFileSystem; 471 | 472 | public AzureBlobFolderStorage(CloudBlobDirectory blob, AzureBlobStorageProvider azureFileSystem) 473 | { 474 | _blob = blob; 475 | _azureFileSystem = azureFileSystem; 476 | } 477 | 478 | public string GetName() 479 | { 480 | return _blob.Prefix.Trim('/'); 481 | } 482 | 483 | public string GetPath() 484 | { 485 | return _blob.Uri.ToString().Replace(_blob.Container.Uri.ToString(), _blob.Container.Name).Trim('/'); 486 | } 487 | 488 | public long GetSize() 489 | { 490 | return GetDirectorySize(_blob); 491 | } 492 | 493 | public DateTime GetLastUpdated() 494 | { 495 | return DateTime.MinValue; 496 | } 497 | 498 | public IStorageFolder GetParent() 499 | { 500 | if (_blob.Parent != null) 501 | { 502 | return new AzureBlobFolderStorage(_blob.Parent, _azureFileSystem); 503 | } 504 | throw new ArgumentException("Directory " + _blob.Uri + " does not have a parent directory"); 505 | } 506 | 507 | private static long GetDirectorySize(CloudBlobDirectory directoryBlob) 508 | { 509 | long size = 0; 510 | 511 | foreach (var blobItem in directoryBlob.ListBlobs()) 512 | { 513 | if (blobItem is CloudBlockBlob) 514 | size += ((CloudBlockBlob)blobItem).Properties.Length; 515 | 516 | if (blobItem is CloudBlobDirectory) 517 | size += GetDirectorySize((CloudBlobDirectory)blobItem); 518 | } 519 | 520 | return size; 521 | } 522 | } 523 | } 524 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/CloudBlobContainerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.WindowsAzure.Storage.Blob; 4 | 5 | namespace AzureBlobFileSystem 6 | { 7 | public static class CloudBlobContainerExtensions 8 | { 9 | public static bool BlobExists(this CloudBlobContainer container, string path) 10 | { 11 | if (String.IsNullOrEmpty(path) || path.Trim() == String.Empty) 12 | throw new ArgumentException("Path can't be empty"); 13 | 14 | return container.GetBlockBlobReference(path.Replace("\\", "/")).Exists(); 15 | } 16 | 17 | public static void EnsureBlobExists(this CloudBlobContainer container, string path) 18 | { 19 | if (!BlobExists(container, path)) 20 | { 21 | throw new ArgumentException("File " + path + " does not exist"); 22 | } 23 | } 24 | 25 | public static void EnsureBlobDoesNotExist(this CloudBlobContainer container, string path) 26 | { 27 | if (BlobExists(container, path)) 28 | { 29 | throw new ArgumentException("File " + path + " already exists"); 30 | } 31 | } 32 | 33 | public static bool DirectoryExists(this CloudBlobContainer container, string path) 34 | { 35 | if (String.IsNullOrEmpty(path) || path.Trim() == String.Empty) 36 | throw new ArgumentException("Path can't be empty"); 37 | 38 | return container.GetDirectoryReference(path).ListBlobs().Count() > 0; 39 | } 40 | 41 | public static void EnsureDirectoryExists(this CloudBlobContainer container, string path) 42 | { 43 | if (!DirectoryExists(container, path)) 44 | { 45 | throw new ArgumentException("Directory " + path + " does not exist"); 46 | } 47 | } 48 | 49 | public static void EnsureDirectoryDoesNotExist(this CloudBlobContainer container, string path) 50 | { 51 | if (DirectoryExists(container, path)) 52 | { 53 | throw new ArgumentException("Directory " + path + " already exists"); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/FileSystemStorageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Web.Hosting; 6 | 7 | namespace AzureBlobFileSystem 8 | { 9 | public class FileSystemStorageProvider : IStorageProvider 10 | { 11 | private readonly string _storagePath; 12 | 13 | public FileSystemStorageProvider(string rootPath = null) 14 | { 15 | _storagePath = rootPath ?? Environment.GetEnvironmentVariable("temp"); 16 | } 17 | 18 | /// 19 | /// Maps a relative path into the storage path. 20 | /// 21 | /// The relative path to be mapped. 22 | /// The relative path combined with the storage path. 23 | private string MapStorage(string path) 24 | { 25 | string mappedPath = string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, path); 26 | return PathValidation.ValidatePath(_storagePath, mappedPath); 27 | } 28 | 29 | private static string Fix(string path) 30 | { 31 | return string.IsNullOrEmpty(path) 32 | ? "" 33 | : Path.DirectorySeparatorChar != '/' 34 | ? path.Replace('/', Path.DirectorySeparatorChar) 35 | : path; 36 | } 37 | 38 | #region Implementation of IStorageProvider 39 | 40 | 41 | /// 42 | /// Retrieves a file within the storage provider. 43 | /// 44 | /// The relative path to the file within the storage provider. 45 | /// The file. 46 | /// If the file is not found. 47 | public IStorageFile GetFile(string path) 48 | { 49 | FileInfo fileInfo = new FileInfo(MapStorage(path)); 50 | if (!fileInfo.Exists) 51 | { 52 | throw new ArgumentException(string.Format("File {0} does not exist", path)); 53 | } 54 | 55 | return new FileSystemStorageFile(Fix(path), fileInfo); 56 | } 57 | 58 | /// 59 | /// Lists the files within a storage provider's path. 60 | /// 61 | /// The relative path to the folder which files to list. 62 | /// The list of files in the folder. 63 | public IEnumerable ListFiles(string path) 64 | { 65 | var directoryInfo = new DirectoryInfo(MapStorage(path)); 66 | if (!directoryInfo.Exists) 67 | { 68 | return Enumerable.Empty(); 69 | } 70 | 71 | return directoryInfo 72 | .GetFiles() 73 | .Where(fi => !IsHidden(fi)) 74 | .Select(fi => new FileSystemStorageFile(Path.Combine(Fix(path), fi.Name), fi)) 75 | .ToList(); 76 | } 77 | 78 | /// 79 | /// Lists the folders within a storage provider's path. 80 | /// 81 | /// The relative path to the folder which folders to list. 82 | /// The list of folders in the folder. 83 | public IEnumerable ListFolders(string path) 84 | { 85 | var directoryInfo = new DirectoryInfo(MapStorage(path)); 86 | if (!directoryInfo.Exists) 87 | { 88 | try 89 | { 90 | directoryInfo.Create(); 91 | } 92 | catch (Exception ex) 93 | { 94 | throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); 95 | } 96 | } 97 | 98 | return directoryInfo 99 | .GetDirectories() 100 | .Where(di => !IsHidden(di)) 101 | .Select(di => new FileSystemStorageFolder(Path.Combine(Fix(path), di.Name), di)) 102 | .ToList(); 103 | } 104 | 105 | /// 106 | /// Tries to create a folder in the storage provider. 107 | /// 108 | /// The relative path to the folder to be created. 109 | /// True if success; False otherwise. 110 | public bool TryCreateFolder(string path) 111 | { 112 | try 113 | { 114 | // prevent unnecessary exception 115 | DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path)); 116 | if (directoryInfo.Exists) 117 | { 118 | return false; 119 | } 120 | 121 | CreateFolder(path); 122 | } 123 | catch 124 | { 125 | return false; 126 | } 127 | 128 | return true; 129 | } 130 | 131 | /// 132 | /// Creates a folder in the storage provider. 133 | /// 134 | /// The relative path to the folder to be created. 135 | /// If the folder already exists. 136 | public void CreateFolder(string path) 137 | { 138 | DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path)); 139 | if (directoryInfo.Exists) 140 | { 141 | throw new ArgumentException(string.Format("Directory {0} already exists", path)); 142 | } 143 | 144 | Directory.CreateDirectory(directoryInfo.FullName); 145 | } 146 | 147 | /// 148 | /// Deletes a folder in the storage provider. 149 | /// 150 | /// The relative path to the folder to be deleted. 151 | /// If the folder doesn't exist. 152 | public void DeleteFolder(string path) 153 | { 154 | DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path)); 155 | if (!directoryInfo.Exists) 156 | { 157 | throw new ArgumentException(string.Format("Directory {0} does not exist", path)); 158 | } 159 | 160 | directoryInfo.Delete(true); 161 | } 162 | 163 | /// 164 | /// Renames a folder in the storage provider. 165 | /// 166 | /// The relative path to the folder to be renamed. 167 | /// The relative path to the new folder. 168 | public void RenameFolder(string oldPath, string newPath) 169 | { 170 | DirectoryInfo sourceDirectory = new DirectoryInfo(MapStorage(oldPath)); 171 | if (!sourceDirectory.Exists) 172 | { 173 | throw new ArgumentException(string.Format("Directory {0} does not exist", oldPath)); 174 | } 175 | 176 | DirectoryInfo targetDirectory = new DirectoryInfo(MapStorage(newPath)); 177 | if (targetDirectory.Exists) 178 | { 179 | throw new ArgumentException(string.Format("Directory {0} already exists", newPath)); 180 | } 181 | 182 | Directory.Move(sourceDirectory.FullName, targetDirectory.FullName); 183 | } 184 | 185 | /// 186 | /// Deletes a file in the storage provider. 187 | /// 188 | /// The relative path to the file to be deleted. 189 | /// If the file doesn't exist. 190 | public void DeleteFile(string path) 191 | { 192 | FileInfo fileInfo = new FileInfo(MapStorage(path)); 193 | if (!fileInfo.Exists) 194 | { 195 | throw new ArgumentException(string.Format("File {0} does not exist", path)); 196 | } 197 | 198 | fileInfo.Delete(); 199 | } 200 | 201 | /// 202 | /// Renames a file in the storage provider. 203 | /// 204 | /// The relative path to the file to be renamed. 205 | /// The relative path to the new file. 206 | public void RenameFile(string oldPath, string newPath) 207 | { 208 | FileInfo sourceFileInfo = new FileInfo(MapStorage(oldPath)); 209 | if (!sourceFileInfo.Exists) 210 | { 211 | throw new ArgumentException(string.Format("File {0} does not exist", oldPath)); 212 | } 213 | 214 | FileInfo targetFileInfo = new FileInfo(MapStorage(newPath)); 215 | if (targetFileInfo.Exists) 216 | { 217 | throw new ArgumentException(string.Format("File {0} already exists", newPath)); 218 | } 219 | 220 | File.Move(sourceFileInfo.FullName, targetFileInfo.FullName); 221 | } 222 | 223 | /// 224 | /// Creates a file in the storage provider. 225 | /// 226 | /// The relative path to the file to be created. 227 | /// If the file already exists. 228 | /// The created file. 229 | public IStorageFile CreateFile(string path) 230 | { 231 | FileInfo fileInfo = new FileInfo(MapStorage(path)); 232 | if (fileInfo.Exists) 233 | { 234 | throw new ArgumentException(string.Format("File {0} already exists", fileInfo.Name)); 235 | } 236 | 237 | // ensure the directory exists 238 | var dirName = Path.GetDirectoryName(fileInfo.FullName); 239 | if (!Directory.Exists(dirName)) 240 | { 241 | Directory.CreateDirectory(dirName); 242 | } 243 | File.WriteAllBytes(fileInfo.FullName, new byte[0]); 244 | 245 | return new FileSystemStorageFile(Fix(path), fileInfo); 246 | } 247 | 248 | /// 249 | /// Tries to save a stream in the storage provider. 250 | /// 251 | /// The relative path to the file to be created. 252 | /// The stream to be saved. 253 | /// True if success; False otherwise. 254 | public bool TrySaveStream(string path, Stream inputStream) 255 | { 256 | try 257 | { 258 | SaveStream(path, inputStream); 259 | } 260 | catch 261 | { 262 | return false; 263 | } 264 | 265 | return true; 266 | } 267 | 268 | /// 269 | /// Saves a stream in the storage provider. 270 | /// 271 | /// The relative path to the file to be created. 272 | /// The stream to be saved. 273 | /// If the stream can't be saved due to access permissions. 274 | public void SaveStream(string path, Stream inputStream) 275 | { 276 | // Create the file. 277 | // The CreateFile method will map the still relative path 278 | var file = CreateFile(path); 279 | 280 | var outputStream = file.OpenWrite(); 281 | var buffer = new byte[8192]; 282 | for (; ; ) 283 | { 284 | 285 | var length = inputStream.Read(buffer, 0, buffer.Length); 286 | if (length <= 0) 287 | break; 288 | outputStream.Write(buffer, 0, length); 289 | } 290 | outputStream.Dispose(); 291 | } 292 | 293 | /// 294 | /// Combines to paths. 295 | /// 296 | /// The parent path. 297 | /// The child path. 298 | /// The combined path. 299 | public string Combine(string path1, string path2) 300 | { 301 | return Path.Combine(path1, path2); 302 | } 303 | 304 | public bool FileExists(string path) 305 | { 306 | return new FileInfo(MapStorage(path)).Exists; 307 | } 308 | 309 | public DateTimeOffset? DefaultSharedAccessExpiration { get; set; } 310 | 311 | public IStorageFile CreateOrReplaceFile(string path) 312 | { 313 | if (FileExists(path)) 314 | DeleteFile(path); 315 | 316 | return CreateFile(path); 317 | } 318 | 319 | private static bool IsHidden(FileSystemInfo di) 320 | { 321 | return (di.Attributes & FileAttributes.Hidden) != 0; 322 | } 323 | 324 | #endregion 325 | 326 | private class FileSystemStorageFile : IStorageFile 327 | { 328 | private readonly string _path; 329 | private readonly FileInfo _fileInfo; 330 | 331 | public FileSystemStorageFile(string path, FileInfo fileInfo) 332 | { 333 | _path = path; 334 | _fileInfo = fileInfo; 335 | } 336 | 337 | #region Implementation of IStorageFile 338 | 339 | public string GetPath() 340 | { 341 | return _path; 342 | } 343 | 344 | public string GetName() 345 | { 346 | return _fileInfo.Name; 347 | } 348 | 349 | public long GetSize() 350 | { 351 | return _fileInfo.Length; 352 | } 353 | 354 | public DateTime GetLastUpdated() 355 | { 356 | return _fileInfo.LastWriteTime; 357 | } 358 | 359 | public string GetFileType() 360 | { 361 | return _fileInfo.Extension; 362 | } 363 | 364 | public string GetSharedAccessPath(DateTimeOffset? expiration = null, SasPermissionFlags permissions = SasPermissionFlags.Read) 365 | { 366 | return _path; 367 | } 368 | 369 | public Stream OpenRead() 370 | { 371 | return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.Read); 372 | } 373 | 374 | public Stream OpenWrite() 375 | { 376 | return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.ReadWrite); 377 | } 378 | 379 | public Stream CreateFile() 380 | { 381 | return new FileStream(_fileInfo.FullName, FileMode.Truncate, FileAccess.ReadWrite); 382 | } 383 | 384 | #endregion 385 | } 386 | 387 | private class FileSystemStorageFolder : IStorageFolder 388 | { 389 | private readonly string _path; 390 | private readonly DirectoryInfo _directoryInfo; 391 | 392 | public FileSystemStorageFolder(string path, DirectoryInfo directoryInfo) 393 | { 394 | _path = path; 395 | _directoryInfo = directoryInfo; 396 | } 397 | 398 | #region Implementation of IStorageFolder 399 | 400 | public string GetPath() 401 | { 402 | return _path; 403 | } 404 | 405 | public string GetName() 406 | { 407 | return _directoryInfo.Name; 408 | } 409 | 410 | public DateTime GetLastUpdated() 411 | { 412 | return _directoryInfo.LastWriteTime; 413 | } 414 | 415 | public long GetSize() 416 | { 417 | return GetDirectorySize(_directoryInfo); 418 | } 419 | 420 | public IStorageFolder GetParent() 421 | { 422 | if (_directoryInfo.Parent != null) 423 | { 424 | return new FileSystemStorageFolder(Path.GetDirectoryName(_path), _directoryInfo.Parent); 425 | } 426 | throw new ArgumentException(string.Format("Directory {0} does not have a parent directory", _directoryInfo.Name)); 427 | } 428 | 429 | #endregion 430 | 431 | private static long GetDirectorySize(DirectoryInfo directoryInfo) 432 | { 433 | long size = 0; 434 | 435 | FileInfo[] fileInfos = directoryInfo.GetFiles(); 436 | foreach (FileInfo fileInfo in fileInfos) 437 | { 438 | if (!IsHidden(fileInfo)) 439 | { 440 | size += fileInfo.Length; 441 | } 442 | } 443 | DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories(); 444 | foreach (DirectoryInfo dInfo in directoryInfos) 445 | { 446 | if (!IsHidden(dInfo)) 447 | { 448 | size += GetDirectorySize(dInfo); 449 | } 450 | } 451 | 452 | return size; 453 | } 454 | } 455 | } 456 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/IStorageFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AzureBlobFileSystem 5 | { 6 | public interface IStorageFile 7 | { 8 | string GetPath(); 9 | string GetName(); 10 | long GetSize(); 11 | DateTime GetLastUpdated(); 12 | string GetFileType(); 13 | string GetSharedAccessPath(DateTimeOffset? expiration = null, SasPermissionFlags permissions = SasPermissionFlags.Read); 14 | 15 | /// 16 | /// Creates a stream for reading from the file. 17 | /// 18 | Stream OpenRead(); 19 | 20 | /// 21 | /// Creates a stream for writing to the file. 22 | /// 23 | Stream OpenWrite(); 24 | 25 | /// 26 | /// Creates a stream for writing to the file, and truncates the existing content. 27 | /// 28 | Stream CreateFile(); 29 | } 30 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/IStorageFolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AzureBlobFileSystem 4 | { 5 | public interface IStorageFolder 6 | { 7 | string GetPath(); 8 | string GetName(); 9 | long GetSize(); 10 | DateTime GetLastUpdated(); 11 | IStorageFolder GetParent(); 12 | } 13 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/IStorageProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace AzureBlobFileSystem 6 | { 7 | public interface IStorageProvider 8 | { 9 | /// 10 | /// Retrieves a file within the storage provider. 11 | /// 12 | /// The relative path to the file within the storage provider. 13 | /// The file. 14 | /// If the file is not found. 15 | IStorageFile GetFile(string path); 16 | 17 | /// 18 | /// Lists the files within a storage provider's path. 19 | /// 20 | /// The relative path to the folder which files to list. 21 | /// The list of files in the folder. 22 | IEnumerable ListFiles(string path); 23 | 24 | /// 25 | /// Lists the folders within a storage provider's path. 26 | /// 27 | /// The relative path to the folder which folders to list. 28 | /// The list of folders in the folder. 29 | IEnumerable ListFolders(string path); 30 | 31 | /// 32 | /// Tries to create a folder in the storage provider. 33 | /// 34 | /// The relative path to the folder to be created. 35 | /// True if success; False otherwise. 36 | bool TryCreateFolder(string path); 37 | 38 | /// 39 | /// Creates a folder in the storage provider. 40 | /// 41 | /// The relative path to the folder to be created. 42 | /// If the folder already exists. 43 | void CreateFolder(string path); 44 | 45 | /// 46 | /// Deletes a folder in the storage provider. 47 | /// 48 | /// The relative path to the folder to be deleted. 49 | /// If the folder doesn't exist. 50 | void DeleteFolder(string path); 51 | 52 | /// 53 | /// Renames a folder in the storage provider. 54 | /// 55 | /// The relative path to the folder to be renamed. 56 | /// The relative path to the new folder. 57 | void RenameFolder(string oldPath, string newPath); 58 | 59 | /// 60 | /// Deletes a file in the storage provider. 61 | /// 62 | /// The relative path to the file to be deleted. 63 | /// If the file doesn't exist. 64 | void DeleteFile(string path); 65 | 66 | /// 67 | /// Renames a file in the storage provider. 68 | /// 69 | /// The relative path to the file to be renamed. 70 | /// The relative path to the new file. 71 | void RenameFile(string oldPath, string newPath); 72 | 73 | /// 74 | /// Creates a file in the storage provider. 75 | /// 76 | /// The relative path to the file to be created. 77 | /// If the file already exists. 78 | /// The created file. 79 | IStorageFile CreateFile(string path); 80 | 81 | /// 82 | /// Tries to save a stream in the storage provider. 83 | /// 84 | /// The relative path to the file to be created. 85 | /// The stream to be saved. 86 | /// True if success; False otherwise. 87 | bool TrySaveStream(string path, Stream inputStream); 88 | 89 | /// 90 | /// Saves a stream in the storage provider. 91 | /// 92 | /// The relative path to the file to be created. 93 | /// The stream to be saved. 94 | /// If the stream can't be saved due to access permissions. 95 | void SaveStream(string path, Stream inputStream); 96 | 97 | /// 98 | /// Combines to paths. 99 | /// 100 | /// The parent path. 101 | /// The child path. 102 | /// The combined path. 103 | string Combine(string path1, string path2); 104 | 105 | bool FileExists(string path); 106 | 107 | 108 | /// 109 | /// Gets or sets the default shared access expiration date. 110 | /// 111 | /// 112 | /// The default shared access expiration date. 113 | /// 114 | DateTimeOffset? DefaultSharedAccessExpiration { get; set; } 115 | } 116 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/PathValidation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AzureBlobFileSystem 5 | { 6 | /// 7 | /// Provides methods to validate paths. 8 | /// 9 | public static class PathValidation 10 | { 11 | /// 12 | /// Determines if a path lies within the base path boundaries. 13 | /// If not, an exception is thrown. 14 | /// 15 | /// The base path which boundaries are not to be transposed. 16 | /// The path to determine. 17 | /// The mapped path if valid. 18 | /// If the path is invalid. 19 | public static string ValidatePath(string basePath, string mappedPath) 20 | { 21 | bool valid = false; 22 | 23 | try 24 | { 25 | // Check that we are indeed within the storage directory boundaries 26 | valid = Path.GetFullPath(mappedPath).StartsWith(Path.GetFullPath(basePath), StringComparison.OrdinalIgnoreCase); 27 | } 28 | catch 29 | { 30 | // Make sure that if invalid for medium trust we give a proper exception 31 | valid = false; 32 | } 33 | 34 | if (!valid) 35 | { 36 | throw new ArgumentException("Invalid path"); 37 | } 38 | 39 | return mappedPath; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AzureBlobFileSystem")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AzureBlobFileSystem")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2e2088ac-4423-4348-a70d-35ac1ef57b59")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.0.7.0")] 36 | [assembly: AssemblyFileVersion("0.0.7.0")] 37 | -------------------------------------------------------------------------------- /AzureBlobFileSystem/SasPermissionFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.WindowsAzure.Storage.Blob; 3 | 4 | namespace AzureBlobFileSystem 5 | { 6 | /// 7 | /// Specifies the set of possible permissions for a shared access policy. 8 | /// This mirrors the flags available in the Azure Storage Client Library 9 | /// 10 | [Flags] 11 | public enum SasPermissionFlags 12 | { 13 | None = 0, 14 | Read = 1, 15 | Write = 2, 16 | Delete = 4, 17 | List = 8, 18 | } 19 | } -------------------------------------------------------------------------------- /AzureBlobFileSystem/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BlobFileSystem.Azure.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BlobFileSystem.Azure 5 | File System In Azure Blob Storage 6 | Jan Blaha 7 | 0.0.0.0 8 | Jan Blaha 9 | This package provides easy file system abstraction and two implementations storing through azure blob storage or local disk. These implementations are interchangeable and you can easily switch between them in different environments. 10 | https://github.com/pofider/AzureBlobFileSystem 11 | http://www.opensource.org/licenses/mit-license.php 12 | Copyright 2014 Jan Blaha 13 | en-us 14 | azure blob storage filesystem directory abstraction 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jan Blaha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File System In Azure Blob Storage 2 | 3 | Azure Blob Storage client provides a way how to structure blobs in storage using folders and it's hierarchy. This is handy but sometimes you may find it too complicated or you need an abstraction over it so you can use normal file system on your local machine or specific installation instead. 4 | 5 | This package provides easy file system abstraction and two implementations storing through azure blob storage or local disk. These implementations are interchangeable and you can easily switch between them in different environments. 6 | 7 | *Source codes of this library were partially taken from Orchard CMS project. The credit goes to Orchard team.* 8 | 9 | ## Installation 10 | 11 | Clone this repository or use nuget package... 12 | ``` 13 | PM> Install-Package BlobFileSystem.Azure 14 | ``` 15 | 16 | ## How it works 17 | Azure Blob Storage does not have support for directories directly. However it can partialy work with directories on the convention based level. This is using BlobFileSystem.Azure. It is looking at blob name with slashes (`folder/folder/file.png`) as it would be a file in nested directory. Renaming folder means for it then rename all blobs with specific name. 18 | 19 | **BlobFileSystem.Azure stores files in cloud container based on the first root folder**. This means that file `folder1/file1.txt` will be stored in the cloud container with name `folder1` and file `folder2/folder3/file2.png` will be stored in the container `folder2` under path `fodler3/file2.png`. 20 | 21 | ## Usage 22 | 23 | ### Instantiate storage 24 | ```c# 25 | //storage using azure blob client 26 | IStorageProvider azureStorage = new AzureBlobStorageProvider(CloudStorageAccount.DevelopmentStorageAccount); 27 | 28 | //storage using fileSystem 29 | IStorageProvider fileSystemStorage = new FileSystemStorageProvider(); 30 | ``` 31 | 32 | ### Operations 33 | ```c# 34 | //create folder 35 | storage.CreateFolder("folder"); 36 | 37 | //delete folder 38 | storage.DeleteFolder("folder"); 39 | 40 | //create and write file 41 | using (var writer = new StreamWriter(storage.CreateFile(storage.Combine("folder", "file.txt")).OpenWrite())) 42 | { 43 | } 44 | 45 | //read file 46 | using (var reader = new StreamReader(storage.GetFile(storage.Combine("folder", "file.txt")).OpenRead())) 47 | { 48 | } 49 | 50 | //delete file 51 | if (storage.FileExists(storage.Combine("folder", "file.txt"))) 52 | storage.DeleteFile(storage.Combine("folder", "file.txt")); 53 | 54 | //list files 55 | storage.ListFiles("folder"); 56 | 57 | //list folders 58 | storage.ListFolders(""); 59 | 60 | //rename file 61 | storage.RenameFile(storage.Combine("folder", "file.txt"), storage.Combine("folder", "file2.txt")); 62 | 63 | //rename folder 64 | storage.CreateFolder(storage.Combine("folder", "child")); 65 | storage.RenameFile(storage.Combine("folder", "child"), storage.Combine("folder", "child2")); 66 | ``` 67 | 68 | ## Limitations 69 | Storage support only limited subset of the original azure storage blob client. It also does not support page blobs currently. 70 | 71 | ## Contributions 72 | Contributions are welcome and I do accept pull requests. Just make shure the tests are running. 73 | 74 | To run all the tests, you need to have azure storage emulator running. To start it you can use following command: 75 | ``` 76 | "C:\Program Files\Microsoft SDKs\Windows Azure\Emulator\csrun.exe" /devstore:start 77 | ``` 78 | 79 | ## License 80 | The MIT License (MIT) 81 | 82 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 83 | 84 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 85 | -------------------------------------------------------------------------------- /Snk/snk.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pofider/AzureBlobFileSystem/873e285b3891519ecbf4cfe527f9eaa72fe08174/Snk/snk.snk -------------------------------------------------------------------------------- /nugetpack.cmd: -------------------------------------------------------------------------------- 1 | call .\.nuget\nuget pack -sym BlobFileSystem.Azure.nuspec -Symbols -Version %1 -OutputDirectory Packages --------------------------------------------------------------------------------