├── .github └── stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── art └── FirebaseLogo.128x128.png ├── samples └── SimpleConsole │ ├── Firebase.Storage.SimpleConsole.sln │ └── SimpleConsole │ ├── App.config │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── SimpleConsole.csproj │ └── packages.config ├── src ├── Firebase.Storage.sln └── Firebase.Storage │ ├── Firebase.Storage.csproj │ ├── FirebaseMetaData.cs │ ├── FirebaseStorage.cs │ ├── FirebaseStorageException.cs │ ├── FirebaseStorageOptions.cs │ ├── FirebaseStorageProgress.cs │ ├── FirebaseStorageReference.cs │ ├── FirebaseStorageTask.cs │ └── HttpClientFactory.cs └── test ├── Firebase.Storage.IntegrationTests ├── AuthenticationExtensions.cs ├── Firebase.Storage.IntegrationTests.csproj ├── Models │ ├── FirebaseAuthenticationModel.cs │ └── Secrets.cs ├── UploadWithMimeTypeTest.cs ├── secrets.json.example └── test.jpg └── Firebase.Storage.Tests.sln /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - enhancement 10 | - up-for-grabs 11 | # Label to use when marking an issue as stale 12 | staleLabel: stale 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: Closing the issue due to inactivity. Feel free to re-open 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # avoid test secrets file 5 | test/*/secrets.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | build/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | 113 | # MightyMoose 114 | *.mm.* 115 | AutoTest.Net/ 116 | 117 | # Web workbench (sass) 118 | .sass-cache/ 119 | 120 | # Installshield output folder 121 | [Ee]xpress/ 122 | 123 | # DocProject is a documentation generator add-in 124 | DocProject/buildhelp/ 125 | DocProject/Help/*.HxT 126 | DocProject/Help/*.HxC 127 | DocProject/Help/*.hhc 128 | DocProject/Help/*.hhk 129 | DocProject/Help/*.hhp 130 | DocProject/Help/Html2 131 | DocProject/Help/html 132 | 133 | # Click-Once directory 134 | publish/ 135 | 136 | # Publish Web Output 137 | *.[Pp]ublish.xml 138 | *.azurePubxml 139 | ## TODO: Comment the next line if you want to checkin your 140 | ## web deploy settings but do note that will include unencrypted 141 | ## passwords 142 | #*.pubxml 143 | 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Windows Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Windows Store app package directory 160 | AppPackages/ 161 | 162 | # Visual Studio cache files 163 | # files ending in .cache can be ignored 164 | *.[Cc]ache 165 | # but keep track of directories ending in .cache 166 | !*.[Cc]ache/ 167 | 168 | # Others 169 | ClientBin/ 170 | [Ss]tyle[Cc]op.* 171 | ~$* 172 | *~ 173 | *.dbmdl 174 | *.dbproj.schemaview 175 | *.pfx 176 | *.publishsettings 177 | node_modules/ 178 | orleans.codegen.cs 179 | 180 | # RIA/Silverlight projects 181 | Generated_Code/ 182 | 183 | # Backup & report files from converting an old project file 184 | # to a newer Visual Studio version. Backup files are not needed, 185 | # because we have git ;-) 186 | _UpgradeReport_Files/ 187 | Backup*/ 188 | UpgradeLog*.XML 189 | UpgradeLog*.htm 190 | 191 | # SQL Server files 192 | *.mdf 193 | *.ldf 194 | 195 | # Business Intelligence projects 196 | *.rdl.data 197 | *.bim.layout 198 | *.bim_*.settings 199 | 200 | # Microsoft Fakes 201 | FakesAssemblies/ 202 | 203 | # Node.js Tools for Visual Studio 204 | .ntvs_analysis.dat 205 | 206 | # Visual Studio 6 build log 207 | *.plg 208 | 209 | # Visual Studio 6 workspace options file 210 | *.opt 211 | 212 | # LightSwitch generated files 213 | GeneratedArtifacts/ 214 | _Pvt_Extensions/ 215 | ModelManifest.xml 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Step Up Labs 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FirebaseStorage.net 2 | [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/o8hpwxrgfyhu527b?svg=true)](https://ci.appveyor.com/project/bezysoftware/firebase-storage-dotnet) 3 | 4 | Easily upload files and other content to Firebase Storage. More info in a [blog post](https://medium.com/step-up-labs/firebase-storage-c-library-d1656cc8b3c3) 5 | 6 | For Authenticating with Firebase checkout the [Firebase Authentication library](https://github.com/step-up-labs/firebase-authentication-dotnet) and related [blog post](https://medium.com/step-up-labs/firebase-authentication-c-library-8e5e1c30acc2) 7 | 8 | ## Installation 9 | ```csharp 10 | // Install release version 11 | Install-Package FirebaseStorage.net -pre 12 | ``` 13 | 14 | ## Supported frameworks 15 | .NET Standard 1.1 - see https://github.com/dotnet/standard/blob/master/docs/versions.md for compatibility matrix 16 | 17 | ## Usage 18 | 19 | ```csharp 20 | // Get any Stream - it can be FileStream, MemoryStream or any other type of Stream 21 | var stream = File.Open(@"C:\Users\you\file.png", FileMode.Open); 22 | 23 | //authentication 24 | var auth = new FirebaseAuthProvider(new FirebaseConfig("api_key")); 25 | var a = await auth.SignInWithEmailAndPasswordAsync("email", "password"); 26 | 27 | // Constructr FirebaseStorage, path to where you want to upload the file and Put it there 28 | var task = new FirebaseStorage( 29 | "your-bucket.appspot.com" 30 | new FirebaseStorageOptions 31 | { 32 | AuthTokenAsyncFactory = () => Task.FromResult(a.FirebaseToken), 33 | ThrowOnCancel = true, 34 | }) 35 | .Child("data") 36 | .Child("random") 37 | .Child("file.png") 38 | .PutAsync(stream); 39 | 40 | // Track progress of the upload 41 | task.Progress.ProgressChanged += (s, e) => Console.WriteLine($"Progress: {e.Percentage} %"); 42 | 43 | // await the task to wait until upload completes and get the download url 44 | var downloadUrl = await task; 45 | ``` 46 | -------------------------------------------------------------------------------- /art/FirebaseLogo.128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/step-up-labs/firebase-storage-dotnet/69dac6f45673d82db139fb6b0bd7b1256db164df/art/FirebaseLogo.128x128.png -------------------------------------------------------------------------------- /samples/SimpleConsole/Firebase.Storage.SimpleConsole.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 14 3 | VisualStudioVersion = 14.0.25420.1 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleConsole", "SimpleConsole\SimpleConsole.csproj", "{EB69C2A3-7824-49A5-9FFC-1A28A9B7AE55}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Firebase.Storage", "..\..\src\Firebase.Storage\Firebase.Storage.csproj", "{FFA7D737-9AAA-41AF-8264-0F109C906A64}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {EB69C2A3-7824-49A5-9FFC-1A28A9B7AE55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {EB69C2A3-7824-49A5-9FFC-1A28A9B7AE55}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {EB69C2A3-7824-49A5-9FFC-1A28A9B7AE55}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {EB69C2A3-7824-49A5-9FFC-1A28A9B7AE55}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /samples/SimpleConsole/SimpleConsole/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /samples/SimpleConsole/SimpleConsole/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage.SimpleConsole 2 | { 3 | using Firebase.Auth; 4 | 5 | using System; 6 | using System.IO; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | internal class Program 12 | { 13 | private static string ApiKey = "YOUR_API_KEY"; 14 | private static string Bucket = "your-bucket.appspot.com"; 15 | private static string AuthEmail = "your@email.com"; 16 | private static string AuthPassword = "yourpasword"; 17 | 18 | private static void Main(string[] args) 19 | { 20 | Run().Wait(); 21 | } 22 | 23 | private static async Task Run() 24 | { 25 | // FirebaseStorage.Put method accepts any type of stream. 26 | var stream = new MemoryStream(Encoding.ASCII.GetBytes("Hello world!")); 27 | //var stream = File.Open(@"C:\someFile.png", FileMode.Open); 28 | 29 | // of course you can login using other method, not just email+password 30 | var auth = new FirebaseAuthProvider(new FirebaseConfig(ApiKey)); 31 | var a = await auth.SignInWithEmailAndPasswordAsync(AuthEmail, AuthPassword); 32 | 33 | // you can use CancellationTokenSource to cancel the upload midway 34 | var cancellation = new CancellationTokenSource(); 35 | 36 | var task = new FirebaseStorage( 37 | Bucket, 38 | new FirebaseStorageOptions 39 | { 40 | AuthTokenAsyncFactory = () => Task.FromResult(a.FirebaseToken), 41 | ThrowOnCancel = true // when you cancel the upload, exception is thrown. By default no exception is thrown 42 | }) 43 | .Child("receipts") 44 | .Child("test") 45 | .Child("someFile.png") 46 | .PutAsync(stream, cancellation.Token); 47 | 48 | task.Progress.ProgressChanged += (s, e) => Console.WriteLine($"Progress: {e.Percentage} %"); 49 | 50 | // cancel the upload 51 | // cancellation.Cancel(); 52 | 53 | try 54 | { 55 | // error during upload will be thrown when you await the task 56 | Console.WriteLine("Download link:\n" + await task); 57 | } 58 | catch (Exception ex) 59 | { 60 | Console.WriteLine("Exception was thrown: {0}", ex); 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /samples/SimpleConsole/SimpleConsole/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("Firebase.Storage.SimpleConsole")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Firebase.Storage.SimpleConsole")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 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("eb69c2a3-7824-49a5-9ffc-1a28a9b7ae55")] 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 | -------------------------------------------------------------------------------- /samples/SimpleConsole/SimpleConsole/SimpleConsole.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EB69C2A3-7824-49A5-9FFC-1A28A9B7AE55} 8 | Exe 9 | Properties 10 | Firebase.Storage.SimpleConsole 11 | Firebase.Storage.SimpleConsole 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\FirebaseAuthentication.net.2.1.3\lib\portable45-net45+win8+wpa81\Firebase.Auth.dll 38 | True 39 | 40 | 41 | ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll 42 | True 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Designer 61 | 62 | 63 | 64 | 65 | {ffa7d737-9aaa-41af-8264-0f109c906a64} 66 | Firebase.Storage 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /samples/SimpleConsole/SimpleConsole/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Firebase.Storage.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Firebase.Storage", "Firebase.Storage\Firebase.Storage.csproj", "{FFA7D737-9AAA-41AF-8264-0F109C906A64}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FFA7D737-9AAA-41AF-8264-0F109C906A64}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Firebase.Storage/Firebase.Storage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.1 5 | FirebaseStorage.net 6 | 1.0.3 7 | Step Up Labs, Inc. 8 | Easily upload files and other content to Firebase Storage. 9 | false 10 | https://github.com/step-up-labs/firebase-storage-dotnet/raw/master/art/FirebaseLogo.128x128.png 11 | https://github.com/step-up-labs/firebase-storage-dotnet 12 | https://github.com/step-up-labs/firebase-storage-dotnet/blob/master/LICENSE 13 | True 14 | True 15 | Step Up Labs, Inc. 2017 16 | Firebase Storage Google 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseMetaData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Firebase.Storage 7 | { 8 | /// 9 | /// Full list of meta data available here: https://firebase.google.com/docs/storage/web/file-metadata 10 | /// 11 | public class FirebaseMetaData 12 | { 13 | [JsonProperty("bucket")] 14 | public string Bucket { get; set; } 15 | 16 | [JsonProperty("generation")] 17 | public string Generation { get; set; } 18 | 19 | [JsonProperty("metageneration")] 20 | public string MetaGeneration { get; set; } 21 | 22 | [JsonProperty("fullPath")] 23 | public string FullPath { get; set; } 24 | 25 | [JsonProperty("name")] 26 | public string Name { get; set; } 27 | 28 | [JsonProperty("size")] 29 | public long Size { get; set; } 30 | 31 | [JsonProperty("contentType")] 32 | public string ContentType { get; set; } 33 | 34 | [JsonProperty("timeCreated")] 35 | public DateTime TimeCreated { get; set; } 36 | 37 | [JsonProperty("updated")] 38 | public DateTime Updated { get; set; } 39 | 40 | [JsonProperty("md5Hash")] 41 | public string Md5Hash { get; set; } 42 | 43 | [JsonProperty("contentEncoding")] 44 | public string ContentEncoding { get; set; } 45 | 46 | [JsonProperty("contentDisposition")] 47 | public string ContentDisposition { get; set; } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseStorage.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | /// 4 | /// Entry class into firebase storage. 5 | /// 6 | public class FirebaseStorage 7 | { 8 | /// 9 | /// Creates an instance of class. 10 | /// 11 | /// Google storage bucket. E.g. 'your-bucket.appspot.com'. 12 | /// Optional settings. 13 | public FirebaseStorage(string storageBucket, FirebaseStorageOptions options = null) 14 | { 15 | this.StorageBucket = storageBucket; 16 | this.Options = options ?? new FirebaseStorageOptions(); 17 | } 18 | 19 | /// 20 | /// Gets the . 21 | /// 22 | public FirebaseStorageOptions Options 23 | { 24 | get; 25 | private set; 26 | } 27 | 28 | /// 29 | /// Gets the google storage bucket. 30 | /// 31 | public string StorageBucket 32 | { 33 | get; 34 | private set; 35 | } 36 | 37 | /// 38 | /// Constructs firebase path to the file. 39 | /// 40 | /// Name of the entity. This can be folder or a file name or full path. 41 | /// 42 | /// storage 43 | /// .Child("some") 44 | /// .Child("path") 45 | /// .Child("to/file.png"); 46 | /// 47 | /// for fluid syntax. 48 | public FirebaseStorageReference Child(string childRoot) 49 | { 50 | return new FirebaseStorageReference(this, childRoot); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseStorageException.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | using System; 4 | 5 | public class FirebaseStorageException : Exception 6 | { 7 | public FirebaseStorageException(string url, string responseData, Exception innerException) : base(GenerateExceptionMessage(url, responseData), innerException) 8 | { 9 | this.RequestUrl = url; 10 | this.ResponseData = responseData; 11 | } 12 | 13 | /// 14 | /// Gets the original request url. 15 | /// 16 | public string RequestUrl 17 | { 18 | get; 19 | private set; 20 | } 21 | 22 | /// 23 | /// Gets the response data returned by the firebase service. 24 | /// 25 | public string ResponseData 26 | { 27 | get; 28 | private set; 29 | } 30 | 31 | private static string GenerateExceptionMessage(string requestUrl, string responseData) 32 | { 33 | return $"Exception occured while processing the request.\nUrl: {requestUrl}\nResponse: {responseData}"; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseStorageOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | public class FirebaseStorageOptions 8 | { 9 | /// 10 | /// Gets or sets the method for retrieving auth tokens. Default is null. 11 | /// 12 | public Func> AuthTokenAsyncFactory 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | /// 19 | /// Gets or sets whether should be thrown when cancelling a running . 20 | /// 21 | public bool ThrowOnCancel 22 | { 23 | get; 24 | set; 25 | } 26 | 27 | /// 28 | /// Timeout of the . Default is 100s. 29 | /// 30 | public TimeSpan HttpClientTimeout 31 | { 32 | get; 33 | set; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseStorageProgress.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | public class FirebaseStorageProgress 4 | { 5 | public FirebaseStorageProgress(long position, long length) 6 | { 7 | this.Position = position; 8 | this.Length = length; 9 | this.Percentage = (int)((position / (double)length) * 100); 10 | } 11 | 12 | public long Length 13 | { 14 | get; 15 | private set; 16 | } 17 | 18 | public int Percentage 19 | { 20 | get; 21 | private set; 22 | } 23 | 24 | public long Position 25 | { 26 | get; 27 | private set; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseStorageReference.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | using Newtonsoft.Json; 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | public class FirebaseStorageReference 12 | { 13 | private const string FirebaseStorageEndpoint = "https://firebasestorage.googleapis.com/v0/b/"; 14 | 15 | private readonly FirebaseStorage storage; 16 | private readonly List children; 17 | 18 | internal FirebaseStorageReference(FirebaseStorage storage, string childRoot) 19 | { 20 | this.children = new List(); 21 | 22 | this.storage = storage; 23 | this.children.Add(childRoot); 24 | } 25 | 26 | /// 27 | /// Starts uploading given stream to target location. 28 | /// 29 | /// Stream to upload. 30 | /// Cancellation token which can be used to cancel the operation. 31 | /// Optional type of data being uploaded, will be used to set HTTP Content-Type header. 32 | /// which can be used to track the progress of the upload. 33 | public FirebaseStorageTask PutAsync(Stream stream, CancellationToken cancellationToken, string mimeType = null) 34 | { 35 | return new FirebaseStorageTask(this.storage.Options, this.GetTargetUrl(), this.GetFullDownloadUrl(), stream, cancellationToken, mimeType); 36 | } 37 | 38 | /// 39 | /// Starts uploading given stream to target location. 40 | /// 41 | /// Stream to upload. 42 | /// which can be used to track the progress of the upload. 43 | public FirebaseStorageTask PutAsync(Stream fileStream) 44 | { 45 | return this.PutAsync(fileStream, CancellationToken.None); 46 | } 47 | 48 | /// 49 | /// Gets the meta data for given file. 50 | /// 51 | /// 52 | public async Task GetMetaDataAsync() 53 | { 54 | var data = await PerformFetch(); 55 | 56 | return data; 57 | } 58 | 59 | /// 60 | /// Gets the url to download given file. 61 | /// 62 | public async Task GetDownloadUrlAsync() 63 | { 64 | var data = await PerformFetch>(); 65 | 66 | object downloadTokens; 67 | 68 | if (!data.TryGetValue("downloadTokens", out downloadTokens)) 69 | { 70 | throw new ArgumentOutOfRangeException($"Could not extract 'downloadTokens' property from response. Response: {JsonConvert.SerializeObject(data)}"); 71 | } 72 | 73 | return this.GetFullDownloadUrl() + downloadTokens; 74 | } 75 | 76 | /// 77 | /// Deletes a file at target location. 78 | /// 79 | public async Task DeleteAsync() 80 | { 81 | var url = this.GetDownloadUrl(); 82 | var resultContent = "N/A"; 83 | 84 | try 85 | { 86 | using (var http = await this.storage.Options.CreateHttpClientAsync().ConfigureAwait(false)) 87 | { 88 | var result = await http.DeleteAsync(url).ConfigureAwait(false); 89 | 90 | resultContent = await result.Content.ReadAsStringAsync().ConfigureAwait(false); 91 | 92 | result.EnsureSuccessStatusCode(); 93 | } 94 | } 95 | catch (Exception ex) 96 | { 97 | throw new FirebaseStorageException(url, resultContent, ex); 98 | } 99 | } 100 | 101 | /// 102 | /// Constructs firebase path to the file. 103 | /// 104 | /// Name of the entity. This can be folder or a file name or full path. 105 | /// 106 | /// storage 107 | /// .Child("some") 108 | /// .Child("path") 109 | /// .Child("to/file.png"); 110 | /// 111 | /// for fluid syntax. 112 | public FirebaseStorageReference Child(string name) 113 | { 114 | this.children.Add(name); 115 | return this; 116 | } 117 | 118 | private async Task PerformFetch() 119 | { 120 | var url = this.GetDownloadUrl(); 121 | var resultContent = "N/A"; 122 | 123 | try 124 | { 125 | using (var http = await this.storage.Options.CreateHttpClientAsync().ConfigureAwait(false)) 126 | { 127 | var result = await http.GetAsync(url); 128 | resultContent = await result.Content.ReadAsStringAsync().ConfigureAwait(false); 129 | var data = JsonConvert.DeserializeObject(resultContent); 130 | 131 | result.EnsureSuccessStatusCode(); 132 | 133 | return data; 134 | } 135 | } 136 | catch (Exception ex) 137 | { 138 | throw new FirebaseStorageException(url, resultContent, ex); 139 | } 140 | } 141 | 142 | private string GetTargetUrl() 143 | { 144 | return $"{FirebaseStorageEndpoint}{this.storage.StorageBucket}/o?name={this.GetEscapedPath()}"; 145 | } 146 | 147 | private string GetDownloadUrl() 148 | { 149 | return $"{FirebaseStorageEndpoint}{this.storage.StorageBucket}/o/{this.GetEscapedPath()}"; 150 | } 151 | 152 | private string GetFullDownloadUrl() 153 | { 154 | return this.GetDownloadUrl() + "?alt=media&token="; 155 | } 156 | 157 | private string GetEscapedPath() 158 | { 159 | return Uri.EscapeDataString(string.Join("/", this.children)); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Firebase.Storage/FirebaseStorageTask.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Net.Http; 8 | using System.Net.Http.Headers; 9 | using System.Runtime.CompilerServices; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | public class FirebaseStorageTask 14 | { 15 | private const int ProgressReportDelayMiliseconds = 500; 16 | 17 | private readonly Task uploadTask; 18 | private readonly Stream stream; 19 | 20 | public FirebaseStorageTask(FirebaseStorageOptions options, string url, string downloadUrl, Stream stream, CancellationToken cancellationToken, string mimeType = null) 21 | { 22 | this.TargetUrl = url; 23 | this.uploadTask = this.UploadFile(options, url, downloadUrl, stream, cancellationToken, mimeType); 24 | this.stream = stream; 25 | this.Progress = new Progress(); 26 | 27 | Task.Factory.StartNew(this.ReportProgressLoop); 28 | } 29 | 30 | public Progress Progress 31 | { 32 | get; 33 | private set; 34 | } 35 | 36 | 37 | public string TargetUrl 38 | { 39 | get; 40 | private set; 41 | } 42 | 43 | public TaskAwaiter GetAwaiter() 44 | { 45 | return this.uploadTask.GetAwaiter(); 46 | } 47 | 48 | private async Task UploadFile(FirebaseStorageOptions options, string url, string downloadUrl, Stream stream, CancellationToken cancellationToken, string mimeType = null) 49 | { 50 | var responseData = "N/A"; 51 | 52 | try 53 | { 54 | using (var client = await options.CreateHttpClientAsync()) 55 | { 56 | var request = new HttpRequestMessage(HttpMethod.Post, url) 57 | { 58 | Content = new StreamContent(stream) 59 | }; 60 | 61 | if (!string.IsNullOrEmpty(mimeType)) 62 | { 63 | request.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType); 64 | } 65 | 66 | var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false); 67 | responseData = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 68 | 69 | response.EnsureSuccessStatusCode(); 70 | var data = JsonConvert.DeserializeObject>(responseData); 71 | 72 | return downloadUrl + data["downloadTokens"]; 73 | } 74 | } 75 | catch (TaskCanceledException) 76 | { 77 | if (options.ThrowOnCancel) 78 | { 79 | throw; 80 | } 81 | 82 | return string.Empty; 83 | } 84 | catch (Exception ex) 85 | { 86 | throw new FirebaseStorageException(url, responseData, ex); 87 | } 88 | } 89 | 90 | private async void ReportProgressLoop() 91 | { 92 | while (!this.uploadTask.IsCompleted) 93 | { 94 | await Task.Delay(ProgressReportDelayMiliseconds); 95 | 96 | try 97 | { 98 | this.OnReportProgress(new FirebaseStorageProgress(this.stream.Position, this.stream.Length)); 99 | } 100 | catch (ObjectDisposedException) 101 | { 102 | // there is no 100 % way to prevent ObjectDisposedException, there are bound to be concurrency issues. 103 | return; 104 | } 105 | } 106 | } 107 | 108 | private void OnReportProgress(FirebaseStorageProgress progress) 109 | { 110 | (this.Progress as IProgress).Report(progress); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Firebase.Storage/HttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Threading.Tasks; 7 | 8 | public static class HttpClientFactory 9 | { 10 | /// 11 | /// Creates a new with authentication header when is specified. 12 | /// 13 | /// Firebase storage options. 14 | public static async Task CreateHttpClientAsync(this FirebaseStorageOptions options) 15 | { 16 | var client = new HttpClient(); 17 | 18 | if (options.HttpClientTimeout != default(TimeSpan)) 19 | { 20 | client.Timeout = options.HttpClientTimeout; 21 | } 22 | 23 | if (options.AuthTokenAsyncFactory != null) 24 | { 25 | var auth = await options.AuthTokenAsyncFactory().ConfigureAwait(false); 26 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Firebase", auth); 27 | } 28 | 29 | return client; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/AuthenticationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage.UnitTests 2 | { 3 | using Firebase.Auth; 4 | using Firebase.Storage.UnitTests.Models; 5 | 6 | internal static class AuthenticationExtensions 7 | { 8 | internal static FirebaseStorageOptions GetStorageAuthOptions(this FirebaseAuthProvider authProvider, FirebaseAuthenticationModel authSettings) 9 | { 10 | return new FirebaseStorageOptions { AuthTokenAsyncFactory = async () => await authSettings.GetAuthenticationTokenAsync(authProvider) }; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/Firebase.Storage.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | PreserveNewest 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/Models/FirebaseAuthenticationModel.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage.UnitTests.Models 2 | { 3 | using System.Threading.Tasks; 4 | 5 | using Firebase.Auth; 6 | 7 | public sealed class FirebaseAuthenticationModel 8 | { 9 | public string ApiKey { get; set; } 10 | 11 | public string Email { get; set; } 12 | 13 | public string Password { get; set; } 14 | 15 | public FirebaseAuthProvider CreateAuthProvider() 16 | { 17 | return new FirebaseAuthProvider(new FirebaseConfig(this.ApiKey)); 18 | } 19 | 20 | public async Task GetAuthenticationTokenAsync(FirebaseAuthProvider authProvider) 21 | { 22 | return (await authProvider.SignInWithEmailAndPasswordAsync(this.Email, this.Password)).FirebaseToken; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/Models/Secrets.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage.UnitTests.Models 2 | { 3 | public sealed class Secrets 4 | { 5 | public string BucketName { get; set; } 6 | 7 | public FirebaseAuthenticationModel Authentication { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/UploadWithMimeTypeTest.cs: -------------------------------------------------------------------------------- 1 | namespace Firebase.Storage.UnitTests 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using Firebase.Storage.UnitTests.Models; 9 | 10 | using FluentAssertions; 11 | 12 | using Newtonsoft.Json; 13 | 14 | using Xunit; 15 | 16 | public sealed class UploadWithMimeTypeTest 17 | { 18 | [Theory] 19 | [InlineData("secrets.json", "test.jpg", "image/jpeg")] 20 | public async Task ShouldBeSetWhenDownloadingAsync(string secretsFilePath, string fileToUpload, string mimeType) 21 | { 22 | File.Exists(secretsFilePath).Should().BeTrue("You need to create your own secrets.json (check out the example file for details)"); 23 | 24 | var secrets = JsonConvert.DeserializeObject(File.ReadAllText(secretsFilePath)); 25 | var options = secrets.Authentication.CreateAuthProvider().GetStorageAuthOptions(secrets.Authentication); 26 | var storage = new FirebaseStorage(secrets.BucketName, options); 27 | 28 | long actualStreamSize; 29 | string downloadUrl; 30 | using (var contentStream = new MemoryStream(File.ReadAllBytes(fileToUpload))) 31 | { 32 | actualStreamSize = contentStream.Length; 33 | downloadUrl = await storage.Child("test.jpg").PutAsync( 34 | contentStream, 35 | CancellationToken.None, 36 | mimeType); 37 | } 38 | 39 | // now download the file and check the mime-type 40 | var http = await storage.Options.CreateHttpClientAsync(); 41 | using (var response = await http.GetAsync(downloadUrl)) 42 | { 43 | response.Content.Headers.ContentType.MediaType.Should().Be(mimeType); 44 | response.Content.Headers.ContentLength.HasValue.Should().BeTrue(); 45 | // ReSharper disable once PossibleInvalidOperationException 46 | response.Content.Headers.ContentLength.Value.Should().Be(actualStreamSize); 47 | (await response.Content.ReadAsByteArrayAsync()).LongLength.Should().Be(actualStreamSize); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/secrets.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "BucketName": "YOUR_PROJECT.appspot.com", 3 | "Authentication": { 4 | "ApiKey": "YOUR_API_KEY", 5 | "Email": "...", 6 | "Password": "..." 7 | } 8 | } -------------------------------------------------------------------------------- /test/Firebase.Storage.IntegrationTests/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/step-up-labs/firebase-storage-dotnet/69dac6f45673d82db139fb6b0bd7b1256db164df/test/Firebase.Storage.IntegrationTests/test.jpg -------------------------------------------------------------------------------- /test/Firebase.Storage.Tests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firebase.Storage", "..\src\Firebase.Storage\Firebase.Storage.csproj", "{626465CB-8713-4E30-9834-D273A3A0D1B1}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F3A8B0F0-FAE4-4F9F-BE3C-E0BCB9570145}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5876DFA6-9BB7-46F8-A8C0-A33A6DB7202A}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firebase.Storage.IntegrationTests", "Firebase.Storage.IntegrationTests\Firebase.Storage.IntegrationTests.csproj", "{C67454BD-7262-4EA0-8147-75F1C5701E42}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {626465CB-8713-4E30-9834-D273A3A0D1B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {626465CB-8713-4E30-9834-D273A3A0D1B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {626465CB-8713-4E30-9834-D273A3A0D1B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {626465CB-8713-4E30-9834-D273A3A0D1B1}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {C67454BD-7262-4EA0-8147-75F1C5701E42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {C67454BD-7262-4EA0-8147-75F1C5701E42}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {C67454BD-7262-4EA0-8147-75F1C5701E42}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {C67454BD-7262-4EA0-8147-75F1C5701E42}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(NestedProjects) = preSolution 33 | {626465CB-8713-4E30-9834-D273A3A0D1B1} = {F3A8B0F0-FAE4-4F9F-BE3C-E0BCB9570145} 34 | {C67454BD-7262-4EA0-8147-75F1C5701E42} = {5876DFA6-9BB7-46F8-A8C0-A33A6DB7202A} 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {A78FBE32-9FF6-46B3-B797-BD566377BD72} 38 | EndGlobalSection 39 | EndGlobal 40 | --------------------------------------------------------------------------------