Skip to content

Commit

Permalink
Add some async functions to StorageAPI
Browse files Browse the repository at this point in the history
  • Loading branch information
sp4ce-cowboy committed Apr 20, 2024
1 parent 701aef5 commit ae7c96f
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 57 deletions.
10 changes: 5 additions & 5 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
BA436AEC2BD42F7800BE3E4F /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */; };
BA436AEE2BD42F8100BE3E4F /* RemoteStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */; };
BA436AF02BD437D900BE3E4F /* LocalStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */; };
BA436AF22BD443A500BE3E4F /* RemoteStorage+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */; };
BA436AF22BD443A500BE3E4F /* RemoteStorage+Access.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */; };
BA443D3D2BAD9557009F0FFB /* RemoveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */; };
BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */; };
BA443D422BAD9885009F0FFB /* DamageEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA443D412BAD9885009F0FFB /* DamageEventTests.swift */; };
Expand Down Expand Up @@ -487,7 +487,7 @@
BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorage.swift; sourceTree = "<group>"; };
BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorage.swift; sourceTree = "<group>"; };
BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalStorage+Metadata.swift"; sourceTree = "<group>"; };
BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Metadata.swift"; sourceTree = "<group>"; };
BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteStorage+Access.swift"; sourceTree = "<group>"; };
BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveSystem.swift; sourceTree = "<group>"; };
BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEvent.swift; sourceTree = "<group>"; };
BA443D412BAD9885009F0FFB /* DamageEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageEventTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1055,10 +1055,11 @@
isa = PBXGroup;
children = (
BA436AE92BD42F5400BE3E4F /* StorageHandler.swift */,
BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */,
BA436AEB2BD42F7800BE3E4F /* LocalStorage.swift */,
BA436AEF2BD437D900BE3E4F /* LocalStorage+Metadata.swift */,
BA436AED2BD42F8100BE3E4F /* RemoteStorage.swift */,
BA436AF12BD443A500BE3E4F /* RemoteStorage+Metadata.swift */,
BA436AF12BD443A500BE3E4F /* RemoteStorage+Access.swift */,
);
path = StorageAPI;
sourceTree = "<group>";
Expand Down Expand Up @@ -1430,7 +1431,6 @@
BAFFB9512BB342E200D8301F /* Storage */ = {
isa = PBXGroup;
children = (
BAEC99FB2BD15AAB00E0C437 /* StorageDatabase.swift */,
BA82C76E2BCBDE91000515A0 /* Metadata.swift */,
BA82C77A2BCD05DC000515A0 /* MetadataManager.swift */,
BA82C76A2BCBD682000515A0 /* StorageManager.swift */,
Expand Down Expand Up @@ -1721,7 +1721,7 @@
52DF5FF92BA35D2B00135367 /* MovableComponent.swift in Sources */,
BA436AEA2BD42F5400BE3E4F /* StorageHandler.swift in Sources */,
5299D1342BC31067003EF746 /* AuthenticationProtocol.swift in Sources */,
BA436AF22BD443A500BE3E4F /* RemoteStorage+Metadata.swift in Sources */,
BA436AF22BD443A500BE3E4F /* RemoteStorage+Access.swift in Sources */,
527A07822BB3F8D300CD9D08 /* TimerProp.swift in Sources */,
52DF5FDE2BA32D7E00135367 /* EntityManager.swift in Sources */,
3CBE72FB2BC8D63E00CC446A /* RemoteDamageEvent.swift in Sources */,
Expand Down
26 changes: 14 additions & 12 deletions TowerForge/TowerForge/Metrics/Statistics/Statistic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,6 @@ extension Statistic {
self.getStatisticUpdateLinks().getAllEventTypes()
}

/*func update<T: TFEvent>(for event: T) {
let eventType = T.asType
guard let updateLink = self.getStatisticUpdateLinks().getStatisticUpdateActor(for: eventType) else {
return
}

updateLink.updateStatistic(statistic: self, withEvent: T.self)

updateLink?(self)
Logger.log("Value update for eventType \(eventType)", self)
}*/

/// Updates the statistic according to an UpdateActor that is retrieved from the
/// StatisticsUpdateLinkDatabase
func update<T: TFEvent>(for event: T) {
Expand Down Expand Up @@ -186,3 +174,17 @@ extension Statistic {
self.init(permanentValue: value, currentValue: current, maxCurrentValue: max)
}
}

/// This extension allows Statistic to be merged
extension Statistic {

static func merge(this: Self, that: Self) -> Self {
let largerPermanent = Double.maximum(this.permanentValue, that.permanentValue)
let largerCurrent = Double.maximum(this.currentValue, that.currentValue)
let largerMaxCurrent = Double.maximum(this.maximumCurrentValue, that.maximumCurrentValue)

return Self(permanentValue: largerPermanent,
currentValue: largerCurrent,
maxCurrentValue: largerMaxCurrent)
}
}
2 changes: 1 addition & 1 deletion TowerForge/TowerForge/Storage/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
///
/// - Information about device and the current user for use with Remote Storage
/// - Meta-information about files stored locally, possibly for use with conflict resolution.
class Metadata: Codable, Comparable, Equatable {
class Metadata: StorageDatabase, Comparable, Equatable {
static var currentPlayerId: String { Constants.CURRENT_PLAYER_ID }
static var currentDeviceId: String { Constants.CURRENT_DEVICE_ID }

Expand Down
145 changes: 145 additions & 0 deletions TowerForge/TowerForge/StorageAPI/RemoteStorage+Access.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//
// RemoteStorage+Metadata.swift
// TowerForge
//
// Created by Rubesh on 21/4/24.
//

import Foundation

/// This class adds utility methods specifically for access operations. Given
/// the nature of the remote backend, closures are used for async operations.
/// This class abstracts away the closure invocation with default access
/// functions, for both storage and metadata.
extension RemoteStorage {

/// Checks if a player's metadata exists without requiring a closure input
/*static func checkIfPlayerMetadataExists(for playerId: String) -> Bool {
var exists = false

Self.remoteStorageExists(for: .Metadata, player: playerId) { bool in
exists = bool
}

return exists
}*/

static func checkIfPlayerMetadataExistsAsync(for playerId: String) async -> Bool {
await withCheckedContinuation { continuation in
Self.remoteStorageExists(for: .Metadata, player: playerId) { exists in
continuation.resume(returning: exists)
}
}
}

/// Checks if a player's statistics exists without requiring a closure input
static func checkIfPlayerStorageExists(for playerId: String) -> Bool {
var exists = false

Self.remoteStorageExists(for: .Statistics, player: playerId) { bool in
exists = bool
}

return exists
}

static func saveMetadataToFirebase(player: String, with inputData: Metadata) {
let metadataCompletion: (Error?) -> Void = { error in
if let error = error {
Logger.log("Saving metadata to firebase error: \(error)", self)
} else {
Logger.log("Saving metadata to firebase success", self)
}
}

Self.saveDataToFirebase(for: .Metadata,
player: player,
with: inputData,
completion: metadataCompletion)
}

static func saveStorageToFirebase(player: String, with inputData: StatisticsDatabase) {
let storageCompletion: (Error?) -> Void = { error in
if let error = error {
Logger.log("Saving storage to firebase error: \(error)", self)
} else {
Logger.log("Saving storage to firebase success", self)
}
}

Self.saveDataToFirebase(for: .Statistics,
player: player,
with: inputData,
completion: storageCompletion)
}

/// Deletes metadata for the specified player from firebase
static func deleteMetadataFromFirebase(player: String) {
let completion: (Error?) -> Void = { error in
if let error = error {
Logger.log("Deleting metadata from firebase error: \(error)", self)
} else {
Logger.log("Saving Metadata from firebase success", self)
}
}

Self.deleteDataFromFirebase(for: .Metadata, player: player, completion: completion)
}

/// Deletes storage for the specific player from Firebase
static func deleteStorageFromFirebase(player: String) {
let completion: (Error?) -> Void = { error in
if let error = error {
Logger.log("Deleting storage from firebase error: \(error)", self)
} else {
Logger.log("Saving storage from firebase success", self)
}
}

Self.deleteDataFromFirebase(for: .Statistics, player: player, completion: completion)
}

static func loadStorageFromFirebase(player: String) -> StatisticsDatabase? {
guard Self.checkIfPlayerStorageExists(for: player) else {
return nil
}

var stats: StatisticsDatabase?
Self.loadDataFromFirebase(for: .Statistics,
player: player) { (statisticsDatabase: StatisticsDatabase?, error: Error?) in
if let error = error {
Logger.log("Error loading storage from firebase: \(error)", self)
} else if let statistics = statisticsDatabase {
Logger.log("Successfully loaded statistics from firebase", self)
stats = statistics
} else {
// No error and no database implies that database is empty, return nil
Logger.log("No error and empty database, new one will NOT be auto-created", self)
}
}

return stats
}

static func loadMetadataFromFirebase(player: String) async -> Metadata? {
guard await Self.checkIfPlayerMetadataExistsAsync(for: player) else {
return nil
}

var metadata: Metadata?
Self.loadDataFromFirebase(for: .Statistics,
player: player) { (remoteMetadata: Metadata?, error: Error?) in
if let error = error {
Logger.log("Error loading storage from firebase: \(error)", self)
} else if let currentMetadata = remoteMetadata {
Logger.log("Successfully loaded statistics from firebase", self)
metadata = currentMetadata
} else {
// No error and no database implies that database is empty, return nil
Logger.log("No error and empty database, new one will NOT be auto-created", self)
}
}

return metadata
}
}
23 changes: 0 additions & 23 deletions TowerForge/TowerForge/StorageAPI/RemoteStorage+Metadata.swift

This file was deleted.

76 changes: 70 additions & 6 deletions TowerForge/TowerForge/StorageAPI/RemoteStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,79 @@ class RemoteStorage {
})
}

/// Checks if a player's statistics exists without requiring a closure input
static func checkIfPlayerStorageExists(for playerId: String) -> Bool {
var exists = false
/// Saves the input StorageDatabase to firebase
static func saveDataToFirebase(for ref: FirebaseReference,
player: String,
with inputData: StorageDatabase,
completion: @escaping (Error?) -> Void) {
let databaseReference = FirebaseDatabaseReference(ref)

do {
let encoder = JSONEncoder()
let data = try encoder.encode(inputData)
let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]

RemoteStorage.remoteStorageExists(for: .Statistics, player: playerId) { bool in
exists = bool
databaseReference.child(player).setValue(dictionary) { error, _ in
if let error = error {
Logger.log("Data could not be saved: \(error).", Self.self)
completion(error)
} else {
Logger.log("Data saved to Firebase successfully!", Self.self)
completion(nil)
}
}
} catch {
Logger.log("Error encoding Data: \(error)", Self.self)
completion(error)
}
}

/// Saves the input StorageDatabase to firebase
static func deleteDataFromFirebase(for ref: FirebaseReference,
player: String,
completion: @escaping (Error?) -> Void) {
let databaseReference = FirebaseDatabaseReference(ref)

// Remove the data at the specific player node
databaseReference.child(player).removeValue { error, _ in
if let error = error {
Logger.log("Error deleting data: \(error).", self)
completion(error)
return
}

Logger.log("Data for player \(player) successfully deleted from Firebase.", self)
completion(nil)
}
}

return exists
static func loadDataFromFirebase<T: StorageDatabase>(for ref: FirebaseReference,
player: String,
completion: @escaping (T?, Error?) -> Void) {
let databaseReference = FirebaseDatabaseReference(ref)

databaseReference.child(player).getData(completion: { error, snapshot in
if let error = error {
Logger.log("Error loading data from firebase: \(error.localizedDescription)", self)
completion(nil, error)
return
}

guard let value = snapshot?.value as? [String: Any],
let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []) else {
completion(nil, nil)
return
}

do {
let decoder = JSONDecoder()
let storageDatabase = try decoder.decode(T.self, from: jsonData)
completion(storageDatabase, nil)
} catch {
Logger.log("Error decoding StatisticsDatabase from Firebase: \(error)", self)
completion(nil, error)
}
})
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

protocol StorageDatabase: Codable {
protocol StorageDatabase: Codable, AnyObject {
func encode(to encoder: Encoder) throws
init(from decoder: Decoder) throws
}
Loading

0 comments on commit ae7c96f

Please sign in to comment.