aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Sebastian Schmidt <mrschmidt@google.com>2017-09-06 14:37:04 -0700
committerGravatar GitHub <noreply@github.com>2017-09-06 14:37:04 -0700
commite8e0916c2ae24ec14130b1dc00f9574d78940462 (patch)
tree92554ccf9e084f62546b85ad035e71c005a3d67a
parent96685dfbb9936ec9b875491ccad9891231afd7ba (diff)
Firebase Storage: Allowing metadata to be cleared (#197)
* Allowing metadata to be cleared
-rw-r--r--Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m72
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageMetadataTests.m49
-rw-r--r--Example/Storage/Tests/Unit/FIRStorageUpdateMetadataTests.m4
-rw-r--r--Firebase/Storage/FIRStorageMetadata.m37
-rw-r--r--Firebase/Storage/FIRStorageUpdateMetadataTask.m2
-rw-r--r--Firebase/Storage/Private/FIRStorageMetadata_Private.h20
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.