@@ -29,67 +29,31 @@ import FirebaseStorageInternal
29
29
/**
30
30
* The name of the bucket containing this object.
31
31
*/
32
- @objc public let bucket : String
32
+ @objc public let bucket : String ?
33
33
34
34
/**
35
35
* Cache-Control directive for the object data.
36
36
*/
37
- @objc public var cacheControl : String ? {
38
- get {
39
- return impl. cacheControl
40
- }
41
- set ( newValue) {
42
- impl. cacheControl = newValue
43
- }
44
- }
45
-
37
+ @objc public var cacheControl : String ?
46
38
/**
47
39
* Content-Disposition of the object data.
48
40
*/
49
- @objc public var contentDisposition : String ? {
50
- get {
51
- return impl. contentDisposition
52
- }
53
- set ( newValue) {
54
- impl. contentDisposition = newValue
55
- }
56
- }
41
+ @objc public var contentDisposition : String ?
57
42
58
43
/**
59
44
* Content-Encoding of the object data.
60
45
*/
61
- @objc public var contentEncoding : String ? {
62
- get {
63
- return impl. contentEncoding
64
- }
65
- set ( newValue) {
66
- impl. contentEncoding = newValue
67
- }
68
- }
46
+ @objc public var contentEncoding : String ?
69
47
70
48
/**
71
49
* Content-Language of the object data.
72
50
*/
73
- @objc public var contentLanguage : String ? {
74
- get {
75
- return impl. contentLanguage
76
- }
77
- set ( newValue) {
78
- impl. contentLanguage = newValue
79
- }
80
- }
51
+ @objc public var contentLanguage : String ?
81
52
82
53
/**
83
54
* Content-Type of the object data.
84
55
*/
85
- @objc public var contentType : String ? {
86
- get {
87
- return impl. contentType
88
- }
89
- set ( newValue) {
90
- impl. contentType = newValue
91
- }
92
- }
56
+ @objc public var contentType : String ?
93
57
94
58
/**
95
59
* MD5 hash of the data; encoded using base64.
@@ -104,14 +68,7 @@ import FirebaseStorageInternal
104
68
/**
105
69
* User-provided metadata, in key/value pairs.
106
70
*/
107
- @objc public var customMetadata : [ String : String ] ? {
108
- get {
109
- return impl. customMetadata
110
- }
111
- set ( newValue) {
112
- impl. customMetadata = newValue
113
- }
114
- }
71
+ @objc public var customMetadata : [ String : String ] ?
115
72
116
73
/**
117
74
* The version of the metadata for this object at this generation. Used
@@ -123,26 +80,12 @@ import FirebaseStorageInternal
123
80
/**
124
81
* The name of this object, in gs://bucket/path/to/object.txt, this is object.txt.
125
82
*/
126
- @objc public internal( set) var name : String ? {
127
- get {
128
- return impl. name
129
- }
130
- set ( newValue) {
131
- impl. name = newValue
132
- }
133
- }
83
+ @objc public internal( set) var name : String ?
134
84
135
85
/**
136
86
* The full path of this object, in gs://bucket/path/to/object.txt, this is path/to/object.txt.
137
87
*/
138
- @objc public internal( set) var path : String ? {
139
- get {
140
- return impl. path
141
- }
142
- set ( newValue) {
143
- impl. path = newValue
144
- }
145
- }
88
+ @objc public internal( set) var path : String ?
146
89
147
90
/**
148
91
* Content-Length of the data in bytes.
@@ -159,83 +102,197 @@ import FirebaseStorageInternal
159
102
*/
160
103
@objc public let updated : Date ?
161
104
162
- /**
163
- * A reference to the object in Firebase Storage.
164
- */
165
- @objc public let storageReference : StorageReference ?
166
-
167
105
/**
168
106
* Creates a Dictionary from the contents of the metadata.
169
107
* @return A Dictionary that represents the contents of the metadata.
170
108
*/
171
- @objc open func dictionaryRepresentation( ) -> [ String : Any ] {
172
- return impl. dictionaryRepresentation ( )
109
+ @objc open func dictionaryRepresentation( ) -> [ String : AnyHashable ] {
110
+ var dictionary : [ String : AnyHashable ] = [ : ]
111
+ if let bucket = bucket {
112
+ dictionary [ " bucket " ] = bucket
113
+ }
114
+ if let cacheControl = cacheControl {
115
+ dictionary [ " cacheControl " ] = cacheControl
116
+ }
117
+ if let contentDisposition = contentDisposition {
118
+ dictionary [ " contentDisposition " ] = contentDisposition
119
+ }
120
+ if let contentEncoding = contentEncoding {
121
+ dictionary [ " contentEncoding " ] = contentEncoding
122
+ }
123
+ if let contentLanguage = contentLanguage {
124
+ dictionary [ " contentLanguage " ] = contentLanguage
125
+ }
126
+ if let contentType = contentType {
127
+ dictionary [ " contentType " ] = contentType
128
+ }
129
+ if let md5Hash = md5Hash {
130
+ dictionary [ " md5Hash " ] = md5Hash
131
+ }
132
+ if let customMetadata = customMetadata {
133
+ dictionary [ " metadata " ] = customMetadata
134
+ }
135
+ if size != 0 {
136
+ dictionary [ " size " ] = size
137
+ }
138
+ if generation != 0 {
139
+ dictionary [ " generation " ] = " \( generation) "
140
+ }
141
+ if metageneration != 0 {
142
+ dictionary [ " metageneration " ] = " \( metageneration) "
143
+ }
144
+ if let timeCreated = timeCreated {
145
+ dictionary [ " timeCreated " ] = StorageMetadata . RFC3339StringFromDate ( timeCreated)
146
+ }
147
+ if let updated = updated {
148
+ dictionary [ " updated " ] = StorageMetadata . RFC3339StringFromDate ( updated)
149
+ }
150
+ if let path = path {
151
+ dictionary [ " name " ] = path
152
+ }
153
+ return dictionary
173
154
}
174
155
175
156
/**
176
157
* Determines if the current metadata represents a "file".
177
158
*/
178
- @objc public let isFile : Bool
159
+ @objc public var isFile : Bool {
160
+ return fileType == . file
161
+ }
179
162
163
+ // TODO: Nothing in the implementation ever sets `.folder`.
180
164
/**
181
165
* Determines if the current metadata represents a "folder".
182
166
*/
183
- @objc public let isFolder : Bool
167
+ @objc public var isFolder : Bool {
168
+ return fileType == . folder
169
+ }
184
170
185
171
// MARK: - Public Initializers
186
172
187
173
@objc override public convenience init ( ) {
188
- self . init ( impl : FIRIMPLStorageMetadata ( dictionary: [ : ] ) ! )
174
+ self . init ( dictionary: [ : ] )
189
175
}
190
176
191
177
/**
192
178
* Creates an instance of StorageMetadata from the contents of a dictionary.
193
179
* @return An instance of StorageMetadata that represents the contents of a dictionary.
194
180
*/
195
- @objc public convenience init ( dictionary: [ String : Any ] ) {
196
- self . init ( impl: FIRIMPLStorageMetadata ( dictionary: dictionary) !)
181
+ @objc public init ( dictionary: [ String : AnyHashable ] ) {
182
+ initialMetadata = dictionary
183
+ bucket = dictionary [ " bucket " ] as? String ?? nil
184
+ cacheControl = dictionary [ " cacheControl " ] as? String ?? nil
185
+ contentDisposition = dictionary [ " contentDisposition " ] as? String ?? nil
186
+ contentEncoding = dictionary [ " contentEncoding " ] as? String ?? nil
187
+ contentLanguage = dictionary [ " contentLanguage " ] as? String ?? nil
188
+ contentType = dictionary [ " contentType " ] as? String ?? nil
189
+ customMetadata = dictionary [ " metadata " ] as? [ String : String ] ?? nil
190
+ size = StorageMetadata . intFromDictionaryValue ( dictionary [ " size " ] )
191
+ generation = StorageMetadata . intFromDictionaryValue ( dictionary [ " generation " ] )
192
+ metageneration = StorageMetadata . intFromDictionaryValue ( dictionary [ " metageneration " ] )
193
+ timeCreated = StorageMetadata . dateFromRFC3339String ( dictionary [ " timeCreated " ] )
194
+ updated = StorageMetadata . dateFromRFC3339String ( dictionary [ " updated " ] )
195
+ md5Hash = dictionary [ " md5Hash " ] as? String ?? nil
196
+
197
+ // GCS "name" is our path, our "name" is just the last path component of the path
198
+ if let name = dictionary [ " name " ] as? String {
199
+ path = name
200
+ self . name = ( name as NSString ) . lastPathComponent
201
+ }
202
+ fileType = . unknown
197
203
}
198
204
199
205
// MARK: - NSObject overrides
200
206
201
207
@objc override open func copy( ) -> Any {
202
- return StorageMetadata ( impl: impl. copy ( ) as! FIRIMPLStorageMetadata )
208
+ let clone = StorageMetadata ( dictionary: dictionaryRepresentation ( ) )
209
+ clone. initialMetadata = initialMetadata
210
+ return clone
203
211
}
204
212
205
213
@objc override open func isEqual( _ object: Any ? ) -> Bool {
206
214
guard let ref = object as? StorageMetadata else {
207
215
return false
208
216
}
209
- return impl . isEqual ( ref. impl )
217
+ return ref. dictionaryRepresentation ( ) == dictionaryRepresentation ( )
210
218
}
211
219
212
220
@objc override public var hash : Int {
213
- return impl . hash
221
+ return ( dictionaryRepresentation ( ) as NSDictionary ) . hash
214
222
}
215
223
216
224
@objc override public var description : String {
217
- return impl . description
225
+ return " \( type ( of : self ) ) \( dictionaryRepresentation ( ) ) "
218
226
}
219
227
220
228
// MARK: - Internal APIs
221
229
222
- internal let impl : FIRIMPLStorageMetadata
223
-
224
- internal init ( impl: FIRIMPLStorageMetadata ) {
225
- self . impl = impl
226
- bucket = impl. bucket
227
- md5Hash = impl. md5Hash
228
- generation = impl. generation
229
- metageneration = impl. metageneration
230
- size = impl. size
231
- timeCreated = impl. timeCreated
232
- updated = impl. updated
233
- if let ref = impl. storageReference {
234
- storageReference = StorageReference ( ref)
235
- } else {
236
- storageReference = nil
237
- }
238
- isFile = impl. isFile
239
- isFolder = impl. isFolder
230
+ internal func updatedMetadata( ) -> [ String : AnyHashable ] {
231
+ return remove ( matchingMetadata: dictionaryRepresentation ( ) , oldMetadata: initialMetadata)
232
+ }
233
+
234
+ internal enum StorageMetadataType {
235
+ case unknown
236
+ case file
237
+ case folder
238
+ }
239
+
240
+ internal var fileType : StorageMetadataType
241
+
242
+ // MARK: - Private APIs and data
243
+
244
+ private var initialMetadata : [ String : AnyHashable ]
245
+
246
+ private static func intFromDictionaryValue( _ value: Any ? ) -> Int64 {
247
+ if let value = value as? Int { return Int64 ( value) }
248
+ if let value = value as? Int64 { return value }
249
+ if let value = value as? String { return Int64 ( value) ?? 0 }
250
+ return 0
251
+ }
252
+
253
+ private static var dateFormatter : DateFormatter = {
254
+ let dateFormatter = DateFormatter ( )
255
+ dateFormatter. locale = Locale ( identifier: " en_US_POSIX " ) // set locale to reliable US_POSIX
256
+ dateFormatter. dateFormat = " yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZZZZZ "
257
+ dateFormatter. timeZone = TimeZone ( secondsFromGMT: 0 )
258
+ return dateFormatter
259
+ } ( )
260
+
261
+ private static func dateFromRFC3339String( _ value: Any ? ) -> Date ? {
262
+ if let stringValue = value as? String {
263
+ return dateFormatter. date ( from: stringValue) ?? nil
264
+ }
265
+ return nil
266
+ }
267
+
268
+ internal static func RFC3339StringFromDate( _ date: Date ) -> String {
269
+ return dateFormatter. string ( from: date)
270
+ }
271
+
272
+ private func remove( matchingMetadata: [ String : AnyHashable ] ,
273
+ oldMetadata: [ String : AnyHashable ] ) -> [ String : AnyHashable ] {
274
+ var newMetadata = matchingMetadata
275
+ for (key, oldValue) in oldMetadata {
276
+ guard let newValue = newMetadata [ key] else {
277
+ // Adds 'NSNull' for entries that only exist in oldMetadata.
278
+ newMetadata [ key] = NSNull ( )
279
+ continue
280
+ }
281
+ if let oldString = oldValue as? String ,
282
+ let newString = newValue as? String {
283
+ if oldString == newString {
284
+ newMetadata [ key] = nil
285
+ }
286
+ } else if let oldDictionary = oldValue as? [ String : AnyHashable ] ,
287
+ let newDictionary = newValue as? [ String : AnyHashable ] {
288
+ let outDictionary = remove ( matchingMetadata: newDictionary, oldMetadata: oldDictionary)
289
+ if outDictionary. count == 0 {
290
+ newMetadata [ key] = nil
291
+ } else {
292
+ newMetadata [ key] = outDictionary
293
+ }
294
+ }
295
+ }
296
+ return newMetadata
240
297
}
241
298
}
0 commit comments