44
55namespace AzureOss \FlysystemAzureBlobStorage ;
66
7- use AzureOss \Storage \Blob \Clients \ContainerClient ;
8- use AzureOss \Storage \Blob \Options \ListBlobsOptions ;
9- use AzureOss \Storage \Blob \Options \UploadBlockBlobOptions ;
10- use AzureOss \Storage \Blob \SAS \BlobSASPermissions ;
11- use AzureOss \Storage \Blob \SAS \BlobSASQueryParameters ;
12- use AzureOss \Storage \Blob \SAS \BlobSASSignatureValues ;
13- use AzureOss \Storage \Common \SAS \SASProtocol ;
7+ use AzureOss \Storage \Blob \BlobContainerClient ;
8+ use AzureOss \Storage \Blob \BlobServiceClient ;
9+ use AzureOss \Storage \Blob \Models \Blob ;
10+ use AzureOss \Storage \Blob \Models \BlobProperties ;
11+ use AzureOss \Storage \Blob \Models \UploadBlobOptions ;
12+ use AzureOss \Storage \Blob \Sas \BlobSasBuilder ;
13+ use League \Flysystem \ChecksumAlgoIsNotSupported ;
14+ use League \Flysystem \ChecksumProvider ;
1415use League \Flysystem \Config ;
1516use League \Flysystem \DirectoryAttributes ;
1617use League \Flysystem \FileAttributes ;
2122use League \Flysystem \UnableToDeleteFile ;
2223use League \Flysystem \UnableToListContents ;
2324use League \Flysystem \UnableToMoveFile ;
25+ use League \Flysystem \UnableToProvideChecksum ;
2426use League \Flysystem \UnableToReadFile ;
2527use League \Flysystem \UnableToRetrieveMetadata ;
2628use League \Flysystem \UnableToSetVisibility ;
2931use League \MimeTypeDetection \FinfoMimeTypeDetector ;
3032use League \MimeTypeDetection \MimeTypeDetector ;
3133
32- class AzureBlobStorageAdapter implements FilesystemAdapter, TemporaryUrlGenerator
34+ class AzureBlobStorageAdapter implements FilesystemAdapter, ChecksumProvider, TemporaryUrlGenerator
3335{
3436 private readonly MimeTypeDetector $ mimeTypeDetector ;
3537
3638 public function __construct (
37- private readonly ContainerClient $ containerClient ,
38- ?MimeTypeDetector $ mimeTypeDetector = null ,
39+ private readonly BlobContainerClient $ containerClient ,
40+ ?MimeTypeDetector $ mimeTypeDetector = null ,
3941 ) {
4042 $ this ->mimeTypeDetector = $ mimeTypeDetector ?? new FinfoMimeTypeDetector ();
4143 }
4244
4345 public function fileExists (string $ path ): bool
4446 {
4547 try {
46- return $ this ->containerClient ->getBlobClient ($ path )->exists ();
48+ return $ this ->containerClient
49+ ->getBlobClient ($ path )
50+ ->exists ();
4751 } catch (\Throwable $ e ) {
4852 throw UnableToCheckExistence::forLocation ($ path , $ e );
4953 }
@@ -52,15 +56,11 @@ public function fileExists(string $path): bool
5256 public function directoryExists (string $ path ): bool
5357 {
5458 try {
55- $ options = new ListBlobsOptions (
56- prefix: $ this ->getPrefix ($ path ),
57- maxResults: 1 ,
58- delimiter: "/ " ,
59- );
60-
61- $ response = $ this ->containerClient ->listBlobs ($ options );
59+ foreach ($ this ->listContents ($ path , false ) as $ ignored ) {
60+ return true ;
61+ };
6262
63- return count ( $ response -> blobs ) > 0 ;
63+ return false ;
6464 } catch (\Throwable $ e ) {
6565 throw UnableToCheckExistence::forLocation ($ path , $ e );
6666 }
@@ -84,11 +84,13 @@ private function upload(string $path, $contents): void
8484 try {
8585 $ mimetype = $ this ->mimeTypeDetector ->detectMimetype ($ path , $ contents );
8686
87- $ options = new UploadBlockBlobOptions (
87+ $ options = new UploadBlobOptions (
8888 contentType: $ mimetype ,
8989 );
9090
91- $ this ->containerClient ->getBlockBlobClient ($ path )->upload ($ contents , $ options );
91+ $ this ->containerClient
92+ ->getBlobClient ($ path )
93+ ->upload ($ contents , $ options );
9294 } catch (\Throwable $ e ) {
9395 throw UnableToWriteFile::atLocation ($ path , previous: $ e );
9496 }
@@ -97,9 +99,11 @@ private function upload(string $path, $contents): void
9799 public function read (string $ path ): string
98100 {
99101 try {
100- $ response = $ this ->containerClient ->getBlobClient ($ path )->get ();
102+ $ result = $ this ->containerClient
103+ ->getBlobClient ($ path )
104+ ->downloadStreaming ();
101105
102- return $ response ->content ->getContents ();
106+ return $ result ->content ->getContents ();
103107 } catch (\Throwable $ e ) {
104108 throw UnableToReadFile::fromLocation ($ path , previous: $ e );
105109 }
@@ -108,8 +112,11 @@ public function read(string $path): string
108112 public function readStream (string $ path )
109113 {
110114 try {
111- $ response = $ this ->containerClient ->getBlobClient ($ path )->get ();
112- $ resource = $ response ->content ->detach ();
115+ $ result = $ this ->containerClient
116+ ->getBlobClient ($ path )
117+ ->downloadStreaming ();
118+
119+ $ resource = $ result ->content ->detach ();
113120
114121 if ($ resource === null ) {
115122 throw new \Exception ("Should not happen " );
@@ -124,7 +131,9 @@ public function readStream(string $path)
124131 public function delete (string $ path ): void
125132 {
126133 try {
127- $ this ->containerClient ->getBlobClient ($ path )->deleteIfExists ();
134+ $ this ->containerClient
135+ ->getBlobClient ($ path )
136+ ->deleteIfExists ();
128137 } catch (\Throwable $ e ) {
129138 throw UnableToDeleteFile::atLocation ($ path , previous: $ e );
130139 }
@@ -135,7 +144,9 @@ public function deleteDirectory(string $path): void
135144 try {
136145 foreach ($ this ->listContents ($ path , true ) as $ item ) {
137146 if ($ item instanceof FileAttributes) {
138- $ this ->containerClient ->getBlobClient ($ item ->path ())->delete ();
147+ $ this ->containerClient
148+ ->getBlobClient ($ item ->path ())
149+ ->delete ();
139150 }
140151 }
141152 } catch (\Throwable $ e ) {
@@ -187,50 +198,46 @@ public function fileSize(string $path): FileAttributes
187198
188199 private function fetchMetadata (string $ path ): FileAttributes
189200 {
190- $ response = $ this ->containerClient ->getBlobClient ($ path )->getProperties ();
201+ $ properties = $ this ->containerClient
202+ ->getBlobClient ($ path )
203+ ->getProperties ();
191204
192- return new FileAttributes (
193- $ path ,
194- fileSize: $ response ->contentLength ,
195- lastModified: $ response ->lastModified ->getTimestamp (),
196- mimeType: $ response ->contentType ,
197- );
205+ return $ this ->normalizeBlob ($ path , $ properties );
198206 }
199207
200208 public function listContents (string $ path , bool $ deep ): iterable
201209 {
202210 try {
203- do {
204- $ nextMarker = "" ;
205-
206- $ options = new ListBlobsOptions (
207- prefix: $ this ->getPrefix ($ path ),
208- marker: $ nextMarker ,
209- delimiter: $ deep ? null : "/ " ,
210- );
211-
212- $ response = $ this ->containerClient ->listBlobs ($ options );
211+ $ prefix = $ path === "" ? null : ltrim ($ path , "/ " ) . "/ " ;
213212
214- foreach ($ response ->blobPrefixes as $ blobPrefix ) {
215- yield new DirectoryAttributes ($ blobPrefix ->name );
213+ if ($ deep ) {
214+ foreach ($ this ->containerClient ->getBlobs ($ prefix ) as $ item ) {
215+ yield $ this ->normalizeBlob ($ item ->name , $ item ->properties );
216216 }
217-
218- foreach ($ response ->blobs as $ blob ) {
219- yield new FileAttributes (
220- $ blob ->name ,
221- fileSize: $ blob ->properties ->contentLength ,
222- lastModified: $ blob ->properties ->lastModified ->getTimestamp (),
223- mimeType: $ blob ->properties ->contentType ,
224- );
217+ } else {
218+ foreach ($ this ->containerClient ->getBlobsByHierarchy ($ prefix ) as $ item ) {
219+ if ($ item instanceof Blob) {
220+ yield $ this ->normalizeBlob ($ item ->name , $ item ->properties );
221+ } else {
222+ yield new DirectoryAttributes ($ item ->name );
223+ }
225224 }
226-
227- $ nextMarker = $ response ->nextMarker ;
228- } while ($ nextMarker !== "" );
225+ }
229226 } catch (\Throwable $ e ) {
230- throw UnableToListContents::atLocation ($ path , $ deep , new \ Exception () );
227+ throw UnableToListContents::atLocation ($ path , $ deep , $ e );
231228 }
232229 }
233230
231+ private function normalizeBlob (string $ name , BlobProperties $ properties ): FileAttributes
232+ {
233+ return new FileAttributes (
234+ $ name ,
235+ fileSize: $ properties ->contentLength ,
236+ lastModified: $ properties ->lastModified ->getTimestamp (),
237+ mimeType: $ properties ->contentType ,
238+ );
239+ }
240+
234241 public function move (string $ source , string $ destination , Config $ config ): void
235242 {
236243 try {
@@ -244,42 +251,44 @@ public function move(string $source, string $destination, Config $config): void
244251 public function copy (string $ source , string $ destination , Config $ config ): void
245252 {
246253 try {
247- $ this ->containerClient ->getBlobClient ($ source )->copy ($ this ->containerClient ->containerName , $ destination );
254+ $ sourceBlobClient = $ this ->containerClient ->getBlobClient ($ source );
255+ $ targetBlobClient = $ this ->containerClient ->getBlobClient ($ destination );
256+
257+ $ targetBlobClient ->copyFromUri ($ sourceBlobClient ->uri );
248258 } catch (\Throwable $ e ) {
249259 throw UnableToCopyFile::fromLocationTo ($ source , $ destination , $ e );
250260 }
251261 }
252262
253- private function getPrefix (string $ path ): ? string
263+ public function temporaryUrl (string $ path, \ DateTimeInterface $ expiresAt , Config $ config ): string
254264 {
255- return $ path === "" ? null : rtrim ($ path , "/ " ) . "/ " ;
265+ $ sasBuilder = BlobSasBuilder::new ()
266+ ->setExpiresOn ($ expiresAt )
267+ ->setPermissions ("r " );
268+
269+ $ sas = $ this ->containerClient
270+ ->getBlobClient ($ path )
271+ ->generateSasUri ($ sasBuilder );
272+
273+ return (string ) $ sas ;
256274 }
257275
258- public function temporaryUrl (string $ path, \ DateTimeInterface $ expiresAt , Config $ config ): string
276+ public function checksum (string $ path , Config $ config ): string
259277 {
260- $ url = $ this ->containerClient ->getBlobClient ($ path )->getUrl ();
261-
262- $ values = new BlobSASSignatureValues (
263- containerName: $ this ->containerClient ->containerName ,
264- expiresOn: $ expiresAt ,
265- blobName: $ path ,
266- permissions: $ config ->get ("permissions " , (string ) new BlobSASPermissions (read: true )),
267- identifier: $ config ->get ("identifier " ),
268- startsOn: $ config ->get ("starts_on " ),
269- cacheControl: $ config ->get ("cache_control " ),
270- contentDisposition: $ config ->get ("content_disposition " ),
271- contentEncoding: $ config ->get ("content_encoding " ),
272- contentLanguage: $ config ->get ("content_language " ),
273- contentType: $ config ->get ("content_type " ),
274- encryptionScope: $ config ->get ("encryption_scope " ),
275- ipRange: $ config ->get ("ip_range " ),
276- snapshotTime: $ config ->get ("snapshot_time " ),
277- protocol: $ config ->get ("protocol " , SASProtocol::HTTPS_AND_HTTP ),
278- version: $ config ->get ("version " ),
279- );
278+ $ algo = $ config ->get ('checksum_algo ' , 'md5 ' );
279+
280+ if ($ algo !== 'md5 ' ) {
281+ throw new ChecksumAlgoIsNotSupported ();
282+ }
280283
281- $ sas = BlobSASQueryParameters::generate ($ values , $ this ->containerClient ->sharedKeyCredentials );
284+ try {
285+ $ properties = $ this ->containerClient
286+ ->getBlobClient ($ path )
287+ ->getProperties ();
282288
283- return sprintf ("%s?%s " , $ url , $ sas );
289+ return bin2hex (base64_decode ($ properties ->contentMD5 ));
290+ } catch (\Throwable $ e ) {
291+ throw new UnableToProvideChecksum ($ e ->getMessage (), $ path , $ e );
292+ }
284293 }
285294}
0 commit comments