Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions IOSAccessAssessment/LocalDataset/DatasetDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ struct DatasetCaptureData {
Finally, it also adds a node to TDEI workspaces at the capture location.
*/
class DatasetDecoder {
private var apiEnvironment: APIEnvironment
private var workspaceId: String

private var environmentDirectory: URL
private var workspaceDirectory: URL
private var datasetDirectory: URL
var totalFrames: Int = 0
Expand All @@ -79,11 +81,13 @@ class DatasetDecoder {
private let otherDetailsDecoder: OtherDetailsDecoder
private let meshDecoder: MeshDecoder

init(workspaceId: String, changesetId: String) throws {
init(apiEnvironment: APIEnvironment, workspaceId: String, changesetId: String) throws {
self.apiEnvironment = apiEnvironment
self.workspaceId = workspaceId

self.environmentDirectory = try DatasetDecoder.findDirectory(id: apiEnvironment.rawValue)
/// Get workspace directory
self.workspaceDirectory = try DatasetDecoder.findDirectory(id: workspaceId)
self.workspaceDirectory = try DatasetDecoder.findDirectory(id: workspaceId, relativeTo: self.environmentDirectory)
/// Get dataset directory
self.datasetDirectory = try DatasetDecoder.findDirectory(id: changesetId, relativeTo: self.workspaceDirectory)

Expand Down
9 changes: 7 additions & 2 deletions IOSAccessAssessment/LocalDataset/DatasetEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ enum DatasetEncoderError: Error, LocalizedError {
Finally, it also adds a node to TDEI workspaces at the capture location.
*/
class DatasetEncoder {
private var apiEnvironment: APIEnvironment
private var workspaceId: String

private var environmentDirectory: URL
private var workspaceDirectory: URL
private var datasetDirectory: URL
private var savedFrames: Int = 0
Expand Down Expand Up @@ -62,11 +64,14 @@ class DatasetEncoder {

public var capturedFrameIds: Set<UUID> = []

init(workspaceId: String, changesetId: String) throws {
init(apiEnvironment: APIEnvironment, workspaceId: String, changesetId: String) throws {
self.apiEnvironment = apiEnvironment
self.workspaceId = workspaceId

/// Create workspace Directory if it doesn't exist
self.workspaceDirectory = try DatasetEncoder.createDirectory(id: workspaceId)
self.environmentDirectory = try DatasetEncoder.createDirectory(id: apiEnvironment.rawValue)
/// if environment directory exists, create workspace directory inside it
self.workspaceDirectory = try DatasetEncoder.createDirectory(id: workspaceId, relativeTo: self.environmentDirectory)
/// if workspace directory exists, create dataset directory inside it
self.datasetDirectory = try DatasetEncoder.createDirectory(id: changesetId, relativeTo: self.workspaceDirectory)

Expand Down
147 changes: 122 additions & 25 deletions IOSAccessAssessment/LocalDataset/DatasetLister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,150 @@
import Foundation

enum DatasetListerError: Error, LocalizedError {
case invalidAPIEnvironment
case directoryRetrievalFailed
case indexDataNotFound(Int)

var errorDescription: String? {
switch self {
case .invalidAPIEnvironment:
return "Invalid API environment."
case .directoryRetrievalFailed:
return "Failed to retrieve dataset directory."
case .indexDataNotFound(let index):
return "Data for index \(index) not found."
}
}
}

struct EnvironmentDirectory: Identifiable, Comparable, Hashable {
let url: URL
let apiEnvironment: APIEnvironment

var id: URL {
return url
}

static func < (lhs: EnvironmentDirectory, rhs: EnvironmentDirectory) -> Bool {
return lhs.url.lastPathComponent < rhs.url.lastPathComponent
}
}

class DatasetLister {
var workspaceDirectories: [URL] = []
struct WorkspaceDirectory: Identifiable, Comparable, Hashable {
let url: URL
let workspaceId: String

var id: URL {
return url
}

static func < (lhs: WorkspaceDirectory, rhs: WorkspaceDirectory) -> Bool {
return lhs.url.lastPathComponent < rhs.url.lastPathComponent
}
}

struct ChangesetDirectory: Identifiable, Comparable, Hashable {
let url: URL
let changesetId: String

var id: URL {
return url
}

private var workspaceId: String? = nil
private var workspaceDirectory: URL? = nil
static func < (lhs: ChangesetDirectory, rhs: ChangesetDirectory) -> Bool {
return lhs.url.lastPathComponent < rhs.url.lastPathComponent
}
}

class DatasetLister: ObservableObject {
@Published var environmentDirectories: [EnvironmentDirectory] = []
@Published var workspaceDirectories: [WorkspaceDirectory] = []
@Published var changesetDirectories: [ChangesetDirectory] = []

var changesetDirectories: [URL] = []
@Published var selectedEnvironment: EnvironmentDirectory? = nil
@Published var selectedWorkspace: WorkspaceDirectory? = nil
@Published var selectedChangeset: ChangesetDirectory? = nil

func configure() throws {
self.workspaceDirectories = try DatasetLister.listWorkspaceDirectories()
self.environmentDirectories = try DatasetLister.listEnvironmentDirectories()
// self.workspaceDirectories = try DatasetLister.listWorkspaceDirectories()
}

func selectWorkspace(workspaceId: String) throws {
self.workspaceId = workspaceId
/// Get workspace directory
let workspaceDirectory = try self.findDirectory(id: workspaceId)
self.workspaceDirectory = workspaceDirectory
self.changesetDirectories = try self.listChangesetDirectories(workspaceDirectory: workspaceDirectory)
func selectEnvironment(environmentDirectory: EnvironmentDirectory) throws {
self.selectedEnvironment = environmentDirectory
self.workspaceDirectories = try DatasetLister.listWorkspaceDirectories(environmentDirectory: environmentDirectory)
}

/**
Finds all workspace directories within the app's document directory.
Finds all the environment directories within the app's document directory

A workspace directory is a number-named directory within the document directory, containing data for a specific workspace.
Each workspace directory is expected to contain at least one changeset directory, which is a number-named directory containing data for a specific changeset.
An environment directory is a string-named directory within the document directory, whose string name matches a specific API environment key.
Each environment directory is expected to contain at least one workspace directory, which is a number-named directory containing data for a specific workspace.
*/
static func listWorkspaceDirectories() throws -> [URL] {
static func listEnvironmentDirectories() throws -> [EnvironmentDirectory] {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw DatasetDecoderError.directoryRetrievalFailed
throw DatasetListerError.directoryRetrievalFailed
}
let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
var environmentDirectories: [EnvironmentDirectory] = contents.filter { content in
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: content.path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue && APIEnvironment(rawValue: content.lastPathComponent) != nil
}.compactMap { content in
if let apiEnvironment = APIEnvironment(rawValue: content.lastPathComponent) {
return EnvironmentDirectory(url: content, apiEnvironment: apiEnvironment)
} else {
return nil
}
}
environmentDirectories.sort()
return environmentDirectories
}


func selectWorkspace(workspaceDirectory: WorkspaceDirectory) throws {
self.selectedWorkspace = workspaceDirectory
self.changesetDirectories = try self.listChangesetDirectories(workspaceDirectory: workspaceDirectory)
}

// static func listWorkspaceDirectories() throws -> [URL] {
// guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
// throw DatasetListerError.directoryRetrievalFailed
// }
// let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
// var workspaceDirectories = contents.filter { content in
// var isDirectory: ObjCBool = false
// let exists = FileManager.default.fileExists(atPath: content.path, isDirectory: &isDirectory)
// return exists && isDirectory.boolValue && content.lastPathComponent.allSatisfy { $0.isNumber }
// }
// workspaceDirectories = workspaceDirectories.sorted { $0.lastPathComponent < $1.lastPathComponent }
// return workspaceDirectories
// }
static func listWorkspaceDirectories(environmentDirectory: EnvironmentDirectory) throws -> [WorkspaceDirectory] {
let contents = try FileManager.default.contentsOfDirectory(at: environmentDirectory.url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
var workspaceDirectories = contents.filter { content in
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: content.path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue && content.lastPathComponent.allSatisfy { $0.isNumber }
}.compactMap { content in
let workspaceId = content.lastPathComponent
return WorkspaceDirectory(url: content, workspaceId: workspaceId)
}
workspaceDirectories = workspaceDirectories.sorted { $0.lastPathComponent < $1.lastPathComponent }
workspaceDirectories.sort()
return workspaceDirectories
}

private func findDirectory(id: String, relativeTo: URL? = nil) throws -> URL {
var relativeTo = relativeTo
if relativeTo == nil {
guard let relativeToUrl = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask).first else {
throw DatasetDecoderError.directoryRetrievalFailed
throw DatasetListerError.directoryRetrievalFailed
}
relativeTo = relativeToUrl
}
let directory = URL(filePath: id, directoryHint: .isDirectory, relativeTo: relativeTo)
guard FileManager.default.fileExists(atPath: directory.path) else {
throw DatasetDecoderError.directoryRetrievalFailed
throw DatasetListerError.directoryRetrievalFailed
}
return directory
}
Expand All @@ -72,24 +162,31 @@ class DatasetLister {
Changesets are number-named directories within the workspace directory, each containing data for a specific changeset.
Each changeset directory is expected to contain an rgb directory, within which there is at least one .png file.
*/
private func listChangesetDirectories(workspaceDirectory: URL) throws -> [URL] {
let contents = try FileManager.default.contentsOfDirectory(at: workspaceDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
private func listChangesetDirectories(workspaceDirectory: WorkspaceDirectory) throws -> [ChangesetDirectory] {
let contents = try FileManager.default.contentsOfDirectory(at: workspaceDirectory.url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
let changesetDirectories = contents.filter { content in
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: content.path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue && content.lastPathComponent.allSatisfy { $0.isNumber }
}.compactMap { content in
let changesetId = content.lastPathComponent
return ChangesetDirectory(url: content, changesetId: changesetId)
}
var finalChangesetDirectories: [URL] = []
var finalChangesetDirectories: [ChangesetDirectory] = []
for changesetDirectory in changesetDirectories {
let rgbDirectory = changesetDirectory.appending(path: "rgb", directoryHint: .isDirectory)
let rgbDirectory = changesetDirectory.url.appending(path: "rgb", directoryHint: .isDirectory)
if FileManager.default.fileExists(atPath: rgbDirectory.path) {
let pngFiles = try FileManager.default.contentsOfDirectory(at: rgbDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).filter { $0.pathExtension.lowercased() == "png" }
if !pngFiles.isEmpty {
finalChangesetDirectories.append(changesetDirectory)
}
}
}
finalChangesetDirectories = finalChangesetDirectories.sorted { $0.lastPathComponent < $1.lastPathComponent }
finalChangesetDirectories.sort()
return finalChangesetDirectories
}

func selectChangeset(changesetDirectory: ChangesetDirectory) {
self.selectedChangeset = changesetDirectory
}
}
2 changes: 1 addition & 1 deletion IOSAccessAssessment/Shared/SharedAppConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct SharedAppConstants {
}

struct WorkspaceConstants {
static let primaryWorkspaceIds: [String] = ["1928"]
static let primaryWorkspaceIds: [String] = ["1940"]
// ["1463"]
// ["288", "349", "1411"]
// "252", "322", "368", "374", "378", "381", "384", "323", "369", "156", "375", "379"]
Expand Down
22 changes: 11 additions & 11 deletions IOSAccessAssessment/TDEI/Config/APIEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Foundation
enum APIEnvironment: String, CaseIterable, Sendable, Hashable {
// case development = "Development"
case staging = "Staging"
// case production = "Production"
case production = "Production"

static var `default`: APIEnvironment {
return .staging
Expand All @@ -25,8 +25,8 @@ enum APIEnvironment: String, CaseIterable, Sendable, Hashable {
// return "https://tdei-api-dev.azurewebsites.net/api/v1"
case .staging:
return "https://tdei-gateway-stage.azurewebsites.net/api/v1"
// case .production:
// return "https://tdei-gateway-prod.azurewebsites.net/api/v1"
case .production:
return "https://tdei-gateway-prod.azurewebsites.net/api/v1"
}
}

Expand All @@ -36,8 +36,8 @@ enum APIEnvironment: String, CaseIterable, Sendable, Hashable {
// return "https://api.workspaces-dev.sidewalks.washington.edu/api/v1"
case .staging:
return "https://api.workspaces-stage.sidewalks.washington.edu/api/v1"
// case .production:
// return "https://api.workspaces.sidewalks.washington.edu/api/v1"
case .production:
return "https://api.workspaces.sidewalks.washington.edu/api/v1"
}
}

Expand All @@ -47,8 +47,8 @@ enum APIEnvironment: String, CaseIterable, Sendable, Hashable {
// return "https://osm.workspaces-dev.sidewalks.washington.edu/api/0.6"
case .staging:
return "https://osm.workspaces-stage.sidewalks.washington.edu/api/0.6"
// case .production:
// return "https://osm.workspaces.sidewalks.washington.edu/api/0.6"
case .production:
return "https://osm.workspaces.sidewalks.washington.edu/api/0.6"
}
}

Expand All @@ -58,8 +58,8 @@ enum APIEnvironment: String, CaseIterable, Sendable, Hashable {
// return "https://tdei-usermanagement-be-dev.azurewebsites.net/api/v1"
case .staging:
return "https://tdei-usermanagement-stage.azurewebsites.net/api/v1"
// case .production:
// return "https://tdei-usermanagement-prod.azurewebsites.net/api/v1"
case .production:
return "https://tdei-usermanagement-prod.azurewebsites.net/api/v1"
}
}

Expand All @@ -69,8 +69,8 @@ enum APIEnvironment: String, CaseIterable, Sendable, Hashable {
// return "Development"
case .staging:
return "Staging"
// case .production:
// return "Production"
case .production:
return "Production"
}
}
}
11 changes: 8 additions & 3 deletions IOSAccessAssessment/View/SetupView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,14 @@ struct SetupView: View {
if userStateViewModel.appMode == .standard {
ARCameraView(selectedClasses: Array(self.selectedClasses).sorted())
} else {
TestWorkspaceListView(selectedClasses: Array(self.selectedClasses).sorted())
TestEnvironmentListView(selectedClasses: Array(self.selectedClasses).sorted())
}
}

private func openChangeset() {
Task {
do {
let selectedEnvironment = userStateViewModel.selectedEnvironment
guard let workspaceId = workspaceViewModel.workspaceId else {
throw SetupViewError.noWorkspaceId
}
Expand All @@ -470,7 +471,10 @@ struct SetupView: View {
)
workspaceViewModel.updateChangeset(id: openedChangesetId)
changesetOpenViewModel.isChangesetOpened = true
try initializeCurrentDataset(workspaceId: workspaceId, changeSetId: openedChangesetId)
try initializeCurrentDataset(
apiEnvironment: selectedEnvironment,
workspaceId: workspaceId, changeSetId: openedChangesetId
)
} catch SetupViewError.currentDatasetInitializationFailed {
setCurrentDatasetStatusErrorHint(SetupViewError.currentDatasetInitializationFailed.localizedDescription)
} catch {
Expand All @@ -483,9 +487,10 @@ struct SetupView: View {
}
}

private func initializeCurrentDataset(workspaceId: String, changeSetId: String) throws {
private func initializeCurrentDataset(apiEnvironment: APIEnvironment, workspaceId: String, changeSetId: String) throws {
do {
sharedAppData.currentDatasetEncoder = try DatasetEncoder(
apiEnvironment: apiEnvironment,
workspaceId: workspaceId, changesetId: changeSetId
)
} catch {
Expand Down
6 changes: 5 additions & 1 deletion IOSAccessAssessment/View/TestMode/TestCameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class LocationManagerPlaceholder: NSObject, ObservableObject {
*/
struct TestCameraView: View {
let selectedClasses: [AccessibilityFeatureClass]
let selectedEnvironment: APIEnvironment
let workspaceId: String
let changesetId: String

Expand Down Expand Up @@ -328,7 +329,10 @@ struct TestCameraView: View {
}

private func initializeDatasetDecoder() throws -> DatasetDecoder {
return try DatasetDecoder(workspaceId: workspaceId, changesetId: changesetId)
return try DatasetDecoder(
apiEnvironment: selectedEnvironment,
workspaceId: workspaceId, changesetId: changesetId
)
}

private func loadData(datasetDecoder: DatasetDecoder, enhancedAnalysisMode: Bool) throws -> DatasetCaptureData {
Expand Down
Loading