diff options
author | Sebastian Schmidt <mrschmidt@google.com> | 2017-09-06 14:37:04 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-06 14:37:04 -0700 |
commit | e8e0916c2ae24ec14130b1dc00f9574d78940462 (patch) | |
tree | 92554ccf9e084f62546b85ad035e71c005a3d67a | |
parent | 96685dfbb9936ec9b875491ccad9891231afd7ba (diff) |
Firebase Storage: Allowing metadata to be cleared (#197)
* Allowing metadata to be cleared
6 files changed, 181 insertions, 3 deletions
diff --git a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m index 882d45e..0e07d97 100644 --- a/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m +++ b/Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m @@ -454,6 +454,78 @@ NSTimeInterval kFIRStorageIntegrationTestTimeout = 30; [self waitForExpectations]; } +- (void)assertMetadata:(FIRStorageMetadata *) actualMetadata + contentType:(NSString *) expectedContentType + customMetadata:(NSDictionary *) expectedCustomMetadata { + XCTAssertEqualObjects(actualMetadata.cacheControl, @"cache-control"); + XCTAssertEqualObjects(actualMetadata.contentDisposition, @"content-disposition"); + XCTAssertEqualObjects(actualMetadata.contentEncoding, @"gzip"); + XCTAssertEqualObjects(actualMetadata.contentLanguage, @"de"); + XCTAssertEqualObjects(actualMetadata.contentType, expectedContentType); + for (NSString* key in expectedCustomMetadata) { + XCTAssertEqualObjects([actualMetadata.customMetadata objectForKey:key], + [expectedCustomMetadata objectForKey:key]); + } +} + +- (void)assertMetadataNil:(FIRStorageMetadata *) actualMetadata { + XCTAssertNil(actualMetadata.cacheControl); + XCTAssertNil(actualMetadata.contentDisposition); + XCTAssertEqualObjects(actualMetadata.contentEncoding, @"identity"); + XCTAssertNil(actualMetadata.contentLanguage); + XCTAssertNil(actualMetadata.contentType); + XCTAssertNil([actualMetadata.customMetadata objectForKey:@"a"]); + XCTAssertNil([actualMetadata.customMetadata objectForKey:@"c"]); + XCTAssertNil([actualMetadata.customMetadata objectForKey:@"f"]); +} + +- (void)testUpdateMetadata { + XCTestExpectation *expectation = [self expectationWithDescription:@"testUpdateMetadata"]; + + FIRStorageReference *ref = [self.storage referenceWithPath:@"ios/public/1mb"]; + + // Update all available metadata + FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] init]; + metadata.cacheControl = @"cache-control"; + metadata.contentDisposition = @"content-disposition"; + metadata.contentEncoding = @"gzip"; + metadata.contentLanguage = @"de"; + metadata.contentType = @"content-type-a"; + metadata.customMetadata = @{@"a" : @"b"}; + + [ref updateMetadata:metadata completion:^(FIRStorageMetadata *updatedMetadata, NSError *error) { + XCTAssertNil(error); + [self assertMetadata:updatedMetadata contentType:@"content-type-a" customMetadata:@{@"a" : @"b"}]; + + // Update a subset of the metadata using the existing object. + FIRStorageMetadata *metadata = updatedMetadata; + metadata.contentType = @"content-type-b"; + metadata.customMetadata = @{@"a" : @"b", @"c" : @"d"}; + + [ref updateMetadata:metadata completion:^(FIRStorageMetadata *updatedMetadata, NSError *error) { + XCTAssertNil(error); + [self assertMetadata:updatedMetadata contentType:@"content-type-b" customMetadata: @{@"a" : @"b", @"c" : @"d"}]; + + // Clear all metadata. + FIRStorageMetadata *metadata = updatedMetadata; + metadata.cacheControl = nil; + metadata.contentDisposition = nil; + metadata.contentEncoding = nil; + metadata.contentLanguage = nil; + metadata.contentType = nil; + metadata.customMetadata = [NSDictionary dictionary]; + + [ref updateMetadata:metadata completion:^(FIRStorageMetadata *updatedMetadata, NSError *error) { + XCTAssertNil(error); + [self assertMetadataNil:updatedMetadata]; + [expectation fulfill]; + }]; + }]; + }]; + + [self waitForExpectations]; +} + - (void)testUnauthenticatedResumeGetFile { XCTestExpectation *expectation = [self expectationWithDescription:@"testUnauthenticatedResumeGetFile"]; diff --git a/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m b/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m index 92e96bb..e750bdf 100644 --- a/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m +++ b/Example/Storage/Tests/Unit/FIRStorageMetadataTests.m @@ -244,6 +244,55 @@ XCTAssertEqualObjects(metadata0, metadata1); } +- (void)testUpdatedMetadata { + NSDictionary *oldMetadata = @{ + kFIRStorageMetadataContentLanguage : @"old", + kFIRStorageMetadataCustomMetadata : @{@"foo" : @"old", @"bar" : @"old"} + }; + FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:oldMetadata]; + metadata.contentLanguage = @"new"; + metadata.customMetadata = @{@"foo" : @"new", @"bar" : @"old"}; + + NSDictionary *update = [metadata updatedMetadata]; + + NSDictionary *expectedUpdate = @{ + kFIRStorageMetadataContentLanguage : @"new", + kFIRStorageMetadataCustomMetadata : @{@"foo" : @"new"} + }; + XCTAssertEqualObjects(update, expectedUpdate); +} + +- (void)testUpdatedMetadataWithEmptyUpdate { + NSDictionary *oldMetadata = @{ + kFIRStorageMetadataContentLanguage : @"old", + kFIRStorageMetadataCustomMetadata : @{@"foo" : @"old", @"bar" : @"old"} + }; + FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:oldMetadata]; + + NSDictionary *update = [metadata updatedMetadata]; + + NSDictionary *expectedUpdate = @{ kFIRStorageMetadataCustomMetadata : @{} }; + XCTAssertEqualObjects(update, expectedUpdate); +} + +- (void)testUpdatedMetadataWithDelete { + NSDictionary *oldMetadata = @{ + kFIRStorageMetadataContentLanguage : @"old", + kFIRStorageMetadataCustomMetadata : @{@"foo" : @"old", @"bar" : @"old"} + }; + FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:oldMetadata]; + metadata.contentLanguage = nil; + metadata.customMetadata = @{@"foo" : @"old"}; + + NSDictionary *update = [metadata updatedMetadata]; + + NSDictionary *expectedUpdate = @{ + kFIRStorageMetadataContentLanguage : [NSNull null], + kFIRStorageMetadataCustomMetadata : @{@"bar" : [NSNull null]} + }; + XCTAssertEqualObjects(update, expectedUpdate); +} + - (void)testMetadataHashEquality { NSDictionary *metaDict = @{ kFIRStorageMetadataBucket : @"bucket", diff --git a/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m b/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m index a85d181..d2d2950 100644 --- a/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m +++ b/Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#import "FIRStorageMetadata_Private.h" #import "FIRStorageTestHelpers.h" #import "FIRStorageUpdateMetadataTask.h" @@ -63,7 +64,7 @@ #pragma clang diagnostic ignored "-Warc-retain-cycles" XCTAssertEqualObjects(fetcher.request.URL, [FIRStorageTestHelpers objectURL]); XCTAssertEqualObjects(fetcher.request.HTTPMethod, @"PATCH"); - NSData *bodyData = [NSData frs_dataFromJSONDictionary:[self.metadata dictionaryRepresentation]]; + NSData *bodyData = [NSData frs_dataFromJSONDictionary:[self.metadata updatedMetadata]]; XCTAssertEqualObjects(fetcher.request.HTTPBody, bodyData); NSDictionary *HTTPHeaders = fetcher.request.allHTTPHeaderFields; XCTAssertEqualObjects(HTTPHeaders[@"Content-Type"], @"application/json; charset=UTF-8"); @@ -75,6 +76,7 @@ HTTPVersion:kHTTPVersion headerFields:nil]; response(httpResponse, nil, nil); + self.fetcherService.testBlock = nil; }; FIRStoragePath *path = [FIRStorageTestHelpers objectPath]; diff --git a/Firebase/Storage/FIRStorageMetadata.m b/Firebase/Storage/FIRStorageMetadata.m index 4269a45..c4ff5a8 100644 --- a/Firebase/Storage/FIRStorageMetadata.m +++ b/Firebase/Storage/FIRStorageMetadata.m @@ -31,6 +31,8 @@ - (instancetype)initWithDictionary:(NSDictionary *)dictionary { self = [super init]; if (self) { + _initialMetadata = [dictionary copy]; + _bucket = dictionary[kFIRStorageMetadataBucket]; _cacheControl = dictionary[kFIRStorageMetadataCacheControl]; _contentDisposition = dictionary[kFIRStorageMetadataContentDisposition]; @@ -73,7 +75,10 @@ #pragma mark - NSObject overrides - (instancetype)copyWithZone:(NSZone *)zone { - return [[[self class] allocWithZone:zone] initWithDictionary:[self dictionaryRepresentation]]; + FIRStorageMetadata *clone = + [[[self class] allocWithZone:zone] initWithDictionary:[self dictionaryRepresentation]]; + clone.initialMetadata = [self.initialMetadata copy]; + return clone; } - (BOOL)isEqual:(id)object { @@ -200,6 +205,36 @@ return [_downloadURLs firstObject]; } +#pragma mark - Private methods + ++ (void)removeMatchingMetadata:(NSMutableDictionary *)metadata + oldMetadata:(NSDictionary *)oldMetadata { + for (NSString* metadataKey in [oldMetadata allKeys]) { + id oldValue = [oldMetadata objectForKey:metadataKey]; + id newValue = [metadata objectForKey:metadataKey]; + + if (oldValue && !newValue) { + [metadata setObject:[NSNull null] forKey:metadataKey]; + } else if ([oldValue isKindOfClass:[NSString class]] && + [newValue isKindOfClass:[NSString class]]) { + if ([oldValue isEqualToString:newValue]) { + [metadata removeObjectForKey:metadataKey]; + } + } else if ([oldValue isKindOfClass:[NSDictionary class]] && + [newValue isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *nestedMetadata = [newValue mutableCopy]; + [self removeMatchingMetadata:nestedMetadata oldMetadata:oldValue]; + [metadata setObject:[nestedMetadata copy] forKey:metadataKey]; + } + } +} + +- (NSDictionary *)updatedMetadata { + NSMutableDictionary *metadataUpdate = [[self dictionaryRepresentation] mutableCopy]; + [FIRStorageMetadata removeMatchingMetadata:metadataUpdate oldMetadata:_initialMetadata]; + return [metadataUpdate copy]; +} + #pragma mark - RFC 3339 conversions static NSDateFormatter *sRFC3339DateFormatter; diff --git a/Firebase/Storage/FIRStorageUpdateMetadataTask.m b/Firebase/Storage/FIRStorageUpdateMetadataTask.m index 005f78f..642ebcd 100644 --- a/Firebase/Storage/FIRStorageUpdateMetadataTask.m +++ b/Firebase/Storage/FIRStorageUpdateMetadataTask.m @@ -45,7 +45,7 @@ - (void)enqueue { NSMutableURLRequest *request = [self.baseRequest mutableCopy]; - NSDictionary *updateDictionary = [_updateMetadata dictionaryRepresentation]; + NSDictionary *updateDictionary = [_updateMetadata updatedMetadata]; NSData *updateData = [NSData frs_dataFromJSONDictionary:updateDictionary]; request.HTTPMethod = @"PATCH"; request.timeoutInterval = self.reference.storage.maxUploadRetryTime; diff --git a/Firebase/Storage/Private/FIRStorageMetadata_Private.h b/Firebase/Storage/Private/FIRStorageMetadata_Private.h index 629c935..ad8cc94 100644 --- a/Firebase/Storage/Private/FIRStorageMetadata_Private.h +++ b/Firebase/Storage/Private/FIRStorageMetadata_Private.h @@ -15,6 +15,7 @@ */ #import "FIRStorageConstants_Private.h" +#import "FIRStorageMetadata.h" @class FIRStorageReference; @@ -34,6 +35,25 @@ NS_ASSUME_NONNULL_BEGIN @property(readwrite) FIRStorageMetadataType type; /** + * The original metadata representation received from the server or an empty dictionary + * if the metadata object was initialized by the user. + */ +@property(copy, nonatomic) NSDictionary *initialMetadata; + +/** + * Recursively removes entries in 'metadata' that are unmodified from 'oldMetadata'. + * Adds 'NSNull' for entries that only exist in oldMetadata. + */ ++ (void)removeMatchingMetadata:(NSMutableDictionary *)metadata + oldMetadata:(NSDictionary *)oldMetadata; + +/** + * Computes the updates between the state at initialization and the current state. + * Returns a dictionary with only the updated data. Removed keys are set to NSNull. + */ +- (NSDictionary *)updatedMetadata; + +/** * Returns an RFC3339 formatted date from a string. * @param dateString An NSString of the form: yyyy-MM-ddTHH:mm:ss.SSSZ. * @return An NSDate populated from the string or nil if conversion isn't possible. |