Skip to content

Commit dc394a5

Browse files
committed
Convert StorageMetadata to Swift (#9979)
1 parent 42877f5 commit dc394a5

22 files changed

+420
-930
lines changed

FirebaseStorage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
- [changed] Remove the storageReference property of StorageMetadata. It had never been implemented
3+
and always returned `nil`.
4+
15
# 9.2.0
26
- [fixed] Importing FirebaseStorage no longer exposes internal FirebaseCore APIs. (#9884)
37

FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ internal class StorageGetMetadataTask: StorageTask, StorageTaskManagement {
6969
} else {
7070
if let data = data,
7171
let responseDictionary = try? JSONSerialization
72-
.jsonObject(with: data) as? [String: Any] {
72+
.jsonObject(with: data) as? [String: AnyHashable] {
7373
metadata = StorageMetadata(dictionary: responseDictionary)
74-
metadata?.impl.type = .file
74+
metadata?.fileType = .file
7575
} else {
7676
self.error = StorageErrorCode.error(withInvalidRequest: data)
7777
}

FirebaseStorage/Sources/Internal/StorageUpdateMetadataTask.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ internal class StorageUpdateMetadataTask: StorageTask, StorageTaskManagement {
2828
private var fetcher: GTMSessionFetcher?
2929
private var fetcherCompletion: ((Data?, NSError?) -> Void)?
3030
private var taskCompletion: ((_ metadata: StorageMetadata?, _: Error?) -> Void)?
31-
private var updateMetadata: FIRIMPLStorageMetadata
31+
private var updateMetadata: StorageMetadata
3232

3333
internal init(reference: FIRIMPLStorageReference,
3434
fetcherService: GTMSessionFetcherService,
3535
queue: DispatchQueue,
36-
metadata: FIRIMPLStorageMetadata,
36+
metadata: StorageMetadata,
3737
completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
3838
updateMetadata = metadata
3939
super.init(reference: reference, service: fetcherService, queue: queue)
@@ -78,9 +78,9 @@ internal class StorageUpdateMetadataTask: StorageTask, StorageTaskManagement {
7878
} else {
7979
if let data = data,
8080
let responseDictionary = try? JSONSerialization
81-
.jsonObject(with: data) as? [String: Any] {
81+
.jsonObject(with: data) as? [String: AnyHashable] {
8282
metadata = StorageMetadata(dictionary: responseDictionary)
83-
metadata?.impl.type = .file
83+
metadata?.fileType = .file
8484
} else {
8585
self.error = StorageErrorCode.error(withInvalidRequest: data)
8686
}

FirebaseStorage/Sources/StorageMetadata.swift

Lines changed: 157 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -29,67 +29,31 @@ import FirebaseStorageInternal
2929
/**
3030
* The name of the bucket containing this object.
3131
*/
32-
@objc public let bucket: String
32+
@objc public let bucket: String?
3333

3434
/**
3535
* Cache-Control directive for the object data.
3636
*/
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?
4638
/**
4739
* Content-Disposition of the object data.
4840
*/
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?
5742

5843
/**
5944
* Content-Encoding of the object data.
6045
*/
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?
6947

7048
/**
7149
* Content-Language of the object data.
7250
*/
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?
8152

8253
/**
8354
* Content-Type of the object data.
8455
*/
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?
9357

9458
/**
9559
* MD5 hash of the data; encoded using base64.
@@ -104,14 +68,7 @@ import FirebaseStorageInternal
10468
/**
10569
* User-provided metadata, in key/value pairs.
10670
*/
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]?
11572

11673
/**
11774
* The version of the metadata for this object at this generation. Used
@@ -123,26 +80,12 @@ import FirebaseStorageInternal
12380
/**
12481
* The name of this object, in gs://bucket/path/to/object.txt, this is object.txt.
12582
*/
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?
13484

13585
/**
13686
* The full path of this object, in gs://bucket/path/to/object.txt, this is path/to/object.txt.
13787
*/
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?
14689

14790
/**
14891
* Content-Length of the data in bytes.
@@ -159,83 +102,197 @@ import FirebaseStorageInternal
159102
*/
160103
@objc public let updated: Date?
161104

162-
/**
163-
* A reference to the object in Firebase Storage.
164-
*/
165-
@objc public let storageReference: StorageReference?
166-
167105
/**
168106
* Creates a Dictionary from the contents of the metadata.
169107
* @return A Dictionary that represents the contents of the metadata.
170108
*/
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
173154
}
174155

175156
/**
176157
* Determines if the current metadata represents a "file".
177158
*/
178-
@objc public let isFile: Bool
159+
@objc public var isFile: Bool {
160+
return fileType == .file
161+
}
179162

163+
// TODO: Nothing in the implementation ever sets `.folder`.
180164
/**
181165
* Determines if the current metadata represents a "folder".
182166
*/
183-
@objc public let isFolder: Bool
167+
@objc public var isFolder: Bool {
168+
return fileType == .folder
169+
}
184170

185171
// MARK: - Public Initializers
186172

187173
@objc override public convenience init() {
188-
self.init(impl: FIRIMPLStorageMetadata(dictionary: [:])!)
174+
self.init(dictionary: [:])
189175
}
190176

191177
/**
192178
* Creates an instance of StorageMetadata from the contents of a dictionary.
193179
* @return An instance of StorageMetadata that represents the contents of a dictionary.
194180
*/
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
197203
}
198204

199205
// MARK: - NSObject overrides
200206

201207
@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
203211
}
204212

205213
@objc override open func isEqual(_ object: Any?) -> Bool {
206214
guard let ref = object as? StorageMetadata else {
207215
return false
208216
}
209-
return impl.isEqual(ref.impl)
217+
return ref.dictionaryRepresentation() == dictionaryRepresentation()
210218
}
211219

212220
@objc override public var hash: Int {
213-
return impl.hash
221+
return (dictionaryRepresentation() as NSDictionary).hash
214222
}
215223

216224
@objc override public var description: String {
217-
return impl.description
225+
return "\(type(of: self)) \(dictionaryRepresentation())"
218226
}
219227

220228
// MARK: - Internal APIs
221229

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
240297
}
241298
}

0 commit comments

Comments
 (0)