Skip to content

Commit

Permalink
update: RegionsService
Browse files Browse the repository at this point in the history
  • Loading branch information
aaryankotharii committed Mar 16, 2024
1 parent 62a6b33 commit cfcae1c
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 134 deletions.
Binary file added .DS_Store
Binary file not shown.
Binary file added OBAKitCore/.DS_Store
Binary file not shown.
Binary file added OBAKitCore/Location/.DS_Store
Binary file not shown.
Binary file added OBAKitCore/Location/Regions/.DS_Store
Binary file not shown.
118 changes: 0 additions & 118 deletions OBAKitCore/Location/Regions/FileManagerProtocol.swift

This file was deleted.

28 changes: 17 additions & 11 deletions OBAKitCore/Location/Regions/RegionsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class RegionsService: NSObject, LocationServiceDelegate {
private let apiService: RegionsAPIService?
private let locationService: LocationService
private let userDefaults: UserDefaults
private let fileManager: FileManagerProtocol
private let fileManager: RegionsServiceFileManagerProtocol
private let bundledRegionsFilePath: String
private let apiPath: String?

Expand All @@ -45,7 +45,7 @@ public class RegionsService: NSObject, LocationServiceDelegate {
/// - bundledRegionsFilePath: The path to the bundled regions file. It is probably named "regions.json" or something similar.
/// - apiPath: The path to the remote regions.json file on the server. e.g. /path/to/regions.json
/// - delegate: A delegate object for callbacks.
public init(apiService: RegionsAPIService?, locationService: LocationService, userDefaults: UserDefaults, fileManager: FileManagerProtocol, bundledRegionsFilePath: String, apiPath: String?, delegate: RegionsServiceDelegate? = nil) {
public init(apiService: RegionsAPIService?, locationService: LocationService, userDefaults: UserDefaults, fileManager: RegionsServiceFileManagerProtocol, bundledRegionsFilePath: String, apiPath: String?, delegate: RegionsServiceDelegate? = nil) {
self.apiService = apiService
self.locationService = locationService
self.userDefaults = userDefaults
Expand Down Expand Up @@ -209,7 +209,8 @@ public class RegionsService: NSObject, LocationServiceDelegate {
/// Adds the provided custom region to the RegionsService.
/// - throws: File system errors.
public func add(customRegion newRegion: Region) async throws {
try fileManager.save(newRegion, to: .customRegion(newRegion.regionIdentifier))
let customRegionPath = RegionsService.getCustomRegionPath(customRegionIdentifier: newRegion.regionIdentifier)
try fileManager.save(newRegion, to: customRegionPath)
}

/// Deletes the custom region. If the region could not be found, this method exits normally.
Expand All @@ -223,23 +224,22 @@ public class RegionsService: NSObject, LocationServiceDelegate {
/// - parameter identifier: The region identifier used to find the custom region to delete.
/// - throws: If a custom region with the provided `identifier` could not be found, or is not a custom region, this method will throw.
public func delete(customRegionIdentifier identifier: RegionIdentifier) async throws {
var regions = customRegions


guard self.currentRegion?.regionIdentifier != identifier else {
throw UnstructuredError(
"Cannot delete the current selected region",
recoverySuggestion: "Choose a different region to be the currently selected region, before deleting this region.")
}

try fileManager.remove(at: .customRegion(identifier))
let customRegionPath = RegionsService.getCustomRegionPath(customRegionIdentifier: identifier)
try fileManager.remove(at: customRegionPath)
}

/// Retrieves an array of custom regions loaded from files in the custom regions directory.
/// -1 passed in customRegion case since identifier param not relevant
/// - Returns: An array of `Region` objects representing custom regions.
public var customRegions: [Region] {
var regions: [Region] = []
if let fileURLs = try? fileManager.urls(at: .customRegion(-1)) {
if let fileURLs = try? fileManager.urls(at: RegionsService.customRegionsPath) {
for url in fileURLs {
if let region = try? fileManager.load(Region.self, from: url) {
regions.append(region)
Expand All @@ -259,12 +259,18 @@ public class RegionsService: NSObject, LocationServiceDelegate {
static let automaticallySelectRegionUserDefaultsKey = "OBAAutomaticallySelectRegionUserDefaultsKey"
static let currentRegionUserDefaultsKey = "OBACurrentRegionUserDefaultsKey"
static let regionsUpdatedAtUserDefaultsKey = "OBARegionsUpdatedAtUserDefaultsKey"
static let defaultRegionsPath = URL.applicationSupportDirectory.appendingPathComponent("default-regions.json")
static let customRegionsPath = URL.documentsDirectory.appendingPathComponent("Regions/custom-regions")

private class func getCustomRegionPath(customRegionIdentifier identifier: RegionIdentifier) -> URL {
return customRegionsPath.appendingPathComponent("region-\(identifier).json")
}

// MARK: - Save Regions

private func storeRegions() {
do {
try fileManager.save(regions, to: .defaultRegion)
try fileManager.save(regions, to: RegionsService.defaultRegionsPath)
userDefaults.set(Date(), forKey: RegionsService.regionsUpdatedAtUserDefaultsKey)
}
catch {
Expand All @@ -274,11 +280,11 @@ public class RegionsService: NSObject, LocationServiceDelegate {

// MARK: - Load Stored Regions

private class func loadStoredRegions(from fileManager: FileManagerProtocol) -> [Region]? {
private class func loadStoredRegions(from fileManager: RegionsServiceFileManagerProtocol) -> [Region]? {
let regions: [Region]

do {
regions = try fileManager.load([Region].self, from: .defaultRegion)
regions = try fileManager.load([Region].self, from: RegionsService.defaultRegionsPath)
} catch {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// RegionsServiceFileManagerProtocol.swift
// OBAKit
//
// Copyright © Open Transit Software Foundation
// This source code is licensed under the Apache 2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

import Foundation

/// A protocol defining file management operations.
public protocol RegionsServiceFileManagerProtocol: AnyObject {

/// Saves the Codable object to the specified directory with the given name.
///
/// - Parameters:
/// - object: The Codable object to save.
/// - destination: The destination specifying where to save the object.
func save<T: Codable>(_ object: T, to destination: URL) throws

/// Loads a Codable object of the specified type from the file with the given URL.
///
/// - Parameters:
/// - type: The type of the Codable object to load.
/// - fileURL: The URL of the file to load the object from.
/// - Returns: The decoded Codable object.
func load<T: Codable>(_ type: T.Type, from fileURL: URL) throws -> T

/// Removes the file at the specified destination.
///
/// - Parameters:
/// - destination: The destination specifying which file to remove.
func remove(at destination: URL) throws

/// Returns an array of URLs for the items at the specified URL.
///
/// - Parameters:
/// - destination: The destination specifying where to load contents of.
/// - Returns: An array of URLs for the items in the directory.
func urls(at destination: URL) throws -> [URL]

}

extension FileManager: RegionsServiceFileManagerProtocol {

/// Creates a directory at the specified URL if it doesn't already exist.
private func createDirectoryIfNeeded(at url: URL) throws {
guard !fileExists(atPath: url.path) else { return }
try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
}

public func urls(at destination: URL) throws -> [URL] {
return try contentsOfDirectory(at: destination, includingPropertiesForKeys: nil)
}

public func load<T: Codable>(_ type: T.Type, from fileURL: URL) throws -> T {
let data = try Data(contentsOf: fileURL)
return try JSONDecoder().decode(T.self, from: data)
}

public func save<T: Codable>(_ object: T, to destination: URL) throws {
let directoryURL = destination.deletingLastPathComponent()
try createDirectoryIfNeeded(at: directoryURL)
let encoded = try JSONEncoder().encode(object)
try encoded.write(to: destination)
}

public func remove(at destination: URL) throws {
guard fileExists(atPath: destination.path) else { return }
try removeItem(at: destination)
}

}
2 changes: 1 addition & 1 deletion OBAKitCore/Orchestration/CoreApplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ open class CoreApplication: NSObject,
@objc public let userDefaults: UserDefaults

/// Default file manager.
private let fileManager: FileManagerProtocol
private let fileManager: RegionsServiceFileManagerProtocol

/// The underlying implementation of our data stores.
private let userDefaultsStore: UserDefaultsStore
Expand Down
8 changes: 4 additions & 4 deletions OBAKitTests/Location/RegionsServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class RegionsServiceTests: OBATestCase {
var locationManagerMock: LocationManagerMock!
var locationService: LocationService!
var dataLoader: MockDataLoader!
var fileManager: FileManagerProtocol!
var fileManager: RegionsServiceFileManagerProtocol!

override func setUp() {
super.setUp()
Expand Down Expand Up @@ -84,11 +84,11 @@ class RegionsServiceTests: OBATestCase {
stubRegions(dataLoader: dataLoader)

let customRegion = Fixtures.customMinneapolisRegion
let plistArrayData = try PropertyListEncoder().encode([customRegion])
let plistArrayData = try JSONEncoder().encode([customRegion])
try fileManager.save(plistArrayData, to: .defaultRegion)
userDefaults.set(false, forKey: RegionsService.automaticallySelectRegionUserDefaultsKey)

let plistData = try PropertyListEncoder().encode(customRegion)
let plistData = try JSONEncoder().encode(customRegion)
userDefaults.set(plistData, forKey: RegionsService.currentRegionUserDefaultsKey)

let service = RegionsService(apiService: regionsAPIService, locationService: locationService, userDefaults: userDefaults, fileManager: fileManager, bundledRegionsFilePath: bundledRegionsPath, apiPath: regionsPath)
Expand All @@ -99,7 +99,7 @@ class RegionsServiceTests: OBATestCase {
func test_init_loadsCurrentRegion_autoSelectEnabled() throws {
stubRegions(dataLoader: dataLoader)

let plistData = try PropertyListEncoder().encode(Fixtures.customMinneapolisRegion)
let plistData = try JSONEncoder().encode(Fixtures.customMinneapolisRegion)
userDefaults.set(plistData, forKey: RegionsService.currentRegionUserDefaultsKey)
locationManagerMock.location = CLLocation(latitude: 47.632445, longitude: -122.312607)

Expand Down

0 comments on commit cfcae1c

Please sign in to comment.