@@ -27,6 +27,12 @@ internal class StorageProvider : IStorageProvider
2727 /// <summary>The underlying text compression helper.</summary>
2828 private readonly IGzipHelper GzipHelper ;
2929
30+ /// <summary>Whether Azure blob storage is configured.</summary>
31+ private bool HasAzure => ! string . IsNullOrWhiteSpace ( this . ClientsConfig . AzureBlobConnectionString ) ;
32+
33+ /// <summary>The number of days since the blob's last-modified date when it will be deleted.</summary>
34+ private int ExpiryDays => this . ClientsConfig . AzureBlobTempExpiryDays ;
35+
3036
3137 /*********
3238 ** Public methods
@@ -43,65 +49,106 @@ public StorageProvider(IOptions<ApiClientsConfig> clientsConfig, IPastebinClient
4349 }
4450
4551 /// <summary>Save a text file to storage.</summary>
46- /// <param name="title">The display title, if applicable.</param>
4752 /// <param name="content">The content to upload.</param>
4853 /// <param name="compress">Whether to gzip the text.</param>
4954 /// <returns>Returns metadata about the save attempt.</returns>
50- public async Task < UploadResult > SaveAsync ( string title , string content , bool compress = true )
55+ public async Task < UploadResult > SaveAsync ( string content , bool compress = true )
5156 {
52- try
53- {
54- using Stream stream = new MemoryStream ( Encoding . UTF8 . GetBytes ( content ) ) ;
55- string id = Guid . NewGuid ( ) . ToString ( "N" ) ;
57+ string id = Guid . NewGuid ( ) . ToString ( "N" ) ;
5658
57- BlobClient blob = this . GetAzureBlobClient ( id ) ;
58- await blob . UploadAsync ( stream ) ;
59+ // save to Azure
60+ if ( this . HasAzure )
61+ {
62+ try
63+ {
64+ using Stream stream = new MemoryStream ( Encoding . UTF8 . GetBytes ( content ) ) ;
65+ BlobClient blob = this . GetAzureBlobClient ( id ) ;
66+ await blob . UploadAsync ( stream ) ;
5967
60- return new UploadResult ( true , id , null ) ;
68+ return new UploadResult ( true , id , null ) ;
69+ }
70+ catch ( Exception ex )
71+ {
72+ return new UploadResult ( false , null , ex . Message ) ;
73+ }
6174 }
62- catch ( Exception ex )
75+
76+ // save to local filesystem for testing
77+ else
6378 {
64- return new UploadResult ( false , null , ex . Message ) ;
79+ string path = this . GetDevFilePath ( id ) ;
80+ Directory . CreateDirectory ( Path . GetDirectoryName ( path ) ) ;
81+
82+ File . WriteAllText ( path , content ) ;
83+ return new UploadResult ( true , id , null ) ;
6584 }
6685 }
6786
6887 /// <summary>Fetch raw text from storage.</summary>
6988 /// <param name="id">The storage ID returned by <see cref="SaveAsync"/>.</param>
7089 public async Task < StoredFileInfo > GetAsync ( string id )
7190 {
72- // fetch from Azure/Amazon
91+ // fetch from blob storage
7392 if ( Guid . TryParseExact ( id , "N" , out Guid _ ) )
7493 {
75- // try Azure
76- try
94+ // Azure Blob storage
95+ if ( this . HasAzure )
7796 {
78- BlobClient blob = this . GetAzureBlobClient ( id ) ;
79- Response < BlobDownloadInfo > response = await blob . DownloadAsync ( ) ;
80- using BlobDownloadInfo result = response . Value ;
97+ try
98+ {
99+ BlobClient blob = this . GetAzureBlobClient ( id ) ;
100+ Response < BlobDownloadInfo > response = await blob . DownloadAsync ( ) ;
101+ using BlobDownloadInfo result = response . Value ;
81102
82- using StreamReader reader = new StreamReader ( result . Content ) ;
83- DateTimeOffset expiry = result . Details . LastModified + TimeSpan . FromDays ( this . ClientsConfig . AzureBlobTempExpiryDays ) ;
84- string content = this . GzipHelper . DecompressString ( reader . ReadToEnd ( ) ) ;
103+ using StreamReader reader = new StreamReader ( result . Content ) ;
104+ DateTimeOffset expiry = result . Details . LastModified + TimeSpan . FromDays ( this . ExpiryDays ) ;
105+ string content = this . GzipHelper . DecompressString ( reader . ReadToEnd ( ) ) ;
85106
86- return new StoredFileInfo
107+ return new StoredFileInfo
108+ {
109+ Success = true ,
110+ Content = content ,
111+ Expiry = expiry . UtcDateTime
112+ } ;
113+ }
114+ catch ( RequestFailedException ex )
87115 {
88- Success = true ,
89- Content = content ,
90- Expiry = expiry . UtcDateTime
91- } ;
116+ return new StoredFileInfo
117+ {
118+ Error = ex . ErrorCode == "BlobNotFound"
119+ ? "There's no file with that ID."
120+ : $ "Could not fetch that file from storage ({ ex . ErrorCode } : { ex . Message } )."
121+ } ;
122+ }
92123 }
93- catch ( RequestFailedException ex )
124+
125+ // local filesystem for testing
126+ else
94127 {
128+ FileInfo file = new FileInfo ( this . GetDevFilePath ( id ) ) ;
129+ if ( file . Exists )
130+ {
131+ if ( file . LastWriteTimeUtc . AddDays ( this . ExpiryDays ) < DateTime . UtcNow )
132+ file . Delete ( ) ;
133+ else
134+ {
135+ return new StoredFileInfo
136+ {
137+ Success = true ,
138+ Content = File . ReadAllText ( file . FullName ) ,
139+ Expiry = DateTime . UtcNow . AddDays ( this . ExpiryDays ) ,
140+ Warning = "This file was saved temporarily to the local computer. This should only happen in a local development environment."
141+ } ;
142+ }
143+ }
95144 return new StoredFileInfo
96145 {
97- Error = ex . ErrorCode == "BlobNotFound"
98- ? "There's no file with that ID."
99- : $ "Could not fetch that file from storage ({ ex . ErrorCode } : { ex . Message } )."
146+ Error = "There's no file with that ID."
100147 } ;
101148 }
102149 }
103150
104- // get from PasteBin
151+ // get from Pastebin
105152 else
106153 {
107154 PasteInfo response = await this . Pastebin . GetAsync ( id ) ;
@@ -116,12 +163,19 @@ public async Task<StoredFileInfo> GetAsync(string id)
116163 }
117164
118165 /// <summary>Get a client for reading and writing to Azure Blob storage.</summary>
119- /// <param name="id">The file ID to fetch .</param>
166+ /// <param name="id">The file ID.</param>
120167 private BlobClient GetAzureBlobClient ( string id )
121168 {
122169 var azure = new BlobServiceClient ( this . ClientsConfig . AzureBlobConnectionString ) ;
123170 var container = azure . GetBlobContainerClient ( this . ClientsConfig . AzureBlobTempContainer ) ;
124171 return container . GetBlobClient ( $ "uploads/{ id } ") ;
125172 }
173+
174+ /// <summary>Get the absolute file path for an upload when running in a local test environment with no Azure account configured.</summary>
175+ /// <param name="id">The file ID.</param>
176+ private string GetDevFilePath ( string id )
177+ {
178+ return Path . Combine ( Path . GetTempPath ( ) , "smapi-web-temp" , $ "{ id } .txt") ;
179+ }
126180 }
127181}
0 commit comments