diff --git a/packages/swift-client/Sources/SpacedriveClient/SpacedriveAPI.swift b/packages/swift-client/Sources/SpacedriveClient/SpacedriveAPI.swift index f66feaa33..a9bf35749 100644 --- a/packages/swift-client/Sources/SpacedriveClient/SpacedriveAPI.swift +++ b/packages/swift-client/Sources/SpacedriveClient/SpacedriveAPI.swift @@ -1,284 +1,19 @@ import Foundation -/// Network operations -public struct NetworkAPI { +/// Search operations +public struct SearchAPI { private let client: SpacedriveClient init(client: SpacedriveClient) { self.client = client } - /// Execute action: network.stop - public func stop(_ input: NetworkStopInput) async throws -> NetworkStopOutput { + /// Execute query: search.files + public func files(_ input: FileSearchInput) async throws -> FileSearchOutput { return try await client.execute( input, - method: "action:network.stop.input.v1", - responseType: NetworkStopOutput.self - ) - } - - /// Execute action: network.device.revoke - public func deviceRevoke(_ input: DeviceRevokeInput) async throws -> DeviceRevokeOutput { - return try await client.execute( - input, - method: "action:network.device.revoke.input.v1", - responseType: DeviceRevokeOutput.self - ) - } - - /// Execute action: network.pair.generate - public func pairGenerate(_ input: PairGenerateInput) async throws -> PairGenerateOutput { - return try await client.execute( - input, - method: "action:network.pair.generate.input.v1", - responseType: PairGenerateOutput.self - ) - } - - /// Execute action: network.start - public func start(_ input: NetworkStartInput) async throws -> NetworkStartOutput { - return try await client.execute( - input, - method: "action:network.start.input.v1", - responseType: NetworkStartOutput.self - ) - } - - /// Execute action: network.spacedrop.send - public func spacedropSend(_ input: SpacedropSendInput) async throws -> SpacedropSendOutput { - return try await client.execute( - input, - method: "action:network.spacedrop.send.input.v1", - responseType: SpacedropSendOutput.self - ) - } - - /// Execute action: network.pair.cancel - public func pairCancel(_ input: PairCancelInput) async throws -> PairCancelOutput { - return try await client.execute( - input, - method: "action:network.pair.cancel.input.v1", - responseType: PairCancelOutput.self - ) - } - - /// Execute action: network.pair.join - public func pairJoin(_ input: PairJoinInput) async throws -> PairJoinOutput { - return try await client.execute( - input, - method: "action:network.pair.join.input.v1", - responseType: PairJoinOutput.self - ) - } - - /// Execute query: network.devices - public func devices(_ input: ListDevicesInput) async throws -> [DeviceInfoLite] { - return try await client.execute( - input, - method: "query:network.devices.v1", - responseType: [DeviceInfoLite].self - ) - } - - /// Execute query: network.pair.status - public func pairStatus(_ input: PairStatusQueryInput) async throws -> PairStatusOutput { - return try await client.execute( - input, - method: "query:network.pair.status.v1", - responseType: PairStatusOutput.self - ) - } - - /// Execute query: network.status - public func status(_ input: NetworkStatusQueryInput) async throws -> NetworkStatus { - return try await client.execute( - input, - method: "query:network.status.v1", - responseType: NetworkStatus.self - ) - } - -} - -/// Locations operations -public struct LocationsAPI { - private let client: SpacedriveClient - - init(client: SpacedriveClient) { - self.client = client - } - - /// Execute action: locations.remove - public func remove(_ input: LocationRemoveInput) async throws -> LocationRemoveOutput { - return try await client.execute( - input, - method: "action:locations.remove.input.v1", - responseType: LocationRemoveOutput.self - ) - } - - /// Execute action: locations.add - public func add(_ input: LocationAddInput) async throws -> LocationAddOutput { - return try await client.execute( - input, - method: "action:locations.add.input.v1", - responseType: LocationAddOutput.self - ) - } - - /// Execute action: locations.rescan - public func rescan(_ input: LocationRescanInput) async throws -> LocationRescanOutput { - return try await client.execute( - input, - method: "action:locations.rescan.input.v1", - responseType: LocationRescanOutput.self - ) - } - - /// Execute query: locations.list - public func list(_ input: LocationsListQueryInput) async throws -> LocationsListOutput { - return try await client.execute( - input, - method: "query:locations.list.v1", - responseType: LocationsListOutput.self - ) - } - -} - -/// Jobs operations -public struct JobsAPI { - private let client: SpacedriveClient - - init(client: SpacedriveClient) { - self.client = client - } - - /// Execute action: jobs.cancel - public func cancel(_ input: JobCancelInput) async throws -> JobCancelOutput { - return try await client.execute( - input, - method: "action:jobs.cancel.input.v1", - responseType: JobCancelOutput.self - ) - } - - /// Execute action: jobs.resume - public func resume(_ input: JobResumeInput) async throws -> JobResumeOutput { - return try await client.execute( - input, - method: "action:jobs.resume.input.v1", - responseType: JobResumeOutput.self - ) - } - - /// Execute action: jobs.pause - public func pause(_ input: JobPauseInput) async throws -> JobPauseOutput { - return try await client.execute( - input, - method: "action:jobs.pause.input.v1", - responseType: JobPauseOutput.self - ) - } - - /// Execute query: jobs.info - public func info(_ input: JobInfoQueryInput) async throws -> JobInfoOutput { - return try await client.execute( - input, - method: "query:jobs.info.v1", - responseType: JobInfoOutput.self - ) - } - - /// Execute query: jobs.list - public func list(_ input: JobListInput) async throws -> JobListOutput { - return try await client.execute( - input, - method: "query:jobs.list.v1", - responseType: JobListOutput.self - ) - } - -} - -/// Libraries operations -public struct LibrariesAPI { - private let client: SpacedriveClient - - init(client: SpacedriveClient) { - self.client = client - } - - /// Execute action: libraries.delete - public func delete(_ input: LibraryDeleteInput) async throws -> LibraryDeleteOutput { - return try await client.execute( - input, - method: "action:libraries.delete.input.v1", - responseType: LibraryDeleteOutput.self - ) - } - - /// Execute action: libraries.rename - public func rename(_ input: LibraryRenameInput) async throws -> LibraryRenameOutput { - return try await client.execute( - input, - method: "action:libraries.rename.input.v1", - responseType: LibraryRenameOutput.self - ) - } - - /// Execute action: libraries.export - public func export(_ input: LibraryExportInput) async throws -> LibraryExportOutput { - return try await client.execute( - input, - method: "action:libraries.export.input.v1", - responseType: LibraryExportOutput.self - ) - } - - /// Execute action: libraries.create - public func create(_ input: LibraryCreateInput) async throws -> LibraryCreateOutput { - return try await client.execute( - input, - method: "action:libraries.create.input.v1", - responseType: LibraryCreateOutput.self - ) - } - - /// Execute query: libraries.list - public func list(_ input: ListLibrariesInput) async throws -> [LibraryInfo] { - return try await client.execute( - input, - method: "query:libraries.list.v1", - responseType: [LibraryInfo].self - ) - } - - /// Execute query: libraries.info - public func info(_ input: LibraryInfoQueryInput) async throws -> LibraryInfoOutput { - return try await client.execute( - input, - method: "query:libraries.info.v1", - responseType: LibraryInfoOutput.self - ) - } - -} - -/// Indexing operations -public struct IndexingAPI { - private let client: SpacedriveClient - - init(client: SpacedriveClient) { - self.client = client - } - - /// Execute action: indexing.start - public func start(_ input: IndexInput) async throws -> JobReceipt { - return try await client.execute( - input, - method: "action:indexing.start.input.v1", - responseType: JobReceipt.self + method: "query:search.files.v1", + responseType: FileSearchOutput.self ) } @@ -303,6 +38,308 @@ public struct CoreAPI { } +/// Libraries operations +public struct LibrariesAPI { + private let client: SpacedriveClient + + init(client: SpacedriveClient) { + self.client = client + } + + /// Execute action: libraries.rename + public func rename(_ input: LibraryRenameInput) async throws -> LibraryRenameOutput { + return try await client.execute( + input, + method: "action:libraries.rename.input.v1", + responseType: LibraryRenameOutput.self + ) + } + + /// Execute action: libraries.create + public func create(_ input: LibraryCreateInput) async throws -> LibraryCreateOutput { + return try await client.execute( + input, + method: "action:libraries.create.input.v1", + responseType: LibraryCreateOutput.self + ) + } + + /// Execute action: libraries.export + public func export(_ input: LibraryExportInput) async throws -> LibraryExportOutput { + return try await client.execute( + input, + method: "action:libraries.export.input.v1", + responseType: LibraryExportOutput.self + ) + } + + /// Execute action: libraries.delete + public func delete(_ input: LibraryDeleteInput) async throws -> LibraryDeleteOutput { + return try await client.execute( + input, + method: "action:libraries.delete.input.v1", + responseType: LibraryDeleteOutput.self + ) + } + + /// Execute query: libraries.info + public func info(_ input: LibraryInfoQueryInput) async throws -> LibraryInfoOutput { + return try await client.execute( + input, + method: "query:libraries.info.v1", + responseType: LibraryInfoOutput.self + ) + } + + /// Execute query: libraries.list + public func list(_ input: ListLibrariesInput) async throws -> [LibraryInfo] { + return try await client.execute( + input, + method: "query:libraries.list.v1", + responseType: [LibraryInfo].self + ) + } + +} + +/// Files operations +public struct FilesAPI { + private let client: SpacedriveClient + + init(client: SpacedriveClient) { + self.client = client + } + + /// Execute action: files.copy + public func copy(_ input: FileCopyInput) async throws -> JobReceipt { + return try await client.execute( + input, + method: "action:files.copy.input.v1", + responseType: JobReceipt.self + ) + } + + /// Execute query: files.directory_listing + public func directoryListing(_ input: DirectoryListingInput) async throws -> DirectoryListingOutput { + return try await client.execute( + input, + method: "query:files.directory_listing.v1", + responseType: DirectoryListingOutput.self + ) + } + + /// Execute query: files.unique_to_location + public func uniqueToLocation(_ input: UniqueToLocationInput) async throws -> UniqueToLocationOutput { + return try await client.execute( + input, + method: "query:files.unique_to_location.v1", + responseType: UniqueToLocationOutput.self + ) + } + + /// Execute query: files.by_path + public func byPath(_ input: FileByPathQuery) async throws -> File { + return try await client.execute( + input, + method: "query:files.by_path.v1", + responseType: File.self + ) + } + + /// Execute query: files.by_id + public func byId(_ input: FileByIdQuery) async throws -> File { + return try await client.execute( + input, + method: "query:files.by_id.v1", + responseType: File.self + ) + } + +} + +/// Tags operations +public struct TagsAPI { + private let client: SpacedriveClient + + init(client: SpacedriveClient) { + self.client = client + } + + /// Execute action: tags.create + public func create(_ input: CreateTagInput) async throws -> CreateTagOutput { + return try await client.execute( + input, + method: "action:tags.create.input.v1", + responseType: CreateTagOutput.self + ) + } + + /// Execute action: tags.apply + public func apply(_ input: ApplyTagsInput) async throws -> ApplyTagsOutput { + return try await client.execute( + input, + method: "action:tags.apply.input.v1", + responseType: ApplyTagsOutput.self + ) + } + + /// Execute query: tags.search + public func search(_ input: SearchTagsInput) async throws -> SearchTagsOutput { + return try await client.execute( + input, + method: "query:tags.search.v1", + responseType: SearchTagsOutput.self + ) + } + +} + +/// Locations operations +public struct LocationsAPI { + private let client: SpacedriveClient + + init(client: SpacedriveClient) { + self.client = client + } + + /// Execute action: locations.remove + public func remove(_ input: LocationRemoveInput) async throws -> LocationRemoveOutput { + return try await client.execute( + input, + method: "action:locations.remove.input.v1", + responseType: LocationRemoveOutput.self + ) + } + + /// Execute action: locations.rescan + public func rescan(_ input: LocationRescanInput) async throws -> LocationRescanOutput { + return try await client.execute( + input, + method: "action:locations.rescan.input.v1", + responseType: LocationRescanOutput.self + ) + } + + /// Execute action: locations.add + public func add(_ input: LocationAddInput) async throws -> LocationAddOutput { + return try await client.execute( + input, + method: "action:locations.add.input.v1", + responseType: LocationAddOutput.self + ) + } + + /// Execute query: locations.list + public func list(_ input: LocationsListQueryInput) async throws -> LocationsListOutput { + return try await client.execute( + input, + method: "query:locations.list.v1", + responseType: LocationsListOutput.self + ) + } + +} + +/// Network operations +public struct NetworkAPI { + private let client: SpacedriveClient + + init(client: SpacedriveClient) { + self.client = client + } + + /// Execute action: network.pair.cancel + public func pairCancel(_ input: PairCancelInput) async throws -> PairCancelOutput { + return try await client.execute( + input, + method: "action:network.pair.cancel.input.v1", + responseType: PairCancelOutput.self + ) + } + + /// Execute action: network.spacedrop.send + public func spacedropSend(_ input: SpacedropSendInput) async throws -> SpacedropSendOutput { + return try await client.execute( + input, + method: "action:network.spacedrop.send.input.v1", + responseType: SpacedropSendOutput.self + ) + } + + /// Execute action: network.device.revoke + public func deviceRevoke(_ input: DeviceRevokeInput) async throws -> DeviceRevokeOutput { + return try await client.execute( + input, + method: "action:network.device.revoke.input.v1", + responseType: DeviceRevokeOutput.self + ) + } + + /// Execute action: network.stop + public func stop(_ input: NetworkStopInput) async throws -> NetworkStopOutput { + return try await client.execute( + input, + method: "action:network.stop.input.v1", + responseType: NetworkStopOutput.self + ) + } + + /// Execute action: network.pair.generate + public func pairGenerate(_ input: PairGenerateInput) async throws -> PairGenerateOutput { + return try await client.execute( + input, + method: "action:network.pair.generate.input.v1", + responseType: PairGenerateOutput.self + ) + } + + /// Execute action: network.pair.join + public func pairJoin(_ input: PairJoinInput) async throws -> PairJoinOutput { + return try await client.execute( + input, + method: "action:network.pair.join.input.v1", + responseType: PairJoinOutput.self + ) + } + + /// Execute action: network.start + public func start(_ input: NetworkStartInput) async throws -> NetworkStartOutput { + return try await client.execute( + input, + method: "action:network.start.input.v1", + responseType: NetworkStartOutput.self + ) + } + + /// Execute query: network.pair.status + public func pairStatus(_ input: PairStatusQueryInput) async throws -> PairStatusOutput { + return try await client.execute( + input, + method: "query:network.pair.status.v1", + responseType: PairStatusOutput.self + ) + } + + /// Execute query: network.devices + public func devices(_ input: ListDevicesInput) async throws -> [DeviceInfoLite] { + return try await client.execute( + input, + method: "query:network.devices.v1", + responseType: [DeviceInfoLite].self + ) + } + + /// Execute query: network.status + public func status(_ input: NetworkStatusQueryInput) async throws -> NetworkStatus { + return try await client.execute( + input, + method: "query:network.status.v1", + responseType: NetworkStatus.self + ) + } + +} + /// Volumes operations public struct VolumesAPI { private let client: SpacedriveClient @@ -320,15 +357,6 @@ public struct VolumesAPI { ) } - /// Execute action: volumes.track - public func track(_ input: VolumeTrackInput) async throws -> VolumeTrackOutput { - return try await client.execute( - input, - method: "action:volumes.track.input.v1", - responseType: VolumeTrackOutput.self - ) - } - /// Execute action: volumes.untrack public func untrack(_ input: VolumeUntrackInput) async throws -> VolumeUntrackOutput { return try await client.execute( @@ -338,6 +366,15 @@ public struct VolumesAPI { ) } + /// Execute action: volumes.track + public func track(_ input: VolumeTrackInput) async throws -> VolumeTrackOutput { + return try await client.execute( + input, + method: "action:volumes.track.input.v1", + responseType: VolumeTrackOutput.self + ) + } + } /// Media operations @@ -359,114 +396,77 @@ public struct MediaAPI { } -/// Search operations -public struct SearchAPI { +/// Jobs operations +public struct JobsAPI { private let client: SpacedriveClient init(client: SpacedriveClient) { self.client = client } - /// Execute query: search.files - public func files(_ input: FileSearchInput) async throws -> FileSearchOutput { + /// Execute action: jobs.cancel + public func cancel(_ input: JobCancelInput) async throws -> JobCancelOutput { return try await client.execute( input, - method: "query:search.files.v1", - responseType: FileSearchOutput.self + method: "action:jobs.cancel.input.v1", + responseType: JobCancelOutput.self + ) + } + + /// Execute action: jobs.pause + public func pause(_ input: JobPauseInput) async throws -> JobPauseOutput { + return try await client.execute( + input, + method: "action:jobs.pause.input.v1", + responseType: JobPauseOutput.self + ) + } + + /// Execute action: jobs.resume + public func resume(_ input: JobResumeInput) async throws -> JobResumeOutput { + return try await client.execute( + input, + method: "action:jobs.resume.input.v1", + responseType: JobResumeOutput.self + ) + } + + /// Execute query: jobs.info + public func info(_ input: JobInfoQueryInput) async throws -> JobInfoOutput { + return try await client.execute( + input, + method: "query:jobs.info.v1", + responseType: JobInfoOutput.self + ) + } + + /// Execute query: jobs.list + public func list(_ input: JobListInput) async throws -> JobListOutput { + return try await client.execute( + input, + method: "query:jobs.list.v1", + responseType: JobListOutput.self ) } } -/// Files operations -public struct FilesAPI { +/// Indexing operations +public struct IndexingAPI { private let client: SpacedriveClient init(client: SpacedriveClient) { self.client = client } - /// Execute action: files.copy - public func copy(_ input: FileCopyInput) async throws -> JobReceipt { + /// Execute action: indexing.start + public func start(_ input: IndexInput) async throws -> JobReceipt { return try await client.execute( input, - method: "action:files.copy.input.v1", + method: "action:indexing.start.input.v1", responseType: JobReceipt.self ) } - /// Execute query: files.unique_to_location - public func uniqueToLocation(_ input: UniqueToLocationInput) async throws -> UniqueToLocationOutput { - return try await client.execute( - input, - method: "query:files.unique_to_location.v1", - responseType: UniqueToLocationOutput.self - ) - } - - /// Execute query: files.by_path - public func byPath(_ input: FileByPathQuery) async throws -> File { - return try await client.execute( - input, - method: "query:files.by_path.v1", - responseType: File.self - ) - } - - /// Execute query: files.directory_listing - public func directoryListing(_ input: DirectoryListingInput) async throws -> DirectoryListingOutput { - return try await client.execute( - input, - method: "query:files.directory_listing.v1", - responseType: DirectoryListingOutput.self - ) - } - - /// Execute query: files.by_id - public func byId(_ input: FileByIdQuery) async throws -> File { - return try await client.execute( - input, - method: "query:files.by_id.v1", - responseType: File.self - ) - } - -} - -/// Tags operations -public struct TagsAPI { - private let client: SpacedriveClient - - init(client: SpacedriveClient) { - self.client = client - } - - /// Execute action: tags.apply - public func apply(_ input: ApplyTagsInput) async throws -> ApplyTagsOutput { - return try await client.execute( - input, - method: "action:tags.apply.input.v1", - responseType: ApplyTagsOutput.self - ) - } - - /// Execute action: tags.create - public func create(_ input: CreateTagInput) async throws -> CreateTagOutput { - return try await client.execute( - input, - method: "action:tags.create.input.v1", - responseType: CreateTagOutput.self - ) - } - - /// Execute query: tags.search - public func search(_ input: SearchTagsInput) async throws -> SearchTagsOutput { - return try await client.execute( - input, - method: "query:tags.search.v1", - responseType: SearchTagsOutput.self - ) - } - } diff --git a/packages/swift-client/Sources/SpacedriveClient/SpacedriveClient.swift b/packages/swift-client/Sources/SpacedriveClient/SpacedriveClient.swift index 35a78042f..2902f201d 100644 --- a/packages/swift-client/Sources/SpacedriveClient/SpacedriveClient.swift +++ b/packages/swift-client/Sources/SpacedriveClient/SpacedriveClient.swift @@ -163,10 +163,7 @@ public class SpacedriveClient { // Encode input to JSON for non-unit types let requestData: Data do { - let encoder = JSONEncoder() - // Don't omit nil values - we need them to be present as null - // This is handled by the Codable implementation of the structs - requestData = try encoder.encode(requestPayload) + requestData = try JSONEncoder().encode(requestPayload) } catch { throw SpacedriveError.serializationError("Failed to encode request: \(error)") } diff --git a/packages/swift-client/Sources/SpacedriveClient/SpacedriveTypes.swift b/packages/swift-client/Sources/SpacedriveClient/SpacedriveTypes.swift index 774b18676..0f6d1143d 100644 --- a/packages/swift-client/Sources/SpacedriveClient/SpacedriveTypes.swift +++ b/packages/swift-client/Sources/SpacedriveClient/SpacedriveTypes.swift @@ -77,6 +77,801 @@ public indirect enum JsonValue: Codable { // MARK: - Generated Types +public struct LocationAddInput: Codable { + public let path: String + public let name: String? + public let mode: IndexMode + + public init(path: String, name: String?, mode: IndexMode) { + self.path = path + self.name = name + self.mode = mode + } +} +// MARK: - LocationAddInput Custom Codable Implementation +extension LocationAddInput { + private enum CodingKeys: String, CodingKey { + case path = "path" + case name = "name" + case mode = "mode" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + path = try container.decode(String.self, forKey: .path) + name = try container.decodeIfPresent(String.self, forKey: .name) + mode = try container.decode(IndexMode.self, forKey: .mode) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(path, forKey: .path) + try container.encode(name, forKey: .name) + try container.encode(mode, forKey: .mode) + } +} + + +public struct JobReceipt: Codable { + public let id: JobId + public let jobName: String + + private enum CodingKeys: String, CodingKey { + case id = "id" + case jobName = "job_name" + } + + public init(id: JobId, jobName: String) { + self.id = id + self.jobName = jobName + } +} + +/// Represents how the volume is mounted in the system +public enum MountType: Codable { + case system + case external + case network + case virtual +} + + +public struct ActionContextInfo: Codable { + public let actionType: String + public let initiatedAt: String + public let initiatedBy: String? + public let actionInput: JsonValue + public let context: JsonValue + + public init(actionType: String, initiatedAt: String, initiatedBy: String?, actionInput: JsonValue, context: JsonValue) { + self.actionType = actionType + self.initiatedAt = initiatedAt + self.initiatedBy = initiatedBy + self.actionInput = actionInput + self.context = context + } +} +// MARK: - ActionContextInfo Custom Codable Implementation +extension ActionContextInfo { + private enum CodingKeys: String, CodingKey { + case actionType = "action_type" + case initiatedAt = "initiated_at" + case initiatedBy = "initiated_by" + case actionInput = "action_input" + case context = "context" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + actionType = try container.decode(String.self, forKey: .actionType) + initiatedAt = try container.decode(String.self, forKey: .initiatedAt) + initiatedBy = try container.decodeIfPresent(String.self, forKey: .initiatedBy) + actionInput = try container.decode(JsonValue.self, forKey: .actionInput) + context = try container.decode(JsonValue.self, forKey: .context) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(actionType, forKey: .actionType) + try container.encode(initiatedAt, forKey: .initiatedAt) + try container.encode(initiatedBy, forKey: .initiatedBy) + try container.encode(actionInput, forKey: .actionInput) + try container.encode(context, forKey: .context) + } +} + + +/// Filter for file size in bytes +public struct SizeRangeFilter: Codable { + public let min: UInt64? + public let max: UInt64? + + public init(min: UInt64?, max: UInt64?) { + self.min = min + self.max = max + } +} +// MARK: - SizeRangeFilter Custom Codable Implementation +extension SizeRangeFilter { + private enum CodingKeys: String, CodingKey { + case min = "min" + case max = "max" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + min = try container.decodeIfPresent(UInt64.self, forKey: .min) + max = try container.decodeIfPresent(UInt64.self, forKey: .max) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(min, forKey: .min) + try container.encode(max, forKey: .max) + } +} + + +/// Unique fingerprint for a storage volume +public struct VolumeFingerprint: Codable { + let value: String +} + +/// Copy method preference for file operations +public enum CopyMethod: Codable { + case auto + case atomic + case streaming +} + + +public struct CoreStatus: Codable { + public let version: String + public let builtAt: String + public let libraryCount: UInt + public let deviceInfo: DeviceInfo + public let libraries: [LibraryInfo] + public let services: ServiceStatus + public let network: NetworkStatus + public let system: SystemInfo + + private enum CodingKeys: String, CodingKey { + case version = "version" + case builtAt = "built_at" + case libraryCount = "library_count" + case deviceInfo = "device_info" + case libraries = "libraries" + case services = "services" + case network = "network" + case system = "system" + } + + public init(version: String, builtAt: String, libraryCount: UInt, deviceInfo: DeviceInfo, libraries: [LibraryInfo], services: ServiceStatus, network: NetworkStatus, system: SystemInfo) { + self.version = version + self.builtAt = builtAt + self.libraryCount = libraryCount + self.deviceInfo = deviceInfo + self.libraries = libraries + self.services = services + self.network = network + self.system = system + } +} + +public struct JobInfoOutput: Codable { + public let id: String + public let name: String + public let status: JobStatus + public let progress: Float + public let startedAt: String + public let completedAt: String? + public let errorMessage: String? + + public init(id: String, name: String, status: JobStatus, progress: Float, startedAt: String, completedAt: String?, errorMessage: String?) { + self.id = id + self.name = name + self.status = status + self.progress = progress + self.startedAt = startedAt + self.completedAt = completedAt + self.errorMessage = errorMessage + } +} +// MARK: - JobInfoOutput Custom Codable Implementation +extension JobInfoOutput { + private enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + case progress = "progress" + case startedAt = "started_at" + case completedAt = "completed_at" + case errorMessage = "error_message" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + name = try container.decode(String.self, forKey: .name) + status = try container.decode(JobStatus.self, forKey: .status) + progress = try container.decode(Float.self, forKey: .progress) + startedAt = try container.decode(String.self, forKey: .startedAt) + completedAt = try container.decodeIfPresent(String.self, forKey: .completedAt) + errorMessage = try container.decodeIfPresent(String.self, forKey: .errorMessage) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(status, forKey: .status) + try container.encode(progress, forKey: .progress) + try container.encode(startedAt, forKey: .startedAt) + try container.encode(completedAt, forKey: .completedAt) + try container.encode(errorMessage, forKey: .errorMessage) + } +} + + +public struct NetworkStopOutput: Codable { + public let stopped: Bool + + public init(stopped: Bool) { + self.stopped = stopped + } +} + +public struct LocationsListOutput: Codable { + public let locations: [LocationInfo] + + public init(locations: [LocationInfo]) { + self.locations = locations + } +} + +/// Information about a library for listing purposes +public struct LibraryInfo: Codable { + public let id: String + public let name: String + public let path: String + public let stats: LibraryStatistics? + + public init(id: String, name: String, path: String, stats: LibraryStatistics?) { + self.id = id + self.name = name + self.path = path + self.stats = stats + } +} +// MARK: - LibraryInfo Custom Codable Implementation +extension LibraryInfo { + private enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case path = "path" + case stats = "stats" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + name = try container.decode(String.self, forKey: .name) + path = try container.decode(String.self, forKey: .path) + stats = try container.decodeIfPresent(LibraryStatistics.self, forKey: .stats) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(path, forKey: .path) + try container.encode(stats, forKey: .stats) + } +} + + +/// Core input structure for file copy operations +/// This is the canonical interface that all external APIs (CLI, GraphQL, REST) convert to +public struct FileCopyInput: Codable { + public let sources: SdPathBatch + public let destination: SdPath + public let overwrite: Bool + public let verifyChecksum: Bool + public let preserveTimestamps: Bool + public let moveFiles: Bool + public let copyMethod: CopyMethod + public let onConflict: FileConflictResolution? + + public init(sources: SdPathBatch, destination: SdPath, overwrite: Bool, verifyChecksum: Bool, preserveTimestamps: Bool, moveFiles: Bool, copyMethod: CopyMethod, onConflict: FileConflictResolution?) { + self.sources = sources + self.destination = destination + self.overwrite = overwrite + self.verifyChecksum = verifyChecksum + self.preserveTimestamps = preserveTimestamps + self.moveFiles = moveFiles + self.copyMethod = copyMethod + self.onConflict = onConflict + } +} +// MARK: - FileCopyInput Custom Codable Implementation +extension FileCopyInput { + private enum CodingKeys: String, CodingKey { + case sources = "sources" + case destination = "destination" + case overwrite = "overwrite" + case verifyChecksum = "verify_checksum" + case preserveTimestamps = "preserve_timestamps" + case moveFiles = "move_files" + case copyMethod = "copy_method" + case onConflict = "on_conflict" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + sources = try container.decode(SdPathBatch.self, forKey: .sources) + destination = try container.decode(SdPath.self, forKey: .destination) + overwrite = try container.decode(Bool.self, forKey: .overwrite) + verifyChecksum = try container.decode(Bool.self, forKey: .verifyChecksum) + preserveTimestamps = try container.decode(Bool.self, forKey: .preserveTimestamps) + moveFiles = try container.decode(Bool.self, forKey: .moveFiles) + copyMethod = try container.decode(CopyMethod.self, forKey: .copyMethod) + onConflict = try container.decodeIfPresent(FileConflictResolution.self, forKey: .onConflict) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(sources, forKey: .sources) + try container.encode(destination, forKey: .destination) + try container.encode(overwrite, forKey: .overwrite) + try container.encode(verifyChecksum, forKey: .verifyChecksum) + try container.encode(preserveTimestamps, forKey: .preserveTimestamps) + try container.encode(moveFiles, forKey: .moveFiles) + try container.encode(copyMethod, forKey: .copyMethod) + try container.encode(onConflict, forKey: .onConflict) + } +} + + +public struct PairCancelOutput: Codable { + public let cancelled: Bool + + public init(cancelled: Bool) { + self.cancelled = cancelled + } +} + +public struct JobListInput: Codable { + public let status: JobStatus? + + public init(status: JobStatus?) { + self.status = status + } +} +// MARK: - JobListInput Custom Codable Implementation +extension JobListInput { + private enum CodingKeys: String, CodingKey { + case status = "status" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + status = try container.decodeIfPresent(JobStatus.self, forKey: .status) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(status, forKey: .status) + } +} + + +/// GPS coordinates +public struct GpsCoordinates: Codable { + public let latitude: Double + public let longitude: Double + public let altitude: Float? + + public init(latitude: Double, longitude: Double, altitude: Float?) { + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + } +} +// MARK: - GpsCoordinates Custom Codable Implementation +extension GpsCoordinates { + private enum CodingKeys: String, CodingKey { + case latitude = "latitude" + case longitude = "longitude" + case altitude = "altitude" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + latitude = try container.decode(Double.self, forKey: .latitude) + longitude = try container.decode(Double.self, forKey: .longitude) + altitude = try container.decodeIfPresent(Float.self, forKey: .altitude) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(latitude, forKey: .latitude) + try container.encode(longitude, forKey: .longitude) + try container.encode(altitude, forKey: .altitude) + } +} + + +/// Represents any filesystem entry (file or directory) in the VDFS +public struct Entry: Codable { + public let id: String + public let sdPath: SdPathSerialized + public let name: String + public let kind: EntryKind + public let size: UInt64? + public let createdAt: String? + public let modifiedAt: String? + public let accessedAt: String? + public let inode: UInt64? + public let fileId: UInt64? + public let parentId: String? + public let locationId: String? + public let metadataId: String + public let contentId: String? + public let firstSeenAt: String + public let lastIndexedAt: String? + + public init(id: String, sdPath: SdPathSerialized, name: String, kind: EntryKind, size: UInt64?, createdAt: String?, modifiedAt: String?, accessedAt: String?, inode: UInt64?, fileId: UInt64?, parentId: String?, locationId: String?, metadataId: String, contentId: String?, firstSeenAt: String, lastIndexedAt: String?) { + self.id = id + self.sdPath = sdPath + self.name = name + self.kind = kind + self.size = size + self.createdAt = createdAt + self.modifiedAt = modifiedAt + self.accessedAt = accessedAt + self.inode = inode + self.fileId = fileId + self.parentId = parentId + self.locationId = locationId + self.metadataId = metadataId + self.contentId = contentId + self.firstSeenAt = firstSeenAt + self.lastIndexedAt = lastIndexedAt + } +} +// MARK: - Entry Custom Codable Implementation +extension Entry { + private enum CodingKeys: String, CodingKey { + case id = "id" + case sdPath = "sd_path" + case name = "name" + case kind = "kind" + case size = "size" + case createdAt = "created_at" + case modifiedAt = "modified_at" + case accessedAt = "accessed_at" + case inode = "inode" + case fileId = "file_id" + case parentId = "parent_id" + case locationId = "location_id" + case metadataId = "metadata_id" + case contentId = "content_id" + case firstSeenAt = "first_seen_at" + case lastIndexedAt = "last_indexed_at" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + sdPath = try container.decode(SdPathSerialized.self, forKey: .sdPath) + name = try container.decode(String.self, forKey: .name) + kind = try container.decode(EntryKind.self, forKey: .kind) + size = try container.decodeIfPresent(UInt64.self, forKey: .size) + createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) + modifiedAt = try container.decodeIfPresent(String.self, forKey: .modifiedAt) + accessedAt = try container.decodeIfPresent(String.self, forKey: .accessedAt) + inode = try container.decodeIfPresent(UInt64.self, forKey: .inode) + fileId = try container.decodeIfPresent(UInt64.self, forKey: .fileId) + parentId = try container.decodeIfPresent(String.self, forKey: .parentId) + locationId = try container.decodeIfPresent(String.self, forKey: .locationId) + metadataId = try container.decode(String.self, forKey: .metadataId) + contentId = try container.decodeIfPresent(String.self, forKey: .contentId) + firstSeenAt = try container.decode(String.self, forKey: .firstSeenAt) + lastIndexedAt = try container.decodeIfPresent(String.self, forKey: .lastIndexedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(sdPath, forKey: .sdPath) + try container.encode(name, forKey: .name) + try container.encode(kind, forKey: .kind) + try container.encode(size, forKey: .size) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(modifiedAt, forKey: .modifiedAt) + try container.encode(accessedAt, forKey: .accessedAt) + try container.encode(inode, forKey: .inode) + try container.encode(fileId, forKey: .fileId) + try container.encode(parentId, forKey: .parentId) + try container.encode(locationId, forKey: .locationId) + try container.encode(metadataId, forKey: .metadataId) + try container.encode(contentId, forKey: .contentId) + try container.encode(firstSeenAt, forKey: .firstSeenAt) + try container.encode(lastIndexedAt, forKey: .lastIndexedAt) + } +} + + +/// Domain representation of content identity +public struct ContentIdentity: Codable { + public let uuid: String + public let kind: ContentKind + public let hash: String + public let mediaData: MediaData? + public let createdAt: String + + public init(uuid: String, kind: ContentKind, hash: String, mediaData: MediaData?, createdAt: String) { + self.uuid = uuid + self.kind = kind + self.hash = hash + self.mediaData = mediaData + self.createdAt = createdAt + } +} +// MARK: - ContentIdentity Custom Codable Implementation +extension ContentIdentity { + private enum CodingKeys: String, CodingKey { + case uuid = "uuid" + case kind = "kind" + case hash = "hash" + case mediaData = "media_data" + case createdAt = "created_at" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + uuid = try container.decode(String.self, forKey: .uuid) + kind = try container.decode(ContentKind.self, forKey: .kind) + hash = try container.decode(String.self, forKey: .hash) + mediaData = try container.decodeIfPresent(MediaData.self, forKey: .mediaData) + createdAt = try container.decode(String.self, forKey: .createdAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid, forKey: .uuid) + try container.encode(kind, forKey: .kind) + try container.encode(hash, forKey: .hash) + try container.encode(mediaData, forKey: .mediaData) + try container.encode(createdAt, forKey: .createdAt) + } +} + + +public struct NetworkStatus: Codable { + public let running: Bool + public let nodeId: String? + public let addresses: [String] + public let pairedDevices: UInt + public let connectedDevices: UInt + public let version: String + + public init(running: Bool, nodeId: String?, addresses: [String], pairedDevices: UInt, connectedDevices: UInt, version: String) { + self.running = running + self.nodeId = nodeId + self.addresses = addresses + self.pairedDevices = pairedDevices + self.connectedDevices = connectedDevices + self.version = version + } +} +// MARK: - NetworkStatus Custom Codable Implementation +extension NetworkStatus { + private enum CodingKeys: String, CodingKey { + case running = "running" + case nodeId = "node_id" + case addresses = "addresses" + case pairedDevices = "paired_devices" + case connectedDevices = "connected_devices" + case version = "version" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + running = try container.decode(Bool.self, forKey: .running) + nodeId = try container.decodeIfPresent(String.self, forKey: .nodeId) + addresses = try container.decode([String].self, forKey: .addresses) + pairedDevices = try container.decode(UInt.self, forKey: .pairedDevices) + connectedDevices = try container.decode(UInt.self, forKey: .connectedDevices) + version = try container.decode(String.self, forKey: .version) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(running, forKey: .running) + try container.encode(nodeId, forKey: .nodeId) + try container.encode(addresses, forKey: .addresses) + try container.encode(pairedDevices, forKey: .pairedDevices) + try container.encode(connectedDevices, forKey: .connectedDevices) + try container.encode(version, forKey: .version) + } +} + + +/// A path within the Spacedrive Virtual Distributed File System +/// +/// This is the core abstraction that enables cross-device operations. +/// An SdPath can represent: +/// - A physical file at a specific path on a specific device +/// - A content-addressed file that can be sourced from any device +/// +/// This enum-based approach enables resilient file operations by allowing +/// content-based paths to be resolved to optimal physical locations at runtime. +public enum SdPath { + case physical(SdPathPhysicalData) + case content(SdPathContentData) +} +public struct SdPathPhysicalData: Codable { + public let deviceId: String + public let path: String + + private enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + case path = "path" + } +} + +public struct SdPathContentData: Codable { + public let contentId: String + + private enum CodingKeys: String, CodingKey { + case contentId = "content_id" + } +} + + +// MARK: - SdPath Codable Implementation +extension SdPath: Codable { + private enum CodingKeys: String, CodingKey { + case physical = "Physical" + case content = "Content" + } + + public init(from decoder: Decoder) throws { + // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + if container.allKeys.count == 1 { + let key = container.allKeys.first! + switch key { + case .physical: + let data = try container.decode(SdPathPhysicalData.self, forKey: .physical) + self = .physical(data) + return + case .content: + let data = try container.decode(SdPathContentData.self, forKey: .content) + self = .content(data) + return + } + return + } + } + + // Fallback: try decoding as plain string for unit variants (serde default) + if let stringContainer = try? decoder.singleValueContainer() { + if let variantString = try? stringContainer.decode(String.self) { + switch variantString { + default: + break + } + } + } + + throw DecodingError.dataCorrupted( + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .physical(let data): + try container.encode(data, forKey: .physical) + case .content(let data): + try container.encode(data, forKey: .content) + } + } +} + + +/// How SdPath is stored in the database +public struct SdPathSerialized: Codable { + public let deviceId: String + public let path: String + + private enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + case path = "path" + } + + public init(deviceId: String, path: String) { + self.deviceId = deviceId + self.path = path + } +} + +/// Rules for composing attributes from multiple tags +public struct CompositionRule: Codable { + public let `operator`: CompositionOperator + public let operands: [String] + public let resultAttribute: String + + private enum CodingKeys: String, CodingKey { + case `operator` = "operator" + case operands = "operands" + case resultAttribute = "result_attribute" + } + + public init(`operator`: CompositionOperator, operands: [String], resultAttribute: String) { + self.`operator` = `operator` + self.operands = operands + self.resultAttribute = resultAttribute + } +} + +/// Represents an APFS container (physical storage with multiple volumes) +public struct ApfsContainer: Codable { + public let containerId: String + public let uuid: String + public let physicalStore: String + public let totalCapacity: UInt64 + public let capacityInUse: UInt64 + public let capacityFree: UInt64 + public let volumes: [ApfsVolumeInfo] + + private enum CodingKeys: String, CodingKey { + case containerId = "container_id" + case uuid = "uuid" + case physicalStore = "physical_store" + case totalCapacity = "total_capacity" + case capacityInUse = "capacity_in_use" + case capacityFree = "capacity_free" + case volumes = "volumes" + } + + public init(containerId: String, uuid: String, physicalStore: String, totalCapacity: UInt64, capacityInUse: UInt64, capacityFree: UInt64, volumes: [ApfsVolumeInfo]) { + self.containerId = containerId + self.uuid = uuid + self.physicalStore = physicalStore + self.totalCapacity = totalCapacity + self.capacityInUse = capacityInUse + self.capacityFree = capacityFree + self.volumes = volumes + } +} + +public struct LocationsListQueryInput: Codable { +} + +/// Output containing files that are unique to the specified location +public struct UniqueToLocationOutput: Codable { + public let uniqueFiles: [File] + public let totalCount: UInt32 + public let totalSize: UInt64 + + private enum CodingKeys: String, CodingKey { + case uniqueFiles = "unique_files" + case totalCount = "total_count" + case totalSize = "total_size" + } + + public init(uniqueFiles: [File], totalCount: UInt32, totalSize: UInt64) { + self.uniqueFiles = uniqueFiles + self.totalCount = totalCount + self.totalSize = totalSize + } +} + /// Indexer settings controlling rule toggles public struct IndexerSettings: Codable { public let noSystemFiles: Bool? @@ -126,80 +921,103 @@ public struct LibraryExportOutput: Codable { } } -public struct VolumeTrackInput: Codable { - public let fingerprint: VolumeFingerprint - public let name: String? +public struct SpacedropSendOutput: Codable { + public let jobId: String? + public let sessionId: String? - public init(fingerprint: VolumeFingerprint, name: String?) { - self.fingerprint = fingerprint - self.name = name + public init(jobId: String?, sessionId: String?) { + self.jobId = jobId + self.sessionId = sessionId + } +} +// MARK: - SpacedropSendOutput Custom Codable Implementation +extension SpacedropSendOutput { + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + case sessionId = "session_id" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + jobId = try container.decodeIfPresent(String.self, forKey: .jobId) + sessionId = try container.decodeIfPresent(String.self, forKey: .sessionId) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(jobId, forKey: .jobId) + try container.encode(sessionId, forKey: .sessionId) } } -/// Source of tag application -public enum TagSource: Codable { - case user - case aI - case `import` - case sync + +public struct CreateTagOutput: Codable { + public let tagId: String + public let canonicalName: String + public let namespace: String? + public let message: String + + public init(tagId: String, canonicalName: String, namespace: String?, message: String) { + self.tagId = tagId + self.canonicalName = canonicalName + self.namespace = namespace + self.message = message + } } +// MARK: - CreateTagOutput Custom Codable Implementation +extension CreateTagOutput { + private enum CodingKeys: String, CodingKey { + case tagId = "tag_id" + case canonicalName = "canonical_name" + case namespace = "namespace" + case message = "message" + } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + tagId = try container.decode(String.self, forKey: .tagId) + canonicalName = try container.decode(String.self, forKey: .canonicalName) + namespace = try container.decodeIfPresent(String.self, forKey: .namespace) + message = try container.decode(String.self, forKey: .message) + } -/// Output from volume untrack operation -public struct VolumeUntrackOutput: Codable { - public let fingerprint: VolumeFingerprint - - public init(fingerprint: VolumeFingerprint) { - self.fingerprint = fingerprint + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(tagId, forKey: .tagId) + try container.encode(canonicalName, forKey: .canonicalName) + try container.encode(namespace, forKey: .namespace) + try container.encode(message, forKey: .message) } } -/// Text highlighting information -public struct TextHighlight: Codable { - public let field: String - public let text: String - public let start: UInt - public let end: UInt - public init(field: String, text: String, start: UInt, end: UInt) { - self.field = field - self.text = text - self.start = start - self.end = end - } +/// Operators for combining tag attributes +public enum CompositionOperator: Codable { + case and + case or + case with + case without } -/// EXIF metadata for images -public struct ExifData: Codable { - public let make: String? - public let model: String? - public let dateTaken: String? - public let gps: GpsCoordinates? - public let iso: UInt32? - public let aperture: Float? - public let shutterSpeed: Float? - public let focalLength: Float? + +public struct ServiceStatus: Codable { + public let locationWatcher: ServiceState + public let networking: ServiceState + public let volumeMonitor: ServiceState + public let fileSharing: ServiceState private enum CodingKeys: String, CodingKey { - case make = "make" - case model = "model" - case dateTaken = "date_taken" - case gps = "gps" - case iso = "iso" - case aperture = "aperture" - case shutterSpeed = "shutter_speed" - case focalLength = "focal_length" + case locationWatcher = "location_watcher" + case networking = "networking" + case volumeMonitor = "volume_monitor" + case fileSharing = "file_sharing" } - public init(make: String?, model: String?, dateTaken: String?, gps: GpsCoordinates?, iso: UInt32?, aperture: Float?, shutterSpeed: Float?, focalLength: Float?) { - self.make = make - self.model = model - self.dateTaken = dateTaken - self.gps = gps - self.iso = iso - self.aperture = aperture - self.shutterSpeed = shutterSpeed - self.focalLength = focalLength + public init(locationWatcher: ServiceState, networking: ServiceState, volumeMonitor: ServiceState, fileSharing: ServiceState) { + self.locationWatcher = locationWatcher + self.networking = networking + self.volumeMonitor = volumeMonitor + self.fileSharing = fileSharing } } @@ -219,454 +1037,66 @@ public struct PathMapping: Codable { } } -public struct ActionContextInfo: Codable { - public let actionType: String - public let initiatedAt: String - public let initiatedBy: String? - public let actionInput: JsonValue - public let context: JsonValue - - private enum CodingKeys: String, CodingKey { - case actionType = "action_type" - case initiatedAt = "initiated_at" - case initiatedBy = "initiated_by" - case actionInput = "action_input" - case context = "context" - } - - public init(actionType: String, initiatedAt: String, initiatedBy: String?, actionInput: JsonValue, context: JsonValue) { - self.actionType = actionType - self.initiatedAt = initiatedAt - self.initiatedBy = initiatedBy - self.actionInput = actionInput - self.context = context - } -} - -public struct LocationRescanInput: Codable { - public let locationId: String - public let fullRescan: Bool - - private enum CodingKeys: String, CodingKey { - case locationId = "location_id" - case fullRescan = "full_rescan" - } - - public init(locationId: String, fullRescan: Bool) { - self.locationId = locationId - self.fullRescan = fullRescan - } -} - -public struct LocationsListOutput: Codable { - public let locations: [LocationInfo] - - public init(locations: [LocationInfo]) { - self.locations = locations - } -} - -/// Defines the search mode and performance characteristics -public enum SearchMode: Codable { - case fast - case normal - case full +/// Internal enum for file conflict resolution strategies +public enum FileConflictResolution: Codable { + case overwrite + case autoModifyName + case abort } -/// Detailed breakdown of how the score was calculated -public struct ScoreBreakdown: Codable { - public let temporalScore: Float - public let semanticScore: Float? - public let metadataScore: Float - public let recencyBoost: Float - public let userPreferenceBoost: Float - public let finalScore: Float - - private enum CodingKeys: String, CodingKey { - case temporalScore = "temporal_score" - case semanticScore = "semantic_score" - case metadataScore = "metadata_score" - case recencyBoost = "recency_boost" - case userPreferenceBoost = "user_preference_boost" - case finalScore = "final_score" - } - - public init(temporalScore: Float, semanticScore: Float?, metadataScore: Float, recencyBoost: Float, userPreferenceBoost: Float, finalScore: Float) { - self.temporalScore = temporalScore - self.semanticScore = semanticScore - self.metadataScore = metadataScore - self.recencyBoost = recencyBoost - self.userPreferenceBoost = userPreferenceBoost - self.finalScore = finalScore - } -} - -public struct PairCancelInput: Codable { - public let sessionId: String - - private enum CodingKeys: String, CodingKey { - case sessionId = "session_id" - } - - public init(sessionId: String) { - self.sessionId = sessionId - } -} - -/// Output from location add action dispatch -public struct LocationAddOutput: Codable { - public let locationId: String - public let path: String - public let name: String? - public let jobId: String? - - private enum CodingKeys: String, CodingKey { - case locationId = "location_id" - case path = "path" - case name = "name" - case jobId = "job_id" - } - - public init(locationId: String, path: String, name: String?, jobId: String?) { - self.locationId = locationId - self.path = path - self.name = name - self.jobId = jobId - } -} - -public struct VolumeUntrackInput: Codable { - public let fingerprint: VolumeFingerprint - - public init(fingerprint: VolumeFingerprint) { - self.fingerprint = fingerprint - } -} - -public struct LocationAddInput: Codable { - public let path: String - public let name: String? - public let mode: IndexMode - - public init(path: String, name: String?, mode: IndexMode) { - self.path = path - self.name = name - self.mode = mode - } -} - -/// Search facets for filtering UI -public struct SearchFacets: Codable { - public let fileTypes: [String: UInt64] - public let tags: [String: UInt64] - public let locations: [String: UInt64] - public let dateRanges: [String: UInt64] - public let sizeRanges: [String: UInt64] - - private enum CodingKeys: String, CodingKey { - case fileTypes = "file_types" - case tags = "tags" - case locations = "locations" - case dateRanges = "date_ranges" - case sizeRanges = "size_ranges" - } - - public init(fileTypes: [String: UInt64], tags: [String: UInt64], locations: [String: UInt64], dateRanges: [String: UInt64], sizeRanges: [String: UInt64]) { - self.fileTypes = fileTypes - self.tags = tags - self.locations = locations - self.dateRanges = dateRanges - self.sizeRanges = sizeRanges - } -} - -public struct JobInfoOutput: Codable { +public struct DeviceInfoLite: Codable { public let id: String public let name: String - public let status: JobStatus - public let progress: Float - public let startedAt: String - public let completedAt: String? - public let errorMessage: String? + public let osVersion: String + public let appVersion: String + public let isConnected: Bool + public let lastSeen: String private enum CodingKeys: String, CodingKey { case id = "id" case name = "name" - case status = "status" - case progress = "progress" - case startedAt = "started_at" - case completedAt = "completed_at" - case errorMessage = "error_message" + case osVersion = "os_version" + case appVersion = "app_version" + case isConnected = "is_connected" + case lastSeen = "last_seen" } - public init(id: String, name: String, status: JobStatus, progress: Float, startedAt: String, completedAt: String?, errorMessage: String?) { + public init(id: String, name: String, osVersion: String, appVersion: String, isConnected: Bool, lastSeen: String) { self.id = id self.name = name - self.status = status - self.progress = progress - self.startedAt = startedAt - self.completedAt = completedAt - self.errorMessage = errorMessage + self.osVersion = osVersion + self.appVersion = appVersion + self.isConnected = isConnected + self.lastSeen = lastSeen } } -public struct NetworkStopInput: Codable { -} - -/// Container for all structured filters -public struct SearchFilters: Codable { - public let fileTypes: [String]? - public let tags: TagFilter? - public let dateRange: DateRangeFilter? - public let sizeRange: SizeRangeFilter? - public let locations: [String]? - public let contentTypes: [ContentKind]? - public let includeHidden: Bool? - public let includeArchived: Bool? - - private enum CodingKeys: String, CodingKey { - case fileTypes = "file_types" - case tags = "tags" - case dateRange = "date_range" - case sizeRange = "size_range" - case locations = "locations" - case contentTypes = "content_types" - case includeHidden = "include_hidden" - case includeArchived = "include_archived" - } - - public init(fileTypes: [String]?, tags: TagFilter?, dateRange: DateRangeFilter?, sizeRange: SizeRangeFilter?, locations: [String]?, contentTypes: [ContentKind]?, includeHidden: Bool?, includeArchived: Bool?) { - self.fileTypes = fileTypes - self.tags = tags - self.dateRange = dateRange - self.sizeRange = sizeRange - self.locations = locations - self.contentTypes = contentTypes - self.includeHidden = includeHidden - self.includeArchived = includeArchived - } -} - -/// Classification of volume types for UX and auto-tracking decisions -public enum VolumeType: Codable { - case primary - case userData - case external - case secondary - case system - case network - case unknown -} - - -/// Represents the type of physical storage device -public enum DiskType: Codable { - case sSD - case hDD - case unknown -} - - -public struct LibraryRenameOutput: Codable { +/// Canonical input for indexing requests from any interface (CLI, API, etc.) +public struct IndexInput: Codable { public let libraryId: String - public let oldName: String - public let newName: String + public let paths: [String] + public let scope: IndexScope + public let mode: IndexMode + public let includeHidden: Bool + public let persistence: IndexPersistence private enum CodingKeys: String, CodingKey { case libraryId = "library_id" - case oldName = "old_name" - case newName = "new_name" + case paths = "paths" + case scope = "scope" + case mode = "mode" + case includeHidden = "include_hidden" + case persistence = "persistence" } - public init(libraryId: String, oldName: String, newName: String) { + public init(libraryId: String, paths: [String], scope: IndexScope, mode: IndexMode, includeHidden: Bool, persistence: IndexPersistence) { self.libraryId = libraryId - self.oldName = oldName - self.newName = newName - } -} - -public struct JobCancelOutput: Codable { - public let jobId: String - public let success: Bool - - private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - case success = "success" - } - - public init(jobId: String, success: Bool) { - self.jobId = jobId - self.success = success - } -} - -/// Domain representation of a sidecar -public struct Sidecar: Codable { - public let id: Int32 - public let contentUuid: String - public let kind: String - public let variant: String - public let format: String - public let status: String - public let size: Int64 - public let createdAt: String - public let updatedAt: String - - private enum CodingKeys: String, CodingKey { - case id = "id" - case contentUuid = "content_uuid" - case kind = "kind" - case variant = "variant" - case format = "format" - case status = "status" - case size = "size" - case createdAt = "created_at" - case updatedAt = "updated_at" - } - - public init(id: Int32, contentUuid: String, kind: String, variant: String, format: String, status: String, size: Int64, createdAt: String, updatedAt: String) { - self.id = id - self.contentUuid = contentUuid - self.kind = kind - self.variant = variant - self.format = format - self.status = status - self.size = size - self.createdAt = createdAt - self.updatedAt = updatedAt - } -} - -/// Types of file operations -public enum FileOperation: Codable { - case copy - case move - case delete - case rename -} - - -public struct JobListItem: Codable { - public let id: String - public let name: String - public let status: JobStatus - public let progress: Float - public let actionType: String? - public let actionContext: ActionContextInfo? - - private enum CodingKeys: String, CodingKey { - case id = "id" - case name = "name" - case status = "status" - case progress = "progress" - case actionType = "action_type" - case actionContext = "action_context" - } - - public init(id: String, name: String, status: JobStatus, progress: Float, actionType: String?, actionContext: ActionContextInfo?) { - self.id = id - self.name = name - self.status = status - self.progress = progress - self.actionType = actionType - self.actionContext = actionContext - } -} - -public struct DeviceInfo: Codable { - public let id: String - public let name: String - public let os: String - public let hardwareModel: String? - public let createdAt: String - - private enum CodingKeys: String, CodingKey { - case id = "id" - case name = "name" - case os = "os" - case hardwareModel = "hardware_model" - case createdAt = "created_at" - } - - public init(id: String, name: String, os: String, hardwareModel: String?, createdAt: String) { - self.id = id - self.name = name - self.os = os - self.hardwareModel = hardwareModel - self.createdAt = createdAt - } -} - -/// Pagination information -public struct PaginationInfo: Codable { - public let currentPage: UInt32 - public let totalPages: UInt32 - public let hasNext: Bool - public let hasPrevious: Bool - public let limit: UInt32 - public let offset: UInt32 - - private enum CodingKeys: String, CodingKey { - case currentPage = "current_page" - case totalPages = "total_pages" - case hasNext = "has_next" - case hasPrevious = "has_previous" - case limit = "limit" - case offset = "offset" - } - - public init(currentPage: UInt32, totalPages: UInt32, hasNext: Bool, hasPrevious: Bool, limit: UInt32, offset: UInt32) { - self.currentPage = currentPage - self.totalPages = totalPages - self.hasNext = hasNext - self.hasPrevious = hasPrevious - self.limit = limit - self.offset = offset - } -} - -public struct PairJoinOutput: Codable { - public let pairedDeviceId: String - public let deviceName: String - - private enum CodingKeys: String, CodingKey { - case pairedDeviceId = "paired_device_id" - case deviceName = "device_name" - } - - public init(pairedDeviceId: String, deviceName: String) { - self.pairedDeviceId = pairedDeviceId - self.deviceName = deviceName - } -} - -public struct VolumeSpeedTestInput: Codable { - public let fingerprint: VolumeFingerprint - - public init(fingerprint: VolumeFingerprint) { - self.fingerprint = fingerprint - } -} - -public struct LocationRescanOutput: Codable { - public let locationId: String - public let locationPath: String - public let jobId: String - public let fullRescan: Bool - - private enum CodingKeys: String, CodingKey { - case locationId = "location_id" - case locationPath = "location_path" - case jobId = "job_id" - case fullRescan = "full_rescan" - } - - public init(locationId: String, locationPath: String, jobId: String, fullRescan: Bool) { - self.locationId = locationId - self.locationPath = locationPath - self.jobId = jobId - self.fullRescan = fullRescan + self.paths = paths + self.scope = scope + self.mode = mode + self.includeHidden = includeHidden + self.persistence = persistence } } @@ -682,125 +1112,219 @@ public struct DateRangeFilter: Codable { self.end = end } } - -/// Raw filesystem event kinds emitted by the watcher without DB resolution -public enum FsRawEventKind { - case create(FsRawEventKindCreate) - case modify(FsRawEventKindModify) - case remove(FsRawEventKindRemove) - case rename(FsRawEventKindRename) -} -public struct FsRawEventKindCreate: Codable { - public let path: String -} - -public struct FsRawEventKindModify: Codable { - public let path: String -} - -public struct FsRawEventKindRemove: Codable { - public let path: String -} - -public struct FsRawEventKindRename: Codable { - public let from: String - public let to: String -} - - -// MARK: - FsRawEventKind Codable Implementation -extension FsRawEventKind: Codable { +// MARK: - DateRangeFilter Custom Codable Implementation +extension DateRangeFilter { private enum CodingKeys: String, CodingKey { - case create = "Create" - case modify = "Modify" - case remove = "Remove" - case rename = "Rename" + case field = "field" + case start = "start" + case end = "end" } public init(from decoder: Decoder) throws { - // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) - if let container = try? decoder.container(keyedBy: CodingKeys.self) { - if container.allKeys.count == 1 { - let key = container.allKeys.first! - switch key { - case .create: - let data = try container.decode(FsRawEventKindCreate.self, forKey: .create) - self = .create(data) - return - case .modify: - let data = try container.decode(FsRawEventKindModify.self, forKey: .modify) - self = .modify(data) - return - case .remove: - let data = try container.decode(FsRawEventKindRemove.self, forKey: .remove) - self = .remove(data) - return - case .rename: - let data = try container.decode(FsRawEventKindRename.self, forKey: .rename) - self = .rename(data) - return - } - return - } - } - - // Fallback: try decoding as plain string for unit variants (serde default) - if let stringContainer = try? decoder.singleValueContainer() { - if let variantString = try? stringContainer.decode(String.self) { - switch variantString { - default: - break - } - } - } - - throw DecodingError.dataCorrupted( - DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") - ) + let container = try decoder.container(keyedBy: CodingKeys.self) + field = try container.decode(DateField.self, forKey: .field) + start = try container.decodeIfPresent(String.self, forKey: .start) + end = try container.decodeIfPresent(String.self, forKey: .end) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - - switch self { - case .create(let data): - try container.encode(data, forKey: .create) - case .modify(let data): - try container.encode(data, forKey: .modify) - case .remove(let data): - try container.encode(data, forKey: .remove) - case .rename(let data): - try container.encode(data, forKey: .rename) - } + try container.encode(field, forKey: .field) + try container.encode(start, forKey: .start) + try container.encode(end, forKey: .end) } } -/// Performance and timing metrics -public struct PerformanceMetrics: Codable { - public let rate: Float - public let estimatedRemaining: RustDuration? - public let elapsed: RustDuration? - public let errorCount: UInt64 - public let warningCount: UInt64 +/// Represents the type of physical storage device +public enum DiskType: Codable { + case sSD + case hDD + case unknown +} + +public struct TagSearchResult: Codable { + public let tag: Tag + public let relevance: Float + public let matchedVariant: String? + public let contextScore: Float? + + public init(tag: Tag, relevance: Float, matchedVariant: String?, contextScore: Float?) { + self.tag = tag + self.relevance = relevance + self.matchedVariant = matchedVariant + self.contextScore = contextScore + } +} +// MARK: - TagSearchResult Custom Codable Implementation +extension TagSearchResult { private enum CodingKeys: String, CodingKey { - case rate = "rate" - case estimatedRemaining = "estimated_remaining" - case elapsed = "elapsed" - case errorCount = "error_count" - case warningCount = "warning_count" + case tag = "tag" + case relevance = "relevance" + case matchedVariant = "matched_variant" + case contextScore = "context_score" } - public init(rate: Float, estimatedRemaining: RustDuration?, elapsed: RustDuration?, errorCount: UInt64, warningCount: UInt64) { - self.rate = rate - self.estimatedRemaining = estimatedRemaining - self.elapsed = elapsed - self.errorCount = errorCount - self.warningCount = warningCount + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + tag = try container.decode(Tag.self, forKey: .tag) + relevance = try container.decode(Float.self, forKey: .relevance) + matchedVariant = try container.decodeIfPresent(String.self, forKey: .matchedVariant) + contextScore = try container.decodeIfPresent(Float.self, forKey: .contextScore) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(tag, forKey: .tag) + try container.encode(relevance, forKey: .relevance) + try container.encode(matchedVariant, forKey: .matchedVariant) + try container.encode(contextScore, forKey: .contextScore) } } + +public struct CreateTagInput: Codable { + public let canonicalName: String + public let displayName: String? + public let formalName: String? + public let abbreviation: String? + public let aliases: [String] + public let namespace: String? + public let tagType: TagType? + public let color: String? + public let icon: String? + public let description: String? + public let isOrganizationalAnchor: Bool? + public let privacyLevel: PrivacyLevel? + public let searchWeight: Int32? + public let attributes: [String: JsonValue]? + + public init(canonicalName: String, displayName: String?, formalName: String?, abbreviation: String?, aliases: [String], namespace: String?, tagType: TagType?, color: String?, icon: String?, description: String?, isOrganizationalAnchor: Bool?, privacyLevel: PrivacyLevel?, searchWeight: Int32?, attributes: [String: JsonValue]?) { + self.canonicalName = canonicalName + self.displayName = displayName + self.formalName = formalName + self.abbreviation = abbreviation + self.aliases = aliases + self.namespace = namespace + self.tagType = tagType + self.color = color + self.icon = icon + self.description = description + self.isOrganizationalAnchor = isOrganizationalAnchor + self.privacyLevel = privacyLevel + self.searchWeight = searchWeight + self.attributes = attributes + } +} +// MARK: - CreateTagInput Custom Codable Implementation +extension CreateTagInput { + private enum CodingKeys: String, CodingKey { + case canonicalName = "canonical_name" + case displayName = "display_name" + case formalName = "formal_name" + case abbreviation = "abbreviation" + case aliases = "aliases" + case namespace = "namespace" + case tagType = "tag_type" + case color = "color" + case icon = "icon" + case description = "description" + case isOrganizationalAnchor = "is_organizational_anchor" + case privacyLevel = "privacy_level" + case searchWeight = "search_weight" + case attributes = "attributes" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + canonicalName = try container.decode(String.self, forKey: .canonicalName) + displayName = try container.decodeIfPresent(String.self, forKey: .displayName) + formalName = try container.decodeIfPresent(String.self, forKey: .formalName) + abbreviation = try container.decodeIfPresent(String.self, forKey: .abbreviation) + aliases = try container.decode([String].self, forKey: .aliases) + namespace = try container.decodeIfPresent(String.self, forKey: .namespace) + tagType = try container.decodeIfPresent(TagType.self, forKey: .tagType) + color = try container.decodeIfPresent(String.self, forKey: .color) + icon = try container.decodeIfPresent(String.self, forKey: .icon) + description = try container.decodeIfPresent(String.self, forKey: .description) + isOrganizationalAnchor = try container.decodeIfPresent(Bool.self, forKey: .isOrganizationalAnchor) + privacyLevel = try container.decodeIfPresent(PrivacyLevel.self, forKey: .privacyLevel) + searchWeight = try container.decodeIfPresent(Int32.self, forKey: .searchWeight) + attributes = try container.decodeIfPresent([String: JsonValue].self, forKey: .attributes) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(canonicalName, forKey: .canonicalName) + try container.encode(displayName, forKey: .displayName) + try container.encode(formalName, forKey: .formalName) + try container.encode(abbreviation, forKey: .abbreviation) + try container.encode(aliases, forKey: .aliases) + try container.encode(namespace, forKey: .namespace) + try container.encode(tagType, forKey: .tagType) + try container.encode(color, forKey: .color) + try container.encode(icon, forKey: .icon) + try container.encode(description, forKey: .description) + try container.encode(isOrganizationalAnchor, forKey: .isOrganizationalAnchor) + try container.encode(privacyLevel, forKey: .privacyLevel) + try container.encode(searchWeight, forKey: .searchWeight) + try container.encode(attributes, forKey: .attributes) + } +} + + +public struct NetworkStopInput: Codable { +} + +/// Sort options for directory listing +public enum DirectorySortBy: String, Codable { + case name = "name" + case modified = "modified" + case size = "size" + case type = "type" +} + +/// Input for finding files unique to a location +public struct UniqueToLocationInput: Codable { + public let locationId: String + public let limit: UInt32? + + public init(locationId: String, limit: UInt32?) { + self.locationId = locationId + self.limit = limit + } +} +// MARK: - UniqueToLocationInput Custom Codable Implementation +extension UniqueToLocationInput { + private enum CodingKeys: String, CodingKey { + case locationId = "location_id" + case limit = "limit" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + locationId = try container.decode(String.self, forKey: .locationId) + limit = try container.decodeIfPresent(UInt32.self, forKey: .limit) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(locationId, forKey: .locationId) + try container.encode(limit, forKey: .limit) + } +} + + +/// Types of semantic tags with different behaviors +public enum TagType: Codable { + case standard + case organizational + case privacy + case system +} + + public struct ThumbnailInput: Codable { public let paths: [String] public let size: UInt32 @@ -813,248 +1337,77 @@ public struct ThumbnailInput: Codable { } } -/// Type of content -public enum ContentKind: String, Codable { - case unknown = "unknown" - case image = "image" - case video = "video" - case audio = "audio" - case document = "document" - case archive = "archive" - case code = "code" - case text = "text" - case database = "database" - case book = "book" - case font = "font" - case mesh = "mesh" - case config = "config" - case encrypted = "encrypted" - case key = "key" - case executable = "executable" - case binary = "binary" +/// Indexing mode determines the depth of indexing +public enum IndexMode: Codable { + case shallow + case content + case deep } -public struct PairStatusOutput: Codable { - public let sessions: [PairingSessionSummary] - public init(sessions: [PairingSessionSummary]) { - self.sessions = sessions +public struct ApplyTagsInput: Codable { + public let entryIds: [Int32] + public let tagIds: [String] + public let source: TagSource? + public let confidence: Float? + public let appliedContext: String? + public let instanceAttributes: [String: JsonValue]? + + public init(entryIds: [Int32], tagIds: [String], source: TagSource?, confidence: Float?, appliedContext: String?, instanceAttributes: [String: JsonValue]?) { + self.entryIds = entryIds + self.tagIds = tagIds + self.source = source + self.confidence = confidence + self.appliedContext = appliedContext + self.instanceAttributes = instanceAttributes } } - -/// Progress completion information -public struct ProgressCompletion: Codable { - public let completed: UInt64 - public let total: UInt64 - public let bytesCompleted: UInt64? - public let totalBytes: UInt64? - +// MARK: - ApplyTagsInput Custom Codable Implementation +extension ApplyTagsInput { private enum CodingKeys: String, CodingKey { - case completed = "completed" - case total = "total" - case bytesCompleted = "bytes_completed" - case totalBytes = "total_bytes" + case entryIds = "entry_ids" + case tagIds = "tag_ids" + case source = "source" + case confidence = "confidence" + case appliedContext = "applied_context" + case instanceAttributes = "instance_attributes" } - public init(completed: UInt64, total: UInt64, bytesCompleted: UInt64?, totalBytes: UInt64?) { - self.completed = completed - self.total = total - self.bytesCompleted = bytesCompleted - self.totalBytes = totalBytes + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + entryIds = try container.decode([Int32].self, forKey: .entryIds) + tagIds = try container.decode([String].self, forKey: .tagIds) + source = try container.decodeIfPresent(TagSource.self, forKey: .source) + confidence = try container.decodeIfPresent(Float.self, forKey: .confidence) + appliedContext = try container.decodeIfPresent(String.self, forKey: .appliedContext) + instanceAttributes = try container.decodeIfPresent([String: JsonValue].self, forKey: .instanceAttributes) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(entryIds, forKey: .entryIds) + try container.encode(tagIds, forKey: .tagIds) + try container.encode(source, forKey: .source) + try container.encode(confidence, forKey: .confidence) + try container.encode(appliedContext, forKey: .appliedContext) + try container.encode(instanceAttributes, forKey: .instanceAttributes) } } -/// Output containing directory contents -public struct DirectoryListingOutput: Codable { - public let files: [File] - public let totalCount: UInt32 - public let hasMore: Bool - - private enum CodingKeys: String, CodingKey { - case files = "files" - case totalCount = "total_count" - case hasMore = "has_more" - } - - public init(files: [File], totalCount: UInt32, hasMore: Bool) { - self.files = files - self.totalCount = totalCount - self.hasMore = hasMore - } -} - -public struct PairJoinInput: Codable { - public let code: String - - public init(code: String) { - self.code = code - } -} - -/// Sort options for directory listing -public enum DirectorySortBy: String, Codable { - case name = "name" - case modified = "modified" - case size = "size" - case type = "type" -} - -/// Statistics collected during indexing -public struct IndexerStats: Codable { - public let files: UInt64 - public let dirs: UInt64 - public let bytes: UInt64 - public let symlinks: UInt64 - public let skipped: UInt64 - public let errors: UInt64 - - public init(files: UInt64, dirs: UInt64, bytes: UInt64, symlinks: UInt64, skipped: UInt64, errors: UInt64) { - self.files = files - self.dirs = dirs - self.bytes = bytes - self.symlinks = symlinks - self.skipped = skipped - self.errors = errors - } -} - -public struct JobReceipt: Codable { - public let id: JobId - public let jobName: String - - private enum CodingKeys: String, CodingKey { - case id = "id" - case jobName = "job_name" - } - - public init(id: JobId, jobName: String) { - self.id = id - self.jobName = jobName - } -} - -public struct DeviceRevokeInput: Codable { - public let deviceId: String - - private enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - } - - public init(deviceId: String) { - self.deviceId = deviceId - } -} - -/// Fields that can be used for sorting -public enum SortField: Codable { - case relevance - case name - case size - case modifiedAt - case createdAt -} - - -public struct JobListOutput: Codable { - public let jobs: [JobListItem] - - public init(jobs: [JobListItem]) { - self.jobs = jobs - } -} - -/// Represents a physical or virtual storage volume in the system -public struct Volume: Codable { - public let fingerprint: VolumeFingerprint - public let deviceId: String - public let name: String - public let mountType: MountType - public let volumeType: VolumeType - public let mountPoint: String - public let mountPoints: [String] - public let isMounted: Bool - public let diskType: DiskType - public let fileSystem: FileSystem - public let readOnly: Bool - public let hardwareId: String? - public let errorStatus: String? - public let apfsContainer: ApfsContainer? - public let containerVolumeId: String? - public let pathMappings: [PathMapping] - public let totalBytesCapacity: UInt64 - public let totalBytesAvailable: UInt64 - public let readSpeedMbps: UInt64? - public let writeSpeedMbps: UInt64? - public let isUserVisible: Bool - public let autoTrackEligible: Bool - public let lastUpdated: String - - private enum CodingKeys: String, CodingKey { - case fingerprint = "fingerprint" - case deviceId = "device_id" - case name = "name" - case mountType = "mount_type" - case volumeType = "volume_type" - case mountPoint = "mount_point" - case mountPoints = "mount_points" - case isMounted = "is_mounted" - case diskType = "disk_type" - case fileSystem = "file_system" - case readOnly = "read_only" - case hardwareId = "hardware_id" - case errorStatus = "error_status" - case apfsContainer = "apfs_container" - case containerVolumeId = "container_volume_id" - case pathMappings = "path_mappings" - case totalBytesCapacity = "total_bytes_capacity" - case totalBytesAvailable = "total_bytes_available" - case readSpeedMbps = "read_speed_mbps" - case writeSpeedMbps = "write_speed_mbps" - case isUserVisible = "is_user_visible" - case autoTrackEligible = "auto_track_eligible" - case lastUpdated = "last_updated" - } - - public init(fingerprint: VolumeFingerprint, deviceId: String, name: String, mountType: MountType, volumeType: VolumeType, mountPoint: String, mountPoints: [String], isMounted: Bool, diskType: DiskType, fileSystem: FileSystem, readOnly: Bool, hardwareId: String?, errorStatus: String?, apfsContainer: ApfsContainer?, containerVolumeId: String?, pathMappings: [PathMapping], totalBytesCapacity: UInt64, totalBytesAvailable: UInt64, readSpeedMbps: UInt64?, writeSpeedMbps: UInt64?, isUserVisible: Bool, autoTrackEligible: Bool, lastUpdated: String) { - self.fingerprint = fingerprint - self.deviceId = deviceId - self.name = name - self.mountType = mountType - self.volumeType = volumeType - self.mountPoint = mountPoint - self.mountPoints = mountPoints - self.isMounted = isMounted - self.diskType = diskType - self.fileSystem = fileSystem - self.readOnly = readOnly - self.hardwareId = hardwareId - self.errorStatus = errorStatus - self.apfsContainer = apfsContainer - self.containerVolumeId = containerVolumeId - self.pathMappings = pathMappings - self.totalBytesCapacity = totalBytesCapacity - self.totalBytesAvailable = totalBytesAvailable - self.readSpeedMbps = readSpeedMbps - self.writeSpeedMbps = writeSpeedMbps - self.isUserVisible = isUserVisible - self.autoTrackEligible = autoTrackEligible - self.lastUpdated = lastUpdated - } -} /// Output from a completed job public enum JobOutput { case success - case fileCopy(JobOutputFileCopy) - case indexed(JobOutputIndexed) - case thumbnailsGenerated(JobOutputThumbnailsGenerated) - case thumbnailGeneration(JobOutputThumbnailGeneration) - case fileMove(JobOutputFileMove) - case fileDelete(JobOutputFileDelete) - case duplicateDetection(JobOutputDuplicateDetection) - case fileValidation(JobOutputFileValidation) + case fileCopy(JobOutputFileCopyData) + case indexed(JobOutputIndexedData) + case thumbnailsGenerated(JobOutputThumbnailsGeneratedData) + case thumbnailGeneration(JobOutputThumbnailGenerationData) + case fileMove(JobOutputFileMoveData) + case fileDelete(JobOutputFileDeleteData) + case duplicateDetection(JobOutputDuplicateDetectionData) + case fileValidation(JobOutputFileValidationData) } -public struct JobOutputFileCopy: Codable { +public struct JobOutputFileCopyData: Codable { public let copiedCount: UInt public let totalBytes: UInt64 @@ -1064,12 +1417,12 @@ public struct JobOutputFileCopy: Codable { } } -public struct JobOutputIndexed: Codable { +public struct JobOutputIndexedData: Codable { public let stats: IndexerStats public let metrics: IndexerMetrics } -public struct JobOutputThumbnailsGenerated: Codable { +public struct JobOutputThumbnailsGeneratedData: Codable { public let generatedCount: UInt public let failedCount: UInt @@ -1079,7 +1432,7 @@ public struct JobOutputThumbnailsGenerated: Codable { } } -public struct JobOutputThumbnailGeneration: Codable { +public struct JobOutputThumbnailGenerationData: Codable { public let generatedCount: UInt64 public let skippedCount: UInt64 public let errorCount: UInt64 @@ -1093,7 +1446,7 @@ public struct JobOutputThumbnailGeneration: Codable { } } -public struct JobOutputFileMove: Codable { +public struct JobOutputFileMoveData: Codable { public let movedCount: UInt public let failedCount: UInt public let totalBytes: UInt64 @@ -1105,7 +1458,7 @@ public struct JobOutputFileMove: Codable { } } -public struct JobOutputFileDelete: Codable { +public struct JobOutputFileDeleteData: Codable { public let deletedCount: UInt public let failedCount: UInt public let totalBytes: UInt64 @@ -1117,7 +1470,7 @@ public struct JobOutputFileDelete: Codable { } } -public struct JobOutputDuplicateDetection: Codable { +public struct JobOutputDuplicateDetectionData: Codable { public let duplicateGroups: UInt public let totalDuplicates: UInt public let potentialSavings: UInt64 @@ -1129,7 +1482,7 @@ public struct JobOutputDuplicateDetection: Codable { } } -public struct JobOutputFileValidation: Codable { +public struct JobOutputFileValidationData: Codable { public let validatedCount: UInt public let issuesFound: UInt public let totalBytesValidated: UInt64 @@ -1169,28 +1522,28 @@ extension JobOutput: Codable { case .success: self = .success case .fileCopy: - let data = try container.decode(JobOutputFileCopy.self, forKey: .content) + let data = try container.decode(JobOutputFileCopyData.self, forKey: .content) self = .fileCopy(data) case .indexed: - let data = try container.decode(JobOutputIndexed.self, forKey: .content) + let data = try container.decode(JobOutputIndexedData.self, forKey: .content) self = .indexed(data) case .thumbnailsGenerated: - let data = try container.decode(JobOutputThumbnailsGenerated.self, forKey: .content) + let data = try container.decode(JobOutputThumbnailsGeneratedData.self, forKey: .content) self = .thumbnailsGenerated(data) case .thumbnailGeneration: - let data = try container.decode(JobOutputThumbnailGeneration.self, forKey: .content) + let data = try container.decode(JobOutputThumbnailGenerationData.self, forKey: .content) self = .thumbnailGeneration(data) case .fileMove: - let data = try container.decode(JobOutputFileMove.self, forKey: .content) + let data = try container.decode(JobOutputFileMoveData.self, forKey: .content) self = .fileMove(data) case .fileDelete: - let data = try container.decode(JobOutputFileDelete.self, forKey: .content) + let data = try container.decode(JobOutputFileDeleteData.self, forKey: .content) self = .fileDelete(data) case .duplicateDetection: - let data = try container.decode(JobOutputDuplicateDetection.self, forKey: .content) + let data = try container.decode(JobOutputDuplicateDetectionData.self, forKey: .content) self = .duplicateDetection(data) case .fileValidation: - let data = try container.decode(JobOutputFileValidation.self, forKey: .content) + let data = try container.decode(JobOutputFileValidationData.self, forKey: .content) self = .fileValidation(data) } } @@ -1230,105 +1583,246 @@ extension JobOutput: Codable { } -public struct PairingSessionSummary: Codable { +/// A tag with advanced capabilities for contextual organization +public struct Tag: Codable { public let id: String - public let state: SerializablePairingState - public let remoteDeviceId: String? - public let expiresAt: String? + public let canonicalName: String + public let displayName: String? + public let formalName: String? + public let abbreviation: String? + public let aliases: [String] + public let namespace: String? + public let tagType: TagType + public let color: String? + public let icon: String? + public let description: String? + public let isOrganizationalAnchor: Bool + public let privacyLevel: PrivacyLevel + public let searchWeight: Int32 + public let attributes: [String: JsonValue] + public let compositionRules: [CompositionRule] + public let createdAt: String + public let updatedAt: String + public let createdByDevice: String + public init(id: String, canonicalName: String, displayName: String?, formalName: String?, abbreviation: String?, aliases: [String], namespace: String?, tagType: TagType, color: String?, icon: String?, description: String?, isOrganizationalAnchor: Bool, privacyLevel: PrivacyLevel, searchWeight: Int32, attributes: [String: JsonValue], compositionRules: [CompositionRule], createdAt: String, updatedAt: String, createdByDevice: String) { + self.id = id + self.canonicalName = canonicalName + self.displayName = displayName + self.formalName = formalName + self.abbreviation = abbreviation + self.aliases = aliases + self.namespace = namespace + self.tagType = tagType + self.color = color + self.icon = icon + self.description = description + self.isOrganizationalAnchor = isOrganizationalAnchor + self.privacyLevel = privacyLevel + self.searchWeight = searchWeight + self.attributes = attributes + self.compositionRules = compositionRules + self.createdAt = createdAt + self.updatedAt = updatedAt + self.createdByDevice = createdByDevice + } +} +// MARK: - Tag Custom Codable Implementation +extension Tag { private enum CodingKeys: String, CodingKey { case id = "id" - case state = "state" - case remoteDeviceId = "remote_device_id" - case expiresAt = "expires_at" - } - - public init(id: String, state: SerializablePairingState, remoteDeviceId: String?, expiresAt: String?) { - self.id = id - self.state = state - self.remoteDeviceId = remoteDeviceId - self.expiresAt = expiresAt - } -} - -/// Canonical input for indexing requests from any interface (CLI, API, etc.) -public struct IndexInput: Codable { - public let libraryId: String - public let paths: [String] - public let scope: IndexScope - public let mode: IndexMode - public let includeHidden: Bool - public let persistence: IndexPersistence - - private enum CodingKeys: String, CodingKey { - case libraryId = "library_id" - case paths = "paths" - case scope = "scope" - case mode = "mode" - case includeHidden = "include_hidden" - case persistence = "persistence" - } - - public init(libraryId: String, paths: [String], scope: IndexScope, mode: IndexMode, includeHidden: Bool, persistence: IndexPersistence) { - self.libraryId = libraryId - self.paths = paths - self.scope = scope - self.mode = mode - self.includeHidden = includeHidden - self.persistence = persistence - } -} - -/// Output from library delete action dispatch -public struct LibraryDeleteOutput: Codable { - public let libraryId: String - public let name: String - - private enum CodingKeys: String, CodingKey { - case libraryId = "library_id" - case name = "name" - } - - public init(libraryId: String, name: String) { - self.libraryId = libraryId - self.name = name - } -} - -/// Library statistics -public struct LibraryStatistics: Codable { - public let totalFiles: UInt64 - public let totalSize: UInt64 - public let locationCount: UInt32 - public let tagCount: UInt32 - public let thumbnailCount: UInt64 - public let databaseSize: UInt64 - public let lastIndexed: String? - public let updatedAt: String - - private enum CodingKeys: String, CodingKey { - case totalFiles = "total_files" - case totalSize = "total_size" - case locationCount = "location_count" - case tagCount = "tag_count" - case thumbnailCount = "thumbnail_count" - case databaseSize = "database_size" - case lastIndexed = "last_indexed" + case canonicalName = "canonical_name" + case displayName = "display_name" + case formalName = "formal_name" + case abbreviation = "abbreviation" + case aliases = "aliases" + case namespace = "namespace" + case tagType = "tag_type" + case color = "color" + case icon = "icon" + case description = "description" + case isOrganizationalAnchor = "is_organizational_anchor" + case privacyLevel = "privacy_level" + case searchWeight = "search_weight" + case attributes = "attributes" + case compositionRules = "composition_rules" + case createdAt = "created_at" case updatedAt = "updated_at" + case createdByDevice = "created_by_device" } - public init(totalFiles: UInt64, totalSize: UInt64, locationCount: UInt32, tagCount: UInt32, thumbnailCount: UInt64, databaseSize: UInt64, lastIndexed: String?, updatedAt: String) { - self.totalFiles = totalFiles - self.totalSize = totalSize - self.locationCount = locationCount - self.tagCount = tagCount - self.thumbnailCount = thumbnailCount - self.databaseSize = databaseSize - self.lastIndexed = lastIndexed - self.updatedAt = updatedAt + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + canonicalName = try container.decode(String.self, forKey: .canonicalName) + displayName = try container.decodeIfPresent(String.self, forKey: .displayName) + formalName = try container.decodeIfPresent(String.self, forKey: .formalName) + abbreviation = try container.decodeIfPresent(String.self, forKey: .abbreviation) + aliases = try container.decode([String].self, forKey: .aliases) + namespace = try container.decodeIfPresent(String.self, forKey: .namespace) + tagType = try container.decode(TagType.self, forKey: .tagType) + color = try container.decodeIfPresent(String.self, forKey: .color) + icon = try container.decodeIfPresent(String.self, forKey: .icon) + description = try container.decodeIfPresent(String.self, forKey: .description) + isOrganizationalAnchor = try container.decode(Bool.self, forKey: .isOrganizationalAnchor) + privacyLevel = try container.decode(PrivacyLevel.self, forKey: .privacyLevel) + searchWeight = try container.decode(Int32.self, forKey: .searchWeight) + attributes = try container.decode([String: JsonValue].self, forKey: .attributes) + compositionRules = try container.decode([CompositionRule].self, forKey: .compositionRules) + createdAt = try container.decode(String.self, forKey: .createdAt) + updatedAt = try container.decode(String.self, forKey: .updatedAt) + createdByDevice = try container.decode(String.self, forKey: .createdByDevice) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(canonicalName, forKey: .canonicalName) + try container.encode(displayName, forKey: .displayName) + try container.encode(formalName, forKey: .formalName) + try container.encode(abbreviation, forKey: .abbreviation) + try container.encode(aliases, forKey: .aliases) + try container.encode(namespace, forKey: .namespace) + try container.encode(tagType, forKey: .tagType) + try container.encode(color, forKey: .color) + try container.encode(icon, forKey: .icon) + try container.encode(description, forKey: .description) + try container.encode(isOrganizationalAnchor, forKey: .isOrganizationalAnchor) + try container.encode(privacyLevel, forKey: .privacyLevel) + try container.encode(searchWeight, forKey: .searchWeight) + try container.encode(attributes, forKey: .attributes) + try container.encode(compositionRules, forKey: .compositionRules) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + try container.encode(createdByDevice, forKey: .createdByDevice) } } + +public struct LocationRescanInput: Codable { + public let locationId: String + public let fullRescan: Bool + + private enum CodingKeys: String, CodingKey { + case locationId = "location_id" + case fullRescan = "full_rescan" + } + + public init(locationId: String, fullRescan: Bool) { + self.locationId = locationId + self.fullRescan = fullRescan + } +} + +public struct PairStatusOutput: Codable { + public let sessions: [PairingSessionSummary] + + public init(sessions: [PairingSessionSummary]) { + self.sessions = sessions + } +} + +public struct SystemInfo: Codable { + public let uptime: UInt64? + public let dataDirectory: String + public let instanceName: String? + public let currentLibrary: String? + + public init(uptime: UInt64?, dataDirectory: String, instanceName: String?, currentLibrary: String?) { + self.uptime = uptime + self.dataDirectory = dataDirectory + self.instanceName = instanceName + self.currentLibrary = currentLibrary + } +} +// MARK: - SystemInfo Custom Codable Implementation +extension SystemInfo { + private enum CodingKeys: String, CodingKey { + case uptime = "uptime" + case dataDirectory = "data_directory" + case instanceName = "instance_name" + case currentLibrary = "current_library" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + uptime = try container.decodeIfPresent(UInt64.self, forKey: .uptime) + dataDirectory = try container.decode(String.self, forKey: .dataDirectory) + instanceName = try container.decodeIfPresent(String.self, forKey: .instanceName) + currentLibrary = try container.decodeIfPresent(String.self, forKey: .currentLibrary) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uptime, forKey: .uptime) + try container.encode(dataDirectory, forKey: .dataDirectory) + try container.encode(instanceName, forKey: .instanceName) + try container.encode(currentLibrary, forKey: .currentLibrary) + } +} + + +/// Detailed information about a library +public struct LibraryInfoOutput: Codable { + public let id: String + public let name: String + public let description: String? + public let path: String + public let createdAt: String + public let updatedAt: String + public let settings: LibrarySettings + public let statistics: LibraryStatistics + + public init(id: String, name: String, description: String?, path: String, createdAt: String, updatedAt: String, settings: LibrarySettings, statistics: LibraryStatistics) { + self.id = id + self.name = name + self.description = description + self.path = path + self.createdAt = createdAt + self.updatedAt = updatedAt + self.settings = settings + self.statistics = statistics + } +} +// MARK: - LibraryInfoOutput Custom Codable Implementation +extension LibraryInfoOutput { + private enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case description = "description" + case path = "path" + case createdAt = "created_at" + case updatedAt = "updated_at" + case settings = "settings" + case statistics = "statistics" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + name = try container.decode(String.self, forKey: .name) + description = try container.decodeIfPresent(String.self, forKey: .description) + path = try container.decode(String.self, forKey: .path) + createdAt = try container.decode(String.self, forKey: .createdAt) + updatedAt = try container.decode(String.self, forKey: .updatedAt) + settings = try container.decode(LibrarySettings.self, forKey: .settings) + statistics = try container.decode(LibraryStatistics.self, forKey: .statistics) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + try container.encode(path, forKey: .path) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(updatedAt, forKey: .updatedAt) + try container.encode(settings, forKey: .settings) + try container.encode(statistics, forKey: .statistics) + } +} + + /// Filter for tags, supporting complex boolean logic public struct TagFilter: Codable { public let include: [String] @@ -1340,67 +1834,82 @@ public struct TagFilter: Codable { } } +public struct JobPauseOutput: Codable { + public let jobId: String + public let success: Bool + + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + case success = "success" + } + + public init(jobId: String, success: Bool) { + self.jobId = jobId + self.success = success + } +} + /// A central event type that represents all events that can be emitted throughout the system public enum Event { case coreStarted case coreShutdown - case libraryCreated(EventLibraryCreated) - case libraryOpened(EventLibraryOpened) - case libraryClosed(EventLibraryClosed) - case libraryDeleted(EventLibraryDeleted) - case libraryStatisticsUpdated(EventLibraryStatisticsUpdated) - case entryCreated(EventEntryCreated) - case entryModified(EventEntryModified) - case entryDeleted(EventEntryDeleted) - case entryMoved(EventEntryMoved) - case fsRawChange(EventFsRawChange) + case libraryCreated(EventLibraryCreatedData) + case libraryOpened(EventLibraryOpenedData) + case libraryClosed(EventLibraryClosedData) + case libraryDeleted(EventLibraryDeletedData) + case libraryStatisticsUpdated(EventLibraryStatisticsUpdatedData) + case entryCreated(EventEntryCreatedData) + case entryModified(EventEntryModifiedData) + case entryDeleted(EventEntryDeletedData) + case entryMoved(EventEntryMovedData) + case fsRawChange(EventFsRawChangeData) case volumeAdded(Volume) - case volumeRemoved(EventVolumeRemoved) - case volumeUpdated(EventVolumeUpdated) - case volumeSpeedTested(EventVolumeSpeedTested) - case volumeMountChanged(EventVolumeMountChanged) - case volumeError(EventVolumeError) - case jobQueued(EventJobQueued) - case jobStarted(EventJobStarted) - case jobProgress(EventJobProgress) - case jobCompleted(EventJobCompleted) - case jobFailed(EventJobFailed) - case jobCancelled(EventJobCancelled) - case jobPaused(EventJobPaused) - case jobResumed(EventJobResumed) - case indexingStarted(EventIndexingStarted) - case indexingProgress(EventIndexingProgress) - case indexingCompleted(EventIndexingCompleted) - case indexingFailed(EventIndexingFailed) - case deviceConnected(EventDeviceConnected) - case deviceDisconnected(EventDeviceDisconnected) - case locationAdded(EventLocationAdded) - case locationRemoved(EventLocationRemoved) - case filesIndexed(EventFilesIndexed) - case thumbnailsGenerated(EventThumbnailsGenerated) - case fileOperationCompleted(EventFileOperationCompleted) - case filesModified(EventFilesModified) - case logMessage(EventLogMessage) - case custom(EventCustom) + case volumeRemoved(EventVolumeRemovedData) + case volumeUpdated(EventVolumeUpdatedData) + case volumeSpeedTested(EventVolumeSpeedTestedData) + case volumeMountChanged(EventVolumeMountChangedData) + case volumeError(EventVolumeErrorData) + case jobQueued(EventJobQueuedData) + case jobStarted(EventJobStartedData) + case jobProgress(EventJobProgressData) + case jobCompleted(EventJobCompletedData) + case jobFailed(EventJobFailedData) + case jobCancelled(EventJobCancelledData) + case jobPaused(EventJobPausedData) + case jobResumed(EventJobResumedData) + case indexingStarted(EventIndexingStartedData) + case indexingProgress(EventIndexingProgressData) + case indexingCompleted(EventIndexingCompletedData) + case indexingFailed(EventIndexingFailedData) + case deviceConnected(EventDeviceConnectedData) + case deviceDisconnected(EventDeviceDisconnectedData) + case locationAdded(EventLocationAddedData) + case locationRemoved(EventLocationRemovedData) + case filesIndexed(EventFilesIndexedData) + case thumbnailsGenerated(EventThumbnailsGeneratedData) + case fileOperationCompleted(EventFileOperationCompletedData) + case filesModified(EventFilesModifiedData) + case logMessage(EventLogMessageData) + case custom(EventCustomData) } -public struct EventLibraryCreated: Codable { +public struct EventLibraryCreatedData: Codable { public let id: String public let name: String public let path: String } -public struct EventLibraryOpened: Codable { +public struct EventLibraryOpenedData: Codable { public let id: String public let name: String public let path: String } -public struct EventLibraryClosed: Codable { +public struct EventLibraryClosedData: Codable { public let id: String public let name: String } -public struct EventLibraryDeleted: Codable { +public struct EventLibraryDeletedData: Codable { public let id: String public let name: String public let deletedData: Bool @@ -1412,7 +1921,7 @@ public struct EventLibraryDeleted: Codable { } } -public struct EventLibraryStatisticsUpdated: Codable { +public struct EventLibraryStatisticsUpdatedData: Codable { public let libraryId: String public let statistics: LibraryStatistics @@ -1422,7 +1931,7 @@ public struct EventLibraryStatisticsUpdated: Codable { } } -public struct EventEntryCreated: Codable { +public struct EventEntryCreatedData: Codable { public let libraryId: String public let entryId: String @@ -1432,7 +1941,7 @@ public struct EventEntryCreated: Codable { } } -public struct EventEntryModified: Codable { +public struct EventEntryModifiedData: Codable { public let libraryId: String public let entryId: String @@ -1442,7 +1951,7 @@ public struct EventEntryModified: Codable { } } -public struct EventEntryDeleted: Codable { +public struct EventEntryDeletedData: Codable { public let libraryId: String public let entryId: String @@ -1452,7 +1961,7 @@ public struct EventEntryDeleted: Codable { } } -public struct EventEntryMoved: Codable { +public struct EventEntryMovedData: Codable { public let libraryId: String public let entryId: String public let oldPath: String @@ -1466,7 +1975,7 @@ public struct EventEntryMoved: Codable { } } -public struct EventFsRawChange: Codable { +public struct EventFsRawChangeData: Codable { public let libraryId: String public let kind: FsRawEventKind @@ -1476,11 +1985,11 @@ public struct EventFsRawChange: Codable { } } -public struct EventVolumeRemoved: Codable { +public struct EventVolumeRemovedData: Codable { public let fingerprint: VolumeFingerprint } -public struct EventVolumeUpdated: Codable { +public struct EventVolumeUpdatedData: Codable { public let fingerprint: VolumeFingerprint public let oldInfo: VolumeInfo public let newInfo: VolumeInfo @@ -1492,7 +2001,7 @@ public struct EventVolumeUpdated: Codable { } } -public struct EventVolumeSpeedTested: Codable { +public struct EventVolumeSpeedTestedData: Codable { public let fingerprint: VolumeFingerprint public let readSpeedMbps: UInt64 public let writeSpeedMbps: UInt64 @@ -1504,7 +2013,7 @@ public struct EventVolumeSpeedTested: Codable { } } -public struct EventVolumeMountChanged: Codable { +public struct EventVolumeMountChangedData: Codable { public let fingerprint: VolumeFingerprint public let isMounted: Bool @@ -1514,12 +2023,12 @@ public struct EventVolumeMountChanged: Codable { } } -public struct EventVolumeError: Codable { +public struct EventVolumeErrorData: Codable { public let fingerprint: VolumeFingerprint public let error: String } -public struct EventJobQueued: Codable { +public struct EventJobQueuedData: Codable { public let jobId: String public let jobType: String @@ -1529,7 +2038,7 @@ public struct EventJobQueued: Codable { } } -public struct EventJobStarted: Codable { +public struct EventJobStartedData: Codable { public let jobId: String public let jobType: String @@ -1539,7 +2048,7 @@ public struct EventJobStarted: Codable { } } -public struct EventJobProgress: Codable { +public struct EventJobProgressData: Codable { public let jobId: String public let jobType: String public let progress: Double @@ -1555,7 +2064,7 @@ public struct EventJobProgress: Codable { } } -public struct EventJobCompleted: Codable { +public struct EventJobCompletedData: Codable { public let jobId: String public let jobType: String public let output: JobOutput @@ -1567,7 +2076,7 @@ public struct EventJobCompleted: Codable { } } -public struct EventJobFailed: Codable { +public struct EventJobFailedData: Codable { public let jobId: String public let jobType: String public let error: String @@ -1579,7 +2088,7 @@ public struct EventJobFailed: Codable { } } -public struct EventJobCancelled: Codable { +public struct EventJobCancelledData: Codable { public let jobId: String public let jobType: String @@ -1589,7 +2098,7 @@ public struct EventJobCancelled: Codable { } } -public struct EventJobPaused: Codable { +public struct EventJobPausedData: Codable { public let jobId: String private enum CodingKeys: String, CodingKey { @@ -1597,7 +2106,7 @@ public struct EventJobPaused: Codable { } } -public struct EventJobResumed: Codable { +public struct EventJobResumedData: Codable { public let jobId: String private enum CodingKeys: String, CodingKey { @@ -1605,7 +2114,7 @@ public struct EventJobResumed: Codable { } } -public struct EventIndexingStarted: Codable { +public struct EventIndexingStartedData: Codable { public let locationId: String private enum CodingKeys: String, CodingKey { @@ -1613,7 +2122,7 @@ public struct EventIndexingStarted: Codable { } } -public struct EventIndexingProgress: Codable { +public struct EventIndexingProgressData: Codable { public let locationId: String public let processed: UInt64 public let total: UInt64? @@ -1625,7 +2134,7 @@ public struct EventIndexingProgress: Codable { } } -public struct EventIndexingCompleted: Codable { +public struct EventIndexingCompletedData: Codable { public let locationId: String public let totalFiles: UInt64 public let totalDirs: UInt64 @@ -1637,7 +2146,7 @@ public struct EventIndexingCompleted: Codable { } } -public struct EventIndexingFailed: Codable { +public struct EventIndexingFailedData: Codable { public let locationId: String public let error: String @@ -1647,7 +2156,7 @@ public struct EventIndexingFailed: Codable { } } -public struct EventDeviceConnected: Codable { +public struct EventDeviceConnectedData: Codable { public let deviceId: String public let deviceName: String @@ -1657,7 +2166,7 @@ public struct EventDeviceConnected: Codable { } } -public struct EventDeviceDisconnected: Codable { +public struct EventDeviceDisconnectedData: Codable { public let deviceId: String private enum CodingKeys: String, CodingKey { @@ -1665,7 +2174,7 @@ public struct EventDeviceDisconnected: Codable { } } -public struct EventLocationAdded: Codable { +public struct EventLocationAddedData: Codable { public let libraryId: String public let locationId: String public let path: String @@ -1677,7 +2186,7 @@ public struct EventLocationAdded: Codable { } } -public struct EventLocationRemoved: Codable { +public struct EventLocationRemovedData: Codable { public let libraryId: String public let locationId: String @@ -1687,7 +2196,7 @@ public struct EventLocationRemoved: Codable { } } -public struct EventFilesIndexed: Codable { +public struct EventFilesIndexedData: Codable { public let libraryId: String public let locationId: String public let count: UInt @@ -1699,7 +2208,7 @@ public struct EventFilesIndexed: Codable { } } -public struct EventThumbnailsGenerated: Codable { +public struct EventThumbnailsGeneratedData: Codable { public let libraryId: String public let count: UInt @@ -1709,7 +2218,7 @@ public struct EventThumbnailsGenerated: Codable { } } -public struct EventFileOperationCompleted: Codable { +public struct EventFileOperationCompletedData: Codable { public let libraryId: String public let operation: FileOperation public let affectedFiles: UInt @@ -1721,7 +2230,7 @@ public struct EventFileOperationCompleted: Codable { } } -public struct EventFilesModified: Codable { +public struct EventFilesModifiedData: Codable { public let libraryId: String public let paths: [String] @@ -1731,7 +2240,7 @@ public struct EventFilesModified: Codable { } } -public struct EventLogMessage: Codable { +public struct EventLogMessageData: Codable { public let timestamp: String public let level: String public let target: String @@ -1749,7 +2258,7 @@ public struct EventLogMessage: Codable { } } -public struct EventCustom: Codable { +public struct EventCustomData: Codable { public let eventType: String private enum CodingKeys: String, CodingKey { @@ -1816,154 +2325,156 @@ extension Event: Codable { self = .coreShutdown return case .libraryCreated: - let data = try container.decode(EventLibraryCreated.self, forKey: .libraryCreated) + let data = try container.decode(EventLibraryCreatedData.self, forKey: .libraryCreated) self = .libraryCreated(data) return case .libraryOpened: - let data = try container.decode(EventLibraryOpened.self, forKey: .libraryOpened) + let data = try container.decode(EventLibraryOpenedData.self, forKey: .libraryOpened) self = .libraryOpened(data) return case .libraryClosed: - let data = try container.decode(EventLibraryClosed.self, forKey: .libraryClosed) + let data = try container.decode(EventLibraryClosedData.self, forKey: .libraryClosed) self = .libraryClosed(data) return case .libraryDeleted: - let data = try container.decode(EventLibraryDeleted.self, forKey: .libraryDeleted) + let data = try container.decode(EventLibraryDeletedData.self, forKey: .libraryDeleted) self = .libraryDeleted(data) return case .libraryStatisticsUpdated: - let data = try container.decode(EventLibraryStatisticsUpdated.self, forKey: .libraryStatisticsUpdated) + let data = try container.decode(EventLibraryStatisticsUpdatedData.self, forKey: .libraryStatisticsUpdated) self = .libraryStatisticsUpdated(data) return case .entryCreated: - let data = try container.decode(EventEntryCreated.self, forKey: .entryCreated) + let data = try container.decode(EventEntryCreatedData.self, forKey: .entryCreated) self = .entryCreated(data) return case .entryModified: - let data = try container.decode(EventEntryModified.self, forKey: .entryModified) + let data = try container.decode(EventEntryModifiedData.self, forKey: .entryModified) self = .entryModified(data) return case .entryDeleted: - let data = try container.decode(EventEntryDeleted.self, forKey: .entryDeleted) + let data = try container.decode(EventEntryDeletedData.self, forKey: .entryDeleted) self = .entryDeleted(data) return case .entryMoved: - let data = try container.decode(EventEntryMoved.self, forKey: .entryMoved) + let data = try container.decode(EventEntryMovedData.self, forKey: .entryMoved) self = .entryMoved(data) return case .fsRawChange: - let data = try container.decode(EventFsRawChange.self, forKey: .fsRawChange) + let data = try container.decode(EventFsRawChangeData.self, forKey: .fsRawChange) self = .fsRawChange(data) return case .volumeAdded: - // TODO: Implement tuple variant decoding for volumeAdded - fatalError("Tuple variant decoding not implemented") + var arrayContainer = try container.nestedUnkeyedContainer(forKey: .volumeAdded) + let value0 = try arrayContainer.decode(Volume.self) + self = .volumeAdded(value0) + return case .volumeRemoved: - let data = try container.decode(EventVolumeRemoved.self, forKey: .volumeRemoved) + let data = try container.decode(EventVolumeRemovedData.self, forKey: .volumeRemoved) self = .volumeRemoved(data) return case .volumeUpdated: - let data = try container.decode(EventVolumeUpdated.self, forKey: .volumeUpdated) + let data = try container.decode(EventVolumeUpdatedData.self, forKey: .volumeUpdated) self = .volumeUpdated(data) return case .volumeSpeedTested: - let data = try container.decode(EventVolumeSpeedTested.self, forKey: .volumeSpeedTested) + let data = try container.decode(EventVolumeSpeedTestedData.self, forKey: .volumeSpeedTested) self = .volumeSpeedTested(data) return case .volumeMountChanged: - let data = try container.decode(EventVolumeMountChanged.self, forKey: .volumeMountChanged) + let data = try container.decode(EventVolumeMountChangedData.self, forKey: .volumeMountChanged) self = .volumeMountChanged(data) return case .volumeError: - let data = try container.decode(EventVolumeError.self, forKey: .volumeError) + let data = try container.decode(EventVolumeErrorData.self, forKey: .volumeError) self = .volumeError(data) return case .jobQueued: - let data = try container.decode(EventJobQueued.self, forKey: .jobQueued) + let data = try container.decode(EventJobQueuedData.self, forKey: .jobQueued) self = .jobQueued(data) return case .jobStarted: - let data = try container.decode(EventJobStarted.self, forKey: .jobStarted) + let data = try container.decode(EventJobStartedData.self, forKey: .jobStarted) self = .jobStarted(data) return case .jobProgress: - let data = try container.decode(EventJobProgress.self, forKey: .jobProgress) + let data = try container.decode(EventJobProgressData.self, forKey: .jobProgress) self = .jobProgress(data) return case .jobCompleted: - let data = try container.decode(EventJobCompleted.self, forKey: .jobCompleted) + let data = try container.decode(EventJobCompletedData.self, forKey: .jobCompleted) self = .jobCompleted(data) return case .jobFailed: - let data = try container.decode(EventJobFailed.self, forKey: .jobFailed) + let data = try container.decode(EventJobFailedData.self, forKey: .jobFailed) self = .jobFailed(data) return case .jobCancelled: - let data = try container.decode(EventJobCancelled.self, forKey: .jobCancelled) + let data = try container.decode(EventJobCancelledData.self, forKey: .jobCancelled) self = .jobCancelled(data) return case .jobPaused: - let data = try container.decode(EventJobPaused.self, forKey: .jobPaused) + let data = try container.decode(EventJobPausedData.self, forKey: .jobPaused) self = .jobPaused(data) return case .jobResumed: - let data = try container.decode(EventJobResumed.self, forKey: .jobResumed) + let data = try container.decode(EventJobResumedData.self, forKey: .jobResumed) self = .jobResumed(data) return case .indexingStarted: - let data = try container.decode(EventIndexingStarted.self, forKey: .indexingStarted) + let data = try container.decode(EventIndexingStartedData.self, forKey: .indexingStarted) self = .indexingStarted(data) return case .indexingProgress: - let data = try container.decode(EventIndexingProgress.self, forKey: .indexingProgress) + let data = try container.decode(EventIndexingProgressData.self, forKey: .indexingProgress) self = .indexingProgress(data) return case .indexingCompleted: - let data = try container.decode(EventIndexingCompleted.self, forKey: .indexingCompleted) + let data = try container.decode(EventIndexingCompletedData.self, forKey: .indexingCompleted) self = .indexingCompleted(data) return case .indexingFailed: - let data = try container.decode(EventIndexingFailed.self, forKey: .indexingFailed) + let data = try container.decode(EventIndexingFailedData.self, forKey: .indexingFailed) self = .indexingFailed(data) return case .deviceConnected: - let data = try container.decode(EventDeviceConnected.self, forKey: .deviceConnected) + let data = try container.decode(EventDeviceConnectedData.self, forKey: .deviceConnected) self = .deviceConnected(data) return case .deviceDisconnected: - let data = try container.decode(EventDeviceDisconnected.self, forKey: .deviceDisconnected) + let data = try container.decode(EventDeviceDisconnectedData.self, forKey: .deviceDisconnected) self = .deviceDisconnected(data) return case .locationAdded: - let data = try container.decode(EventLocationAdded.self, forKey: .locationAdded) + let data = try container.decode(EventLocationAddedData.self, forKey: .locationAdded) self = .locationAdded(data) return case .locationRemoved: - let data = try container.decode(EventLocationRemoved.self, forKey: .locationRemoved) + let data = try container.decode(EventLocationRemovedData.self, forKey: .locationRemoved) self = .locationRemoved(data) return case .filesIndexed: - let data = try container.decode(EventFilesIndexed.self, forKey: .filesIndexed) + let data = try container.decode(EventFilesIndexedData.self, forKey: .filesIndexed) self = .filesIndexed(data) return case .thumbnailsGenerated: - let data = try container.decode(EventThumbnailsGenerated.self, forKey: .thumbnailsGenerated) + let data = try container.decode(EventThumbnailsGeneratedData.self, forKey: .thumbnailsGenerated) self = .thumbnailsGenerated(data) return case .fileOperationCompleted: - let data = try container.decode(EventFileOperationCompleted.self, forKey: .fileOperationCompleted) + let data = try container.decode(EventFileOperationCompletedData.self, forKey: .fileOperationCompleted) self = .fileOperationCompleted(data) return case .filesModified: - let data = try container.decode(EventFilesModified.self, forKey: .filesModified) + let data = try container.decode(EventFilesModifiedData.self, forKey: .filesModified) self = .filesModified(data) return case .logMessage: - let data = try container.decode(EventLogMessage.self, forKey: .logMessage) + let data = try container.decode(EventLogMessageData.self, forKey: .logMessage) self = .logMessage(data) return case .custom: - let data = try container.decode(EventCustom.self, forKey: .custom) + let data = try container.decode(EventCustomData.self, forKey: .custom) self = .custom(data) return } @@ -2020,9 +2531,9 @@ extension Event: Codable { try container.encode(data, forKey: .entryMoved) case .fsRawChange(let data): try container.encode(data, forKey: .fsRawChange) - case .volumeAdded: - // TODO: Implement tuple variant encoding for volumeAdded - fatalError("Tuple variant encoding not implemented") + case .volumeAdded(let value0): + var arrayContainer = container.nestedUnkeyedContainer(forKey: .volumeAdded) + try arrayContainer.encode(value0) case .volumeRemoved(let data): try container.encode(data, forKey: .volumeRemoved) case .volumeUpdated(let data): @@ -2082,84 +2593,483 @@ extension Event: Codable { } -/// Domain representation of content identity -public struct ContentIdentity: Codable { - public let uuid: String - public let kind: ContentKind - public let hash: String - public let mediaData: MediaData? +public struct PairJoinOutput: Codable { + public let pairedDeviceId: String + public let deviceName: String + + private enum CodingKeys: String, CodingKey { + case pairedDeviceId = "paired_device_id" + case deviceName = "device_name" + } + + public init(pairedDeviceId: String, deviceName: String) { + self.pairedDeviceId = pairedDeviceId + self.deviceName = deviceName + } +} + +/// Privacy levels for tag visibility control +public enum PrivacyLevel: Codable { + case normal + case archive + case hidden +} + + +/// Represents a physical or virtual storage volume in the system +public struct Volume: Codable { + public let fingerprint: VolumeFingerprint + public let deviceId: String + public let name: String + public let mountType: MountType + public let volumeType: VolumeType + public let mountPoint: String + public let mountPoints: [String] + public let isMounted: Bool + public let diskType: DiskType + public let fileSystem: FileSystem + public let readOnly: Bool + public let hardwareId: String? + public let errorStatus: String? + public let apfsContainer: ApfsContainer? + public let containerVolumeId: String? + public let pathMappings: [PathMapping] + public let totalBytesCapacity: UInt64 + public let totalBytesAvailable: UInt64 + public let readSpeedMbps: UInt64? + public let writeSpeedMbps: UInt64? + public let isUserVisible: Bool + public let autoTrackEligible: Bool + public let lastUpdated: String + + public init(fingerprint: VolumeFingerprint, deviceId: String, name: String, mountType: MountType, volumeType: VolumeType, mountPoint: String, mountPoints: [String], isMounted: Bool, diskType: DiskType, fileSystem: FileSystem, readOnly: Bool, hardwareId: String?, errorStatus: String?, apfsContainer: ApfsContainer?, containerVolumeId: String?, pathMappings: [PathMapping], totalBytesCapacity: UInt64, totalBytesAvailable: UInt64, readSpeedMbps: UInt64?, writeSpeedMbps: UInt64?, isUserVisible: Bool, autoTrackEligible: Bool, lastUpdated: String) { + self.fingerprint = fingerprint + self.deviceId = deviceId + self.name = name + self.mountType = mountType + self.volumeType = volumeType + self.mountPoint = mountPoint + self.mountPoints = mountPoints + self.isMounted = isMounted + self.diskType = diskType + self.fileSystem = fileSystem + self.readOnly = readOnly + self.hardwareId = hardwareId + self.errorStatus = errorStatus + self.apfsContainer = apfsContainer + self.containerVolumeId = containerVolumeId + self.pathMappings = pathMappings + self.totalBytesCapacity = totalBytesCapacity + self.totalBytesAvailable = totalBytesAvailable + self.readSpeedMbps = readSpeedMbps + self.writeSpeedMbps = writeSpeedMbps + self.isUserVisible = isUserVisible + self.autoTrackEligible = autoTrackEligible + self.lastUpdated = lastUpdated + } +} +// MARK: - Volume Custom Codable Implementation +extension Volume { + private enum CodingKeys: String, CodingKey { + case fingerprint = "fingerprint" + case deviceId = "device_id" + case name = "name" + case mountType = "mount_type" + case volumeType = "volume_type" + case mountPoint = "mount_point" + case mountPoints = "mount_points" + case isMounted = "is_mounted" + case diskType = "disk_type" + case fileSystem = "file_system" + case readOnly = "read_only" + case hardwareId = "hardware_id" + case errorStatus = "error_status" + case apfsContainer = "apfs_container" + case containerVolumeId = "container_volume_id" + case pathMappings = "path_mappings" + case totalBytesCapacity = "total_bytes_capacity" + case totalBytesAvailable = "total_bytes_available" + case readSpeedMbps = "read_speed_mbps" + case writeSpeedMbps = "write_speed_mbps" + case isUserVisible = "is_user_visible" + case autoTrackEligible = "auto_track_eligible" + case lastUpdated = "last_updated" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + fingerprint = try container.decode(VolumeFingerprint.self, forKey: .fingerprint) + deviceId = try container.decode(String.self, forKey: .deviceId) + name = try container.decode(String.self, forKey: .name) + mountType = try container.decode(MountType.self, forKey: .mountType) + volumeType = try container.decode(VolumeType.self, forKey: .volumeType) + mountPoint = try container.decode(String.self, forKey: .mountPoint) + mountPoints = try container.decode([String].self, forKey: .mountPoints) + isMounted = try container.decode(Bool.self, forKey: .isMounted) + diskType = try container.decode(DiskType.self, forKey: .diskType) + fileSystem = try container.decode(FileSystem.self, forKey: .fileSystem) + readOnly = try container.decode(Bool.self, forKey: .readOnly) + hardwareId = try container.decodeIfPresent(String.self, forKey: .hardwareId) + errorStatus = try container.decodeIfPresent(String.self, forKey: .errorStatus) + apfsContainer = try container.decodeIfPresent(ApfsContainer.self, forKey: .apfsContainer) + containerVolumeId = try container.decodeIfPresent(String.self, forKey: .containerVolumeId) + pathMappings = try container.decode([PathMapping].self, forKey: .pathMappings) + totalBytesCapacity = try container.decode(UInt64.self, forKey: .totalBytesCapacity) + totalBytesAvailable = try container.decode(UInt64.self, forKey: .totalBytesAvailable) + readSpeedMbps = try container.decodeIfPresent(UInt64.self, forKey: .readSpeedMbps) + writeSpeedMbps = try container.decodeIfPresent(UInt64.self, forKey: .writeSpeedMbps) + isUserVisible = try container.decode(Bool.self, forKey: .isUserVisible) + autoTrackEligible = try container.decode(Bool.self, forKey: .autoTrackEligible) + lastUpdated = try container.decode(String.self, forKey: .lastUpdated) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fingerprint, forKey: .fingerprint) + try container.encode(deviceId, forKey: .deviceId) + try container.encode(name, forKey: .name) + try container.encode(mountType, forKey: .mountType) + try container.encode(volumeType, forKey: .volumeType) + try container.encode(mountPoint, forKey: .mountPoint) + try container.encode(mountPoints, forKey: .mountPoints) + try container.encode(isMounted, forKey: .isMounted) + try container.encode(diskType, forKey: .diskType) + try container.encode(fileSystem, forKey: .fileSystem) + try container.encode(readOnly, forKey: .readOnly) + try container.encode(hardwareId, forKey: .hardwareId) + try container.encode(errorStatus, forKey: .errorStatus) + try container.encode(apfsContainer, forKey: .apfsContainer) + try container.encode(containerVolumeId, forKey: .containerVolumeId) + try container.encode(pathMappings, forKey: .pathMappings) + try container.encode(totalBytesCapacity, forKey: .totalBytesCapacity) + try container.encode(totalBytesAvailable, forKey: .totalBytesAvailable) + try container.encode(readSpeedMbps, forKey: .readSpeedMbps) + try container.encode(writeSpeedMbps, forKey: .writeSpeedMbps) + try container.encode(isUserVisible, forKey: .isUserVisible) + try container.encode(autoTrackEligible, forKey: .autoTrackEligible) + try container.encode(lastUpdated, forKey: .lastUpdated) + } +} + + +/// Time-based fields that can be filtered +public enum DateField: Codable { + case createdAt + case modifiedAt + case accessedAt +} + + +/// Search facets for filtering UI +public struct SearchFacets: Codable { + public let fileTypes: [String: UInt64] + public let tags: [String: UInt64] + public let locations: [String: UInt64] + public let dateRanges: [String: UInt64] + public let sizeRanges: [String: UInt64] + + private enum CodingKeys: String, CodingKey { + case fileTypes = "file_types" + case tags = "tags" + case locations = "locations" + case dateRanges = "date_ranges" + case sizeRanges = "size_ranges" + } + + public init(fileTypes: [String: UInt64], tags: [String: UInt64], locations: [String: UInt64], dateRanges: [String: UInt64], sizeRanges: [String: UInt64]) { + self.fileTypes = fileTypes + self.tags = tags + self.locations = locations + self.dateRanges = dateRanges + self.sizeRanges = sizeRanges + } +} + +public struct NetworkStartInput: Codable { +} + +/// Defines the search mode and performance characteristics +public enum SearchMode: Codable { + case fast + case normal + case full +} + + +/// Represents a file within the Spacedrive VDFS. +/// +/// This is a computed domain model that aggregates data from Entry, ContentIdentity, +/// Tags, and Sidecars. It provides a rich, developer-friendly interface without +/// duplicating data in the database. +public struct File: Codable { + public let id: String + public let sdPath: SdPath + public let name: String + public let size: UInt64 + public let contentIdentity: ContentIdentity? + public let alternatePaths: [SdPath] + public let tags: [Tag] + public let sidecars: [Sidecar] public let createdAt: String + public let modifiedAt: String + public let accessedAt: String? + public let contentKind: ContentKind + public let `extension`: String? + public let isLocal: Bool - private enum CodingKeys: String, CodingKey { - case uuid = "uuid" - case kind = "kind" - case hash = "hash" - case mediaData = "media_data" - case createdAt = "created_at" - } - - public init(uuid: String, kind: ContentKind, hash: String, mediaData: MediaData?, createdAt: String) { - self.uuid = uuid - self.kind = kind - self.hash = hash - self.mediaData = mediaData + public init(id: String, sdPath: SdPath, name: String, size: UInt64, contentIdentity: ContentIdentity?, alternatePaths: [SdPath], tags: [Tag], sidecars: [Sidecar], createdAt: String, modifiedAt: String, accessedAt: String?, contentKind: ContentKind, `extension`: String?, isLocal: Bool) { + self.id = id + self.sdPath = sdPath + self.name = name + self.size = size + self.contentIdentity = contentIdentity + self.alternatePaths = alternatePaths + self.tags = tags + self.sidecars = sidecars self.createdAt = createdAt + self.modifiedAt = modifiedAt + self.accessedAt = accessedAt + self.contentKind = contentKind + self.`extension` = `extension` + self.isLocal = isLocal + } +} +// MARK: - File Custom Codable Implementation +extension File { + private enum CodingKeys: String, CodingKey { + case id = "id" + case sdPath = "sd_path" + case name = "name" + case size = "size" + case contentIdentity = "content_identity" + case alternatePaths = "alternate_paths" + case tags = "tags" + case sidecars = "sidecars" + case createdAt = "created_at" + case modifiedAt = "modified_at" + case accessedAt = "accessed_at" + case contentKind = "content_kind" + case `extension` = "extension" + case isLocal = "is_local" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + sdPath = try container.decode(SdPath.self, forKey: .sdPath) + name = try container.decode(String.self, forKey: .name) + size = try container.decode(UInt64.self, forKey: .size) + contentIdentity = try container.decodeIfPresent(ContentIdentity.self, forKey: .contentIdentity) + alternatePaths = try container.decode([SdPath].self, forKey: .alternatePaths) + tags = try container.decode([Tag].self, forKey: .tags) + sidecars = try container.decode([Sidecar].self, forKey: .sidecars) + createdAt = try container.decode(String.self, forKey: .createdAt) + modifiedAt = try container.decode(String.self, forKey: .modifiedAt) + accessedAt = try container.decodeIfPresent(String.self, forKey: .accessedAt) + contentKind = try container.decode(ContentKind.self, forKey: .contentKind) + `extension` = try container.decodeIfPresent(String.self, forKey: .`extension`) + isLocal = try container.decode(Bool.self, forKey: .isLocal) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(sdPath, forKey: .sdPath) + try container.encode(name, forKey: .name) + try container.encode(size, forKey: .size) + try container.encode(contentIdentity, forKey: .contentIdentity) + try container.encode(alternatePaths, forKey: .alternatePaths) + try container.encode(tags, forKey: .tags) + try container.encode(sidecars, forKey: .sidecars) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(modifiedAt, forKey: .modifiedAt) + try container.encode(accessedAt, forKey: .accessedAt) + try container.encode(contentKind, forKey: .contentKind) + try container.encode(`extension`, forKey: .`extension`) + try container.encode(isLocal, forKey: .isLocal) } } -/// Core input structure for file copy operations -/// This is the canonical interface that all external APIs (CLI, GraphQL, REST) convert to -public struct FileCopyInput: Codable { - public let sources: SdPathBatch - public let destination: SdPath - public let overwrite: Bool - public let verifyChecksum: Bool - public let preserveTimestamps: Bool - public let moveFiles: Bool - public let copyMethod: CopyMethod - public let onConflict: FileConflictResolution? +/// Progress completion information +public struct ProgressCompletion: Codable { + public let completed: UInt64 + public let total: UInt64 + public let bytesCompleted: UInt64? + public let totalBytes: UInt64? + + public init(completed: UInt64, total: UInt64, bytesCompleted: UInt64?, totalBytes: UInt64?) { + self.completed = completed + self.total = total + self.bytesCompleted = bytesCompleted + self.totalBytes = totalBytes + } +} +// MARK: - ProgressCompletion Custom Codable Implementation +extension ProgressCompletion { private enum CodingKeys: String, CodingKey { - case sources = "sources" - case destination = "destination" - case overwrite = "overwrite" - case verifyChecksum = "verify_checksum" - case preserveTimestamps = "preserve_timestamps" - case moveFiles = "move_files" - case copyMethod = "copy_method" - case onConflict = "on_conflict" + case completed = "completed" + case total = "total" + case bytesCompleted = "bytes_completed" + case totalBytes = "total_bytes" } - public init(sources: SdPathBatch, destination: SdPath, overwrite: Bool, verifyChecksum: Bool, preserveTimestamps: Bool, moveFiles: Bool, copyMethod: CopyMethod, onConflict: FileConflictResolution?) { - self.sources = sources - self.destination = destination - self.overwrite = overwrite - self.verifyChecksum = verifyChecksum - self.preserveTimestamps = preserveTimestamps - self.moveFiles = moveFiles - self.copyMethod = copyMethod - self.onConflict = onConflict + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + completed = try container.decode(UInt64.self, forKey: .completed) + total = try container.decode(UInt64.self, forKey: .total) + bytesCompleted = try container.decodeIfPresent(UInt64.self, forKey: .bytesCompleted) + totalBytes = try container.decodeIfPresent(UInt64.self, forKey: .totalBytes) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(completed, forKey: .completed) + try container.encode(total, forKey: .total) + try container.encode(bytesCompleted, forKey: .bytesCompleted) + try container.encode(totalBytes, forKey: .totalBytes) } } -public struct TagSearchResult: Codable { - public let tag: Tag - public let relevance: Float - public let matchedVariant: String? - public let contextScore: Float? +/// Input for creating a new library +public struct LibraryCreateInput: Codable { + public let name: String + public let path: String? + + public init(name: String, path: String?) { + self.name = name + self.path = path + } +} +// MARK: - LibraryCreateInput Custom Codable Implementation +extension LibraryCreateInput { private enum CodingKeys: String, CodingKey { - case tag = "tag" - case relevance = "relevance" - case matchedVariant = "matched_variant" - case contextScore = "context_score" + case name = "name" + case path = "path" } - public init(tag: Tag, relevance: Float, matchedVariant: String?, contextScore: Float?) { - self.tag = tag - self.relevance = relevance - self.matchedVariant = matchedVariant - self.contextScore = contextScore + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + path = try container.decodeIfPresent(String.self, forKey: .path) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(path, forKey: .path) + } +} + + +/// Media-specific metadata +public struct MediaData: Codable { + public let width: UInt32? + public let height: UInt32? + public let duration: Double? + public let bitrate: UInt32? + public let fps: Float? + public let exif: ExifData? + public let extra: JsonValue + + public init(width: UInt32?, height: UInt32?, duration: Double?, bitrate: UInt32?, fps: Float?, exif: ExifData?, extra: JsonValue) { + self.width = width + self.height = height + self.duration = duration + self.bitrate = bitrate + self.fps = fps + self.exif = exif + self.extra = extra + } +} +// MARK: - MediaData Custom Codable Implementation +extension MediaData { + private enum CodingKeys: String, CodingKey { + case width = "width" + case height = "height" + case duration = "duration" + case bitrate = "bitrate" + case fps = "fps" + case exif = "exif" + case extra = "extra" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + width = try container.decodeIfPresent(UInt32.self, forKey: .width) + height = try container.decodeIfPresent(UInt32.self, forKey: .height) + duration = try container.decodeIfPresent(Double.self, forKey: .duration) + bitrate = try container.decodeIfPresent(UInt32.self, forKey: .bitrate) + fps = try container.decodeIfPresent(Float.self, forKey: .fps) + exif = try container.decodeIfPresent(ExifData.self, forKey: .exif) + extra = try container.decode(JsonValue.self, forKey: .extra) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + try container.encode(duration, forKey: .duration) + try container.encode(bitrate, forKey: .bitrate) + try container.encode(fps, forKey: .fps) + try container.encode(exif, forKey: .exif) + try container.encode(extra, forKey: .extra) + } +} + + +/// Output from volume speed test operation +public struct VolumeSpeedTestOutput: Codable { + public let fingerprint: VolumeFingerprint + public let readSpeedMbps: UInt32? + public let writeSpeedMbps: UInt32? + + public init(fingerprint: VolumeFingerprint, readSpeedMbps: UInt32?, writeSpeedMbps: UInt32?) { + self.fingerprint = fingerprint + self.readSpeedMbps = readSpeedMbps + self.writeSpeedMbps = writeSpeedMbps + } +} +// MARK: - VolumeSpeedTestOutput Custom Codable Implementation +extension VolumeSpeedTestOutput { + private enum CodingKeys: String, CodingKey { + case fingerprint = "fingerprint" + case readSpeedMbps = "read_speed_mbps" + case writeSpeedMbps = "write_speed_mbps" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + fingerprint = try container.decode(VolumeFingerprint.self, forKey: .fingerprint) + readSpeedMbps = try container.decodeIfPresent(UInt32.self, forKey: .readSpeedMbps) + writeSpeedMbps = try container.decodeIfPresent(UInt32.self, forKey: .writeSpeedMbps) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fingerprint, forKey: .fingerprint) + try container.encode(readSpeedMbps, forKey: .readSpeedMbps) + try container.encode(writeSpeedMbps, forKey: .writeSpeedMbps) + } +} + + +public struct PairGenerateOutput: Codable { + public let code: String + public let sessionId: String + public let expiresAt: String + + private enum CodingKeys: String, CodingKey { + case code = "code" + case sessionId = "session_id" + case expiresAt = "expires_at" + } + + public init(code: String, sessionId: String, expiresAt: String) { + self.code = code + self.sessionId = sessionId + self.expiresAt = expiresAt } } @@ -2175,82 +3085,309 @@ public struct ListLibrariesInput: Codable { } } -/// Represents how the volume is mounted in the system -public enum MountType: Codable { - case system - case external - case network - case virtual +public struct ServiceState: Codable { + public let running: Bool + public let details: String? + + public init(running: Bool, details: String?) { + self.running = running + self.details = details + } +} +// MARK: - ServiceState Custom Codable Implementation +extension ServiceState { + private enum CodingKeys: String, CodingKey { + case running = "running" + case details = "details" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + running = try container.decode(Bool.self, forKey: .running) + details = try container.decodeIfPresent(String.self, forKey: .details) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(running, forKey: .running) + try container.encode(details, forKey: .details) + } } -/// Output containing files that are unique to the specified location -public struct UniqueToLocationOutput: Codable { - public let uniqueFiles: [File] - public let totalCount: UInt32 - public let totalSize: UInt64 +/// Determines whether indexing results are persisted to database or kept in memory +public enum IndexPersistence: Codable { + case persistent + case ephemeral +} + + +/// Library-specific settings +public struct LibrarySettings: Codable { + public let generateThumbnails: Bool + public let thumbnailQuality: UInt8 + public let enableAiTagging: Bool + public let syncEnabled: Bool + public let encryptionEnabled: Bool + public let thumbnailSizes: [UInt32] + public let ignoredExtensions: [String] + public let maxFileSize: UInt64? + public let autoTrackSystemVolumes: Bool + public let autoTrackExternalVolumes: Bool + public let indexer: IndexerSettings? + + public init(generateThumbnails: Bool, thumbnailQuality: UInt8, enableAiTagging: Bool, syncEnabled: Bool, encryptionEnabled: Bool, thumbnailSizes: [UInt32], ignoredExtensions: [String], maxFileSize: UInt64?, autoTrackSystemVolumes: Bool, autoTrackExternalVolumes: Bool, indexer: IndexerSettings?) { + self.generateThumbnails = generateThumbnails + self.thumbnailQuality = thumbnailQuality + self.enableAiTagging = enableAiTagging + self.syncEnabled = syncEnabled + self.encryptionEnabled = encryptionEnabled + self.thumbnailSizes = thumbnailSizes + self.ignoredExtensions = ignoredExtensions + self.maxFileSize = maxFileSize + self.autoTrackSystemVolumes = autoTrackSystemVolumes + self.autoTrackExternalVolumes = autoTrackExternalVolumes + self.indexer = indexer + } +} +// MARK: - LibrarySettings Custom Codable Implementation +extension LibrarySettings { + private enum CodingKeys: String, CodingKey { + case generateThumbnails = "generate_thumbnails" + case thumbnailQuality = "thumbnail_quality" + case enableAiTagging = "enable_ai_tagging" + case syncEnabled = "sync_enabled" + case encryptionEnabled = "encryption_enabled" + case thumbnailSizes = "thumbnail_sizes" + case ignoredExtensions = "ignored_extensions" + case maxFileSize = "max_file_size" + case autoTrackSystemVolumes = "auto_track_system_volumes" + case autoTrackExternalVolumes = "auto_track_external_volumes" + case indexer = "indexer" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + generateThumbnails = try container.decode(Bool.self, forKey: .generateThumbnails) + thumbnailQuality = try container.decode(UInt8.self, forKey: .thumbnailQuality) + enableAiTagging = try container.decode(Bool.self, forKey: .enableAiTagging) + syncEnabled = try container.decode(Bool.self, forKey: .syncEnabled) + encryptionEnabled = try container.decode(Bool.self, forKey: .encryptionEnabled) + thumbnailSizes = try container.decode([UInt32].self, forKey: .thumbnailSizes) + ignoredExtensions = try container.decode([String].self, forKey: .ignoredExtensions) + maxFileSize = try container.decodeIfPresent(UInt64.self, forKey: .maxFileSize) + autoTrackSystemVolumes = try container.decode(Bool.self, forKey: .autoTrackSystemVolumes) + autoTrackExternalVolumes = try container.decode(Bool.self, forKey: .autoTrackExternalVolumes) + indexer = try container.decode(IndexerSettings.self, forKey: .indexer) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(generateThumbnails, forKey: .generateThumbnails) + try container.encode(thumbnailQuality, forKey: .thumbnailQuality) + try container.encode(enableAiTagging, forKey: .enableAiTagging) + try container.encode(syncEnabled, forKey: .syncEnabled) + try container.encode(encryptionEnabled, forKey: .encryptionEnabled) + try container.encode(thumbnailSizes, forKey: .thumbnailSizes) + try container.encode(ignoredExtensions, forKey: .ignoredExtensions) + try container.encode(maxFileSize, forKey: .maxFileSize) + try container.encode(autoTrackSystemVolumes, forKey: .autoTrackSystemVolumes) + try container.encode(autoTrackExternalVolumes, forKey: .autoTrackExternalVolumes) + try container.encode(indexer, forKey: .indexer) + } +} + + +/// Performance and timing metrics +public struct PerformanceMetrics: Codable { + public let rate: Float + public let estimatedRemaining: RustDuration? + public let elapsed: RustDuration? + public let errorCount: UInt64 + public let warningCount: UInt64 + + public init(rate: Float, estimatedRemaining: RustDuration?, elapsed: RustDuration?, errorCount: UInt64, warningCount: UInt64) { + self.rate = rate + self.estimatedRemaining = estimatedRemaining + self.elapsed = elapsed + self.errorCount = errorCount + self.warningCount = warningCount + } +} +// MARK: - PerformanceMetrics Custom Codable Implementation +extension PerformanceMetrics { + private enum CodingKeys: String, CodingKey { + case rate = "rate" + case estimatedRemaining = "estimated_remaining" + case elapsed = "elapsed" + case errorCount = "error_count" + case warningCount = "warning_count" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + rate = try container.decode(Float.self, forKey: .rate) + estimatedRemaining = try container.decodeIfPresent(RustDuration.self, forKey: .estimatedRemaining) + elapsed = try container.decodeIfPresent(RustDuration.self, forKey: .elapsed) + errorCount = try container.decode(UInt64.self, forKey: .errorCount) + warningCount = try container.decode(UInt64.self, forKey: .warningCount) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(rate, forKey: .rate) + try container.encode(estimatedRemaining, forKey: .estimatedRemaining) + try container.encode(elapsed, forKey: .elapsed) + try container.encode(errorCount, forKey: .errorCount) + try container.encode(warningCount, forKey: .warningCount) + } +} + + +public struct PairCancelInput: Codable { + public let sessionId: String private enum CodingKeys: String, CodingKey { - case uniqueFiles = "unique_files" - case totalCount = "total_count" - case totalSize = "total_size" + case sessionId = "session_id" } - public init(uniqueFiles: [File], totalCount: UInt32, totalSize: UInt64) { - self.uniqueFiles = uniqueFiles - self.totalCount = totalCount - self.totalSize = totalSize + public init(sessionId: String) { + self.sessionId = sessionId } } -/// Input for directory listing -public struct DirectoryListingInput: Codable { +/// Defines the scope of the filesystem to search within +public enum SearchScope { + case library + case location(SearchScopeLocationData) + case path(SearchScopePathData) +} +public struct SearchScopeLocationData: Codable { + public let locationId: String + + private enum CodingKeys: String, CodingKey { + case locationId = "location_id" + } +} + +public struct SearchScopePathData: Codable { public let path: SdPath - public let limit: UInt32? - public let includeHidden: Bool? - public let sortBy: DirectorySortBy +} + +// MARK: - SearchScope Codable Implementation +extension SearchScope: Codable { private enum CodingKeys: String, CodingKey { - case path = "path" - case limit = "limit" - case includeHidden = "include_hidden" - case sortBy = "sort_by" + case library = "Library" + case location = "Location" + case path = "Path" } - public init(path: SdPath, limit: UInt32?, includeHidden: Bool?, sortBy: DirectorySortBy) { - self.path = path - self.limit = limit - self.includeHidden = includeHidden - self.sortBy = sortBy + public init(from decoder: Decoder) throws { + // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + if container.allKeys.count == 1 { + let key = container.allKeys.first! + switch key { + case .library: + self = .library + return + case .location: + let data = try container.decode(SearchScopeLocationData.self, forKey: .location) + self = .location(data) + return + case .path: + let data = try container.decode(SearchScopePathData.self, forKey: .path) + self = .path(data) + return + } + return + } + } + + // Fallback: try decoding as plain string for unit variants (serde default) + if let stringContainer = try? decoder.singleValueContainer() { + if let variantString = try? stringContainer.decode(String.self) { + switch variantString { + case "Library": + self = .library + return + default: + break + } + } + } + + throw DecodingError.dataCorrupted( + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .library: + try container.encodeNil(forKey: .library) + case .location(let data): + try container.encode(data, forKey: .location) + case .path(let data): + try container.encode(data, forKey: .path) + } } } -public struct ApplyTagsInput: Codable { - public let entryIds: [Int32] - public let tagIds: [String] - public let source: TagSource? - public let confidence: Float? - public let appliedContext: String? - public let instanceAttributes: [String: JsonValue]? - private enum CodingKeys: String, CodingKey { - case entryIds = "entry_ids" - case tagIds = "tag_ids" - case source = "source" - case confidence = "confidence" - case appliedContext = "applied_context" - case instanceAttributes = "instance_attributes" - } +/// Detailed breakdown of how the score was calculated +public struct ScoreBreakdown: Codable { + public let temporalScore: Float + public let semanticScore: Float? + public let metadataScore: Float + public let recencyBoost: Float + public let userPreferenceBoost: Float + public let finalScore: Float - public init(entryIds: [Int32], tagIds: [String], source: TagSource?, confidence: Float?, appliedContext: String?, instanceAttributes: [String: JsonValue]?) { - self.entryIds = entryIds - self.tagIds = tagIds - self.source = source - self.confidence = confidence - self.appliedContext = appliedContext - self.instanceAttributes = instanceAttributes + public init(temporalScore: Float, semanticScore: Float?, metadataScore: Float, recencyBoost: Float, userPreferenceBoost: Float, finalScore: Float) { + self.temporalScore = temporalScore + self.semanticScore = semanticScore + self.metadataScore = metadataScore + self.recencyBoost = recencyBoost + self.userPreferenceBoost = userPreferenceBoost + self.finalScore = finalScore } } +// MARK: - ScoreBreakdown Custom Codable Implementation +extension ScoreBreakdown { + private enum CodingKeys: String, CodingKey { + case temporalScore = "temporal_score" + case semanticScore = "semantic_score" + case metadataScore = "metadata_score" + case recencyBoost = "recency_boost" + case userPreferenceBoost = "user_preference_boost" + case finalScore = "final_score" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + temporalScore = try container.decode(Float.self, forKey: .temporalScore) + semanticScore = try container.decodeIfPresent(Float.self, forKey: .semanticScore) + metadataScore = try container.decode(Float.self, forKey: .metadataScore) + recencyBoost = try container.decode(Float.self, forKey: .recencyBoost) + userPreferenceBoost = try container.decode(Float.self, forKey: .userPreferenceBoost) + finalScore = try container.decode(Float.self, forKey: .finalScore) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(temporalScore, forKey: .temporalScore) + try container.encode(semanticScore, forKey: .semanticScore) + try container.encode(metadataScore, forKey: .metadataScore) + try container.encode(recencyBoost, forKey: .recencyBoost) + try container.encode(userPreferenceBoost, forKey: .userPreferenceBoost) + try container.encode(finalScore, forKey: .finalScore) + } +} + public struct JobPauseInput: Codable { public let jobId: String @@ -2264,229 +3401,6 @@ public struct JobPauseInput: Codable { } } -/// Represents any filesystem entry (file or directory) in the VDFS -public struct Entry: Codable { - public let id: String - public let sdPath: SdPathSerialized - public let name: String - public let kind: EntryKind - public let size: UInt64? - public let createdAt: String? - public let modifiedAt: String? - public let accessedAt: String? - public let inode: UInt64? - public let fileId: UInt64? - public let parentId: String? - public let locationId: String? - public let metadataId: String - public let contentId: String? - public let firstSeenAt: String - public let lastIndexedAt: String? - - private enum CodingKeys: String, CodingKey { - case id = "id" - case sdPath = "sd_path" - case name = "name" - case kind = "kind" - case size = "size" - case createdAt = "created_at" - case modifiedAt = "modified_at" - case accessedAt = "accessed_at" - case inode = "inode" - case fileId = "file_id" - case parentId = "parent_id" - case locationId = "location_id" - case metadataId = "metadata_id" - case contentId = "content_id" - case firstSeenAt = "first_seen_at" - case lastIndexedAt = "last_indexed_at" - } - - public init(id: String, sdPath: SdPathSerialized, name: String, kind: EntryKind, size: UInt64?, createdAt: String?, modifiedAt: String?, accessedAt: String?, inode: UInt64?, fileId: UInt64?, parentId: String?, locationId: String?, metadataId: String, contentId: String?, firstSeenAt: String, lastIndexedAt: String?) { - self.id = id - self.sdPath = sdPath - self.name = name - self.kind = kind - self.size = size - self.createdAt = createdAt - self.modifiedAt = modifiedAt - self.accessedAt = accessedAt - self.inode = inode - self.fileId = fileId - self.parentId = parentId - self.locationId = locationId - self.metadataId = metadataId - self.contentId = contentId - self.firstSeenAt = firstSeenAt - self.lastIndexedAt = lastIndexedAt - } -} - -/// Output from library create action dispatch -public struct LibraryCreateOutput: Codable { - public let libraryId: String - public let name: String - public let path: String - - private enum CodingKeys: String, CodingKey { - case libraryId = "library_id" - case name = "name" - case path = "path" - } - - public init(libraryId: String, name: String, path: String) { - self.libraryId = libraryId - self.name = name - self.path = path - } -} - -public struct NetworkStatusQueryInput: Codable { -} - -/// Comprehensive metrics for indexing operations -public struct IndexerMetrics: Codable { - public let totalDuration: RustDuration - public let discoveryDuration: RustDuration - public let processingDuration: RustDuration - public let contentDuration: RustDuration - public let filesPerSecond: Float - public let bytesPerSecond: Double - public let dirsPerSecond: Float - public let dbWrites: UInt64 - public let dbReads: UInt64 - public let batchCount: UInt64 - public let avgBatchSize: Float - public let totalErrors: UInt64 - public let criticalErrors: UInt64 - public let nonCriticalErrors: UInt64 - public let skippedPaths: UInt64 - public let peakMemoryBytes: UInt64? - public let avgMemoryBytes: UInt64? - - private enum CodingKeys: String, CodingKey { - case totalDuration = "total_duration" - case discoveryDuration = "discovery_duration" - case processingDuration = "processing_duration" - case contentDuration = "content_duration" - case filesPerSecond = "files_per_second" - case bytesPerSecond = "bytes_per_second" - case dirsPerSecond = "dirs_per_second" - case dbWrites = "db_writes" - case dbReads = "db_reads" - case batchCount = "batch_count" - case avgBatchSize = "avg_batch_size" - case totalErrors = "total_errors" - case criticalErrors = "critical_errors" - case nonCriticalErrors = "non_critical_errors" - case skippedPaths = "skipped_paths" - case peakMemoryBytes = "peak_memory_bytes" - case avgMemoryBytes = "avg_memory_bytes" - } - - public init(totalDuration: RustDuration, discoveryDuration: RustDuration, processingDuration: RustDuration, contentDuration: RustDuration, filesPerSecond: Float, bytesPerSecond: Double, dirsPerSecond: Float, dbWrites: UInt64, dbReads: UInt64, batchCount: UInt64, avgBatchSize: Float, totalErrors: UInt64, criticalErrors: UInt64, nonCriticalErrors: UInt64, skippedPaths: UInt64, peakMemoryBytes: UInt64?, avgMemoryBytes: UInt64?) { - self.totalDuration = totalDuration - self.discoveryDuration = discoveryDuration - self.processingDuration = processingDuration - self.contentDuration = contentDuration - self.filesPerSecond = filesPerSecond - self.bytesPerSecond = bytesPerSecond - self.dirsPerSecond = dirsPerSecond - self.dbWrites = dbWrites - self.dbReads = dbReads - self.batchCount = batchCount - self.avgBatchSize = avgBatchSize - self.totalErrors = totalErrors - self.criticalErrors = criticalErrors - self.nonCriticalErrors = nonCriticalErrors - self.skippedPaths = skippedPaths - self.peakMemoryBytes = peakMemoryBytes - self.avgMemoryBytes = avgMemoryBytes - } -} - -public struct DeviceRevokeOutput: Codable { - public let revoked: Bool - - public init(revoked: Bool) { - self.revoked = revoked - } -} - -public struct JobCancelInput: Codable { - public let jobId: String - - private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - } - - public init(jobId: String) { - self.jobId = jobId - } -} - -/// Filter for file size in bytes -public struct SizeRangeFilter: Codable { - public let min: UInt64? - public let max: UInt64? - - public init(min: UInt64?, max: UInt64?) { - self.min = min - self.max = max - } -} - -/// Unique fingerprint for a storage volume -public struct VolumeFingerprint: Codable { - let value: String -} - -public struct ServiceStatus: Codable { - public let locationWatcher: ServiceState - public let networking: ServiceState - public let volumeMonitor: ServiceState - public let fileSharing: ServiceState - - private enum CodingKeys: String, CodingKey { - case locationWatcher = "location_watcher" - case networking = "networking" - case volumeMonitor = "volume_monitor" - case fileSharing = "file_sharing" - } - - public init(locationWatcher: ServiceState, networking: ServiceState, volumeMonitor: ServiceState, fileSharing: ServiceState) { - self.locationWatcher = locationWatcher - self.networking = networking - self.volumeMonitor = volumeMonitor - self.fileSharing = fileSharing - } -} - -/// Summary information about a volume (for updates and caching) -public struct VolumeInfo: Codable { - public let isMounted: Bool - public let totalBytesAvailable: UInt64 - public let readSpeedMbps: UInt64? - public let writeSpeedMbps: UInt64? - public let errorStatus: String? - - private enum CodingKeys: String, CodingKey { - case isMounted = "is_mounted" - case totalBytesAvailable = "total_bytes_available" - case readSpeedMbps = "read_speed_mbps" - case writeSpeedMbps = "write_speed_mbps" - case errorStatus = "error_status" - } - - public init(isMounted: Bool, totalBytesAvailable: UInt64, readSpeedMbps: UInt64?, writeSpeedMbps: UInt64?, errorStatus: String?) { - self.isMounted = isMounted - self.totalBytesAvailable = totalBytesAvailable - self.readSpeedMbps = readSpeedMbps - self.writeSpeedMbps = writeSpeedMbps - self.errorStatus = errorStatus - } -} - public struct SearchTagsOutput: Codable { public let tags: [TagSearchResult] public let totalFound: UInt @@ -2511,6 +3425,798 @@ public struct SearchTagsOutput: Codable { } } +public struct DeviceInfo: Codable { + public let id: String + public let name: String + public let os: String + public let hardwareModel: String? + public let createdAt: String + + public init(id: String, name: String, os: String, hardwareModel: String?, createdAt: String) { + self.id = id + self.name = name + self.os = os + self.hardwareModel = hardwareModel + self.createdAt = createdAt + } +} +// MARK: - DeviceInfo Custom Codable Implementation +extension DeviceInfo { + private enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case os = "os" + case hardwareModel = "hardware_model" + case createdAt = "created_at" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + name = try container.decode(String.self, forKey: .name) + os = try container.decode(String.self, forKey: .os) + hardwareModel = try container.decodeIfPresent(String.self, forKey: .hardwareModel) + createdAt = try container.decode(String.self, forKey: .createdAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(os, forKey: .os) + try container.encode(hardwareModel, forKey: .hardwareModel) + try container.encode(createdAt, forKey: .createdAt) + } +} + + +/// Statistics collected during indexing +public struct IndexerStats: Codable { + public let files: UInt64 + public let dirs: UInt64 + public let bytes: UInt64 + public let symlinks: UInt64 + public let skipped: UInt64 + public let errors: UInt64 + + public init(files: UInt64, dirs: UInt64, bytes: UInt64, symlinks: UInt64, skipped: UInt64, errors: UInt64) { + self.files = files + self.dirs = dirs + self.bytes = bytes + self.symlinks = symlinks + self.skipped = skipped + self.errors = errors + } +} + +public struct PairingSessionSummary: Codable { + public let id: String + public let state: SerializablePairingState + public let remoteDeviceId: String? + public let expiresAt: String? + + public init(id: String, state: SerializablePairingState, remoteDeviceId: String?, expiresAt: String?) { + self.id = id + self.state = state + self.remoteDeviceId = remoteDeviceId + self.expiresAt = expiresAt + } +} +// MARK: - PairingSessionSummary Custom Codable Implementation +extension PairingSessionSummary { + private enum CodingKeys: String, CodingKey { + case id = "id" + case state = "state" + case remoteDeviceId = "remote_device_id" + case expiresAt = "expires_at" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + state = try container.decode(SerializablePairingState.self, forKey: .state) + remoteDeviceId = try container.decodeIfPresent(String.self, forKey: .remoteDeviceId) + expiresAt = try container.decodeIfPresent(String.self, forKey: .expiresAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(state, forKey: .state) + try container.encode(remoteDeviceId, forKey: .remoteDeviceId) + try container.encode(expiresAt, forKey: .expiresAt) + } +} + + +/// Represents the filesystem type of the volume +public enum FileSystem { + case nTFS + case fAT32 + case eXT4 + case aPFS + case exFAT + case btrfs + case zFS + case reFS + case other(String) +} + +// MARK: - FileSystem Codable Implementation +extension FileSystem: Codable { + private enum CodingKeys: String, CodingKey { + case nTFS = "NTFS" + case fAT32 = "FAT32" + case eXT4 = "EXT4" + case aPFS = "APFS" + case exFAT = "ExFAT" + case btrfs = "Btrfs" + case zFS = "ZFS" + case reFS = "ReFS" + case other = "Other" + } + + public init(from decoder: Decoder) throws { + // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + if container.allKeys.count == 1 { + let key = container.allKeys.first! + switch key { + case .nTFS: + self = .nTFS + return + case .fAT32: + self = .fAT32 + return + case .eXT4: + self = .eXT4 + return + case .aPFS: + self = .aPFS + return + case .exFAT: + self = .exFAT + return + case .btrfs: + self = .btrfs + return + case .zFS: + self = .zFS + return + case .reFS: + self = .reFS + return + case .other: + var arrayContainer = try container.nestedUnkeyedContainer(forKey: .other) + let value0 = try arrayContainer.decode(String.self) + self = .other(value0) + return + } + return + } + } + + // Fallback: try decoding as plain string for unit variants (serde default) + if let stringContainer = try? decoder.singleValueContainer() { + if let variantString = try? stringContainer.decode(String.self) { + switch variantString { + case "NTFS": + self = .nTFS + return + case "FAT32": + self = .fAT32 + return + case "EXT4": + self = .eXT4 + return + case "APFS": + self = .aPFS + return + case "ExFAT": + self = .exFAT + return + case "Btrfs": + self = .btrfs + return + case "ZFS": + self = .zFS + return + case "ReFS": + self = .reFS + return + default: + break + } + } + } + + throw DecodingError.dataCorrupted( + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .nTFS: + try container.encodeNil(forKey: .nTFS) + case .fAT32: + try container.encodeNil(forKey: .fAT32) + case .eXT4: + try container.encodeNil(forKey: .eXT4) + case .aPFS: + try container.encodeNil(forKey: .aPFS) + case .exFAT: + try container.encodeNil(forKey: .exFAT) + case .btrfs: + try container.encodeNil(forKey: .btrfs) + case .zFS: + try container.encodeNil(forKey: .zFS) + case .reFS: + try container.encodeNil(forKey: .reFS) + case .other(let value0): + var arrayContainer = container.nestedUnkeyedContainer(forKey: .other) + try arrayContainer.encode(value0) + } + } +} + + +/// Generic progress information that all job types can convert into +public struct GenericProgress: Codable { + public let percentage: Float + public let phase: String + public let currentPath: SdPath? + public let message: String + public let completion: ProgressCompletion + public let performance: PerformanceMetrics + + public init(percentage: Float, phase: String, currentPath: SdPath?, message: String, completion: ProgressCompletion, performance: PerformanceMetrics) { + self.percentage = percentage + self.phase = phase + self.currentPath = currentPath + self.message = message + self.completion = completion + self.performance = performance + } +} +// MARK: - GenericProgress Custom Codable Implementation +extension GenericProgress { + private enum CodingKeys: String, CodingKey { + case percentage = "percentage" + case phase = "phase" + case currentPath = "current_path" + case message = "message" + case completion = "completion" + case performance = "performance" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + percentage = try container.decode(Float.self, forKey: .percentage) + phase = try container.decode(String.self, forKey: .phase) + currentPath = try container.decodeIfPresent(SdPath.self, forKey: .currentPath) + message = try container.decode(String.self, forKey: .message) + completion = try container.decode(ProgressCompletion.self, forKey: .completion) + performance = try container.decode(PerformanceMetrics.self, forKey: .performance) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(percentage, forKey: .percentage) + try container.encode(phase, forKey: .phase) + try container.encode(currentPath, forKey: .currentPath) + try container.encode(message, forKey: .message) + try container.encode(completion, forKey: .completion) + try container.encode(performance, forKey: .performance) + } +} + + +public struct JobListItem: Codable { + public let id: String + public let name: String + public let status: JobStatus + public let progress: Float + public let actionType: String? + public let actionContext: ActionContextInfo? + + public init(id: String, name: String, status: JobStatus, progress: Float, actionType: String?, actionContext: ActionContextInfo?) { + self.id = id + self.name = name + self.status = status + self.progress = progress + self.actionType = actionType + self.actionContext = actionContext + } +} +// MARK: - JobListItem Custom Codable Implementation +extension JobListItem { + private enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + case progress = "progress" + case actionType = "action_type" + case actionContext = "action_context" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + name = try container.decode(String.self, forKey: .name) + status = try container.decode(JobStatus.self, forKey: .status) + progress = try container.decode(Float.self, forKey: .progress) + actionType = try container.decodeIfPresent(String.self, forKey: .actionType) + actionContext = try container.decodeIfPresent(ActionContextInfo.self, forKey: .actionContext) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(status, forKey: .status) + try container.encode(progress, forKey: .progress) + try container.encode(actionType, forKey: .actionType) + try container.encode(actionContext, forKey: .actionContext) + } +} + + +/// Query to get a file by its local path with all related data +public struct FileByPathQuery: Codable { + public let path: String + + public init(path: String) { + self.path = path + } +} + +/// Summary information about a volume (for updates and caching) +public struct VolumeInfo: Codable { + public let isMounted: Bool + public let totalBytesAvailable: UInt64 + public let readSpeedMbps: UInt64? + public let writeSpeedMbps: UInt64? + public let errorStatus: String? + + public init(isMounted: Bool, totalBytesAvailable: UInt64, readSpeedMbps: UInt64?, writeSpeedMbps: UInt64?, errorStatus: String?) { + self.isMounted = isMounted + self.totalBytesAvailable = totalBytesAvailable + self.readSpeedMbps = readSpeedMbps + self.writeSpeedMbps = writeSpeedMbps + self.errorStatus = errorStatus + } +} +// MARK: - VolumeInfo Custom Codable Implementation +extension VolumeInfo { + private enum CodingKeys: String, CodingKey { + case isMounted = "is_mounted" + case totalBytesAvailable = "total_bytes_available" + case readSpeedMbps = "read_speed_mbps" + case writeSpeedMbps = "write_speed_mbps" + case errorStatus = "error_status" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + isMounted = try container.decode(Bool.self, forKey: .isMounted) + totalBytesAvailable = try container.decode(UInt64.self, forKey: .totalBytesAvailable) + readSpeedMbps = try container.decodeIfPresent(UInt64.self, forKey: .readSpeedMbps) + writeSpeedMbps = try container.decodeIfPresent(UInt64.self, forKey: .writeSpeedMbps) + errorStatus = try container.decodeIfPresent(String.self, forKey: .errorStatus) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(isMounted, forKey: .isMounted) + try container.encode(totalBytesAvailable, forKey: .totalBytesAvailable) + try container.encode(readSpeedMbps, forKey: .readSpeedMbps) + try container.encode(writeSpeedMbps, forKey: .writeSpeedMbps) + try container.encode(errorStatus, forKey: .errorStatus) + } +} + + +/// Individual search result +public struct FileSearchResult: Codable { + public let entry: Entry + public let score: Float + public let scoreBreakdown: ScoreBreakdown + public let highlights: [TextHighlight] + public let matchedContent: String? + + public init(entry: Entry, score: Float, scoreBreakdown: ScoreBreakdown, highlights: [TextHighlight], matchedContent: String?) { + self.entry = entry + self.score = score + self.scoreBreakdown = scoreBreakdown + self.highlights = highlights + self.matchedContent = matchedContent + } +} +// MARK: - FileSearchResult Custom Codable Implementation +extension FileSearchResult { + private enum CodingKeys: String, CodingKey { + case entry = "entry" + case score = "score" + case scoreBreakdown = "score_breakdown" + case highlights = "highlights" + case matchedContent = "matched_content" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + entry = try container.decode(Entry.self, forKey: .entry) + score = try container.decode(Float.self, forKey: .score) + scoreBreakdown = try container.decode(ScoreBreakdown.self, forKey: .scoreBreakdown) + highlights = try container.decode([TextHighlight].self, forKey: .highlights) + matchedContent = try container.decodeIfPresent(String.self, forKey: .matchedContent) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(entry, forKey: .entry) + try container.encode(score, forKey: .score) + try container.encode(scoreBreakdown, forKey: .scoreBreakdown) + try container.encode(highlights, forKey: .highlights) + try container.encode(matchedContent, forKey: .matchedContent) + } +} + + +public struct ListDevicesInput: Codable { + public let pairedOnly: Bool + public let connectedOnly: Bool + + private enum CodingKeys: String, CodingKey { + case pairedOnly = "paired_only" + case connectedOnly = "connected_only" + } + + public init(pairedOnly: Bool, connectedOnly: Bool) { + self.pairedOnly = pairedOnly + self.connectedOnly = connectedOnly + } +} + +/// Output from volume track operation +public struct VolumeTrackOutput: Codable { + public let fingerprint: VolumeFingerprint + public let volumeName: String + + private enum CodingKeys: String, CodingKey { + case fingerprint = "fingerprint" + case volumeName = "volume_name" + } + + public init(fingerprint: VolumeFingerprint, volumeName: String) { + self.fingerprint = fingerprint + self.volumeName = volumeName + } +} + +/// APFS volume roles in the container +public enum ApfsVolumeRole { + case system + case data + case preboot + case recovery + case vM + case other(String) +} + +// MARK: - ApfsVolumeRole Codable Implementation +extension ApfsVolumeRole: Codable { + private enum CodingKeys: String, CodingKey { + case system = "System" + case data = "Data" + case preboot = "Preboot" + case recovery = "Recovery" + case vM = "VM" + case other = "Other" + } + + public init(from decoder: Decoder) throws { + // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + if container.allKeys.count == 1 { + let key = container.allKeys.first! + switch key { + case .system: + self = .system + return + case .data: + self = .data + return + case .preboot: + self = .preboot + return + case .recovery: + self = .recovery + return + case .vM: + self = .vM + return + case .other: + var arrayContainer = try container.nestedUnkeyedContainer(forKey: .other) + let value0 = try arrayContainer.decode(String.self) + self = .other(value0) + return + } + return + } + } + + // Fallback: try decoding as plain string for unit variants (serde default) + if let stringContainer = try? decoder.singleValueContainer() { + if let variantString = try? stringContainer.decode(String.self) { + switch variantString { + case "System": + self = .system + return + case "Data": + self = .data + return + case "Preboot": + self = .preboot + return + case "Recovery": + self = .recovery + return + case "VM": + self = .vM + return + default: + break + } + } + } + + throw DecodingError.dataCorrupted( + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .system: + try container.encodeNil(forKey: .system) + case .data: + try container.encodeNil(forKey: .data) + case .preboot: + try container.encodeNil(forKey: .preboot) + case .recovery: + try container.encodeNil(forKey: .recovery) + case .vM: + try container.encodeNil(forKey: .vM) + case .other(let value0): + var arrayContainer = container.nestedUnkeyedContainer(forKey: .other) + try arrayContainer.encode(value0) + } + } +} + + +/// Raw filesystem event kinds emitted by the watcher without DB resolution +public enum FsRawEventKind { + case create(FsRawEventKindCreateData) + case modify(FsRawEventKindModifyData) + case remove(FsRawEventKindRemoveData) + case rename(FsRawEventKindRenameData) +} +public struct FsRawEventKindCreateData: Codable { + public let path: String +} + +public struct FsRawEventKindModifyData: Codable { + public let path: String +} + +public struct FsRawEventKindRemoveData: Codable { + public let path: String +} + +public struct FsRawEventKindRenameData: Codable { + public let from: String + public let to: String +} + + +// MARK: - FsRawEventKind Codable Implementation +extension FsRawEventKind: Codable { + private enum CodingKeys: String, CodingKey { + case create = "Create" + case modify = "Modify" + case remove = "Remove" + case rename = "Rename" + } + + public init(from decoder: Decoder) throws { + // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + if container.allKeys.count == 1 { + let key = container.allKeys.first! + switch key { + case .create: + let data = try container.decode(FsRawEventKindCreateData.self, forKey: .create) + self = .create(data) + return + case .modify: + let data = try container.decode(FsRawEventKindModifyData.self, forKey: .modify) + self = .modify(data) + return + case .remove: + let data = try container.decode(FsRawEventKindRemoveData.self, forKey: .remove) + self = .remove(data) + return + case .rename: + let data = try container.decode(FsRawEventKindRenameData.self, forKey: .rename) + self = .rename(data) + return + } + return + } + } + + // Fallback: try decoding as plain string for unit variants (serde default) + if let stringContainer = try? decoder.singleValueContainer() { + if let variantString = try? stringContainer.decode(String.self) { + switch variantString { + default: + break + } + } + } + + throw DecodingError.dataCorrupted( + DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .create(let data): + try container.encode(data, forKey: .create) + case .modify(let data): + try container.encode(data, forKey: .modify) + case .remove(let data): + try container.encode(data, forKey: .remove) + case .rename(let data): + try container.encode(data, forKey: .rename) + } + } +} + + +public struct TagSearchFilters: Codable { + public let namespace: String? + public let tagType: String? + public let includeArchived: Bool + public let limit: UInt? + + public init(namespace: String?, tagType: String?, includeArchived: Bool, limit: UInt?) { + self.namespace = namespace + self.tagType = tagType + self.includeArchived = includeArchived + self.limit = limit + } +} +// MARK: - TagSearchFilters Custom Codable Implementation +extension TagSearchFilters { + private enum CodingKeys: String, CodingKey { + case namespace = "namespace" + case tagType = "tag_type" + case includeArchived = "include_archived" + case limit = "limit" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + namespace = try container.decodeIfPresent(String.self, forKey: .namespace) + tagType = try container.decodeIfPresent(String.self, forKey: .tagType) + includeArchived = try container.decode(Bool.self, forKey: .includeArchived) + limit = try container.decodeIfPresent(UInt.self, forKey: .limit) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(namespace, forKey: .namespace) + try container.encode(tagType, forKey: .tagType) + try container.encode(includeArchived, forKey: .includeArchived) + try container.encode(limit, forKey: .limit) + } +} + + +public struct VolumeSpeedTestInput: Codable { + public let fingerprint: VolumeFingerprint + + public init(fingerprint: VolumeFingerprint) { + self.fingerprint = fingerprint + } +} + +public struct DeviceRevokeInput: Codable { + public let deviceId: String + + private enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + } + + public init(deviceId: String) { + self.deviceId = deviceId + } +} + +/// APFS volume information within a container +public struct ApfsVolumeInfo: Codable { + public let diskId: String + public let uuid: String + public let role: ApfsVolumeRole + public let name: String + public let mountPoint: String? + public let capacityConsumed: UInt64 + public let sealed: Bool + public let filevault: Bool + + public init(diskId: String, uuid: String, role: ApfsVolumeRole, name: String, mountPoint: String?, capacityConsumed: UInt64, sealed: Bool, filevault: Bool) { + self.diskId = diskId + self.uuid = uuid + self.role = role + self.name = name + self.mountPoint = mountPoint + self.capacityConsumed = capacityConsumed + self.sealed = sealed + self.filevault = filevault + } +} +// MARK: - ApfsVolumeInfo Custom Codable Implementation +extension ApfsVolumeInfo { + private enum CodingKeys: String, CodingKey { + case diskId = "disk_id" + case uuid = "uuid" + case role = "role" + case name = "name" + case mountPoint = "mount_point" + case capacityConsumed = "capacity_consumed" + case sealed = "sealed" + case filevault = "filevault" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + diskId = try container.decode(String.self, forKey: .diskId) + uuid = try container.decode(String.self, forKey: .uuid) + role = try container.decode(ApfsVolumeRole.self, forKey: .role) + name = try container.decode(String.self, forKey: .name) + mountPoint = try container.decodeIfPresent(String.self, forKey: .mountPoint) + capacityConsumed = try container.decode(UInt64.self, forKey: .capacityConsumed) + sealed = try container.decode(Bool.self, forKey: .sealed) + filevault = try container.decode(Bool.self, forKey: .filevault) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(diskId, forKey: .diskId) + try container.encode(uuid, forKey: .uuid) + try container.encode(role, forKey: .role) + try container.encode(name, forKey: .name) + try container.encode(mountPoint, forKey: .mountPoint) + try container.encode(capacityConsumed, forKey: .capacityConsumed) + try container.encode(sealed, forKey: .sealed) + try container.encode(filevault, forKey: .filevault) + } +} + + +public struct PairStatusQueryInput: Codable { +} + public struct ApplyTagsOutput: Codable { public let entriesAffected: UInt public let tagsApplied: UInt @@ -2538,637 +4244,74 @@ public struct ApplyTagsOutput: Codable { } } -public struct JobResumeOutput: Codable { - public let jobId: String - public let success: Bool +/// EXIF metadata for images +public struct ExifData: Codable { + public let make: String? + public let model: String? + public let dateTaken: String? + public let gps: GpsCoordinates? + public let iso: UInt32? + public let aperture: Float? + public let shutterSpeed: Float? + public let focalLength: Float? + public init(make: String?, model: String?, dateTaken: String?, gps: GpsCoordinates?, iso: UInt32?, aperture: Float?, shutterSpeed: Float?, focalLength: Float?) { + self.make = make + self.model = model + self.dateTaken = dateTaken + self.gps = gps + self.iso = iso + self.aperture = aperture + self.shutterSpeed = shutterSpeed + self.focalLength = focalLength + } +} +// MARK: - ExifData Custom Codable Implementation +extension ExifData { private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - case success = "success" - } - - public init(jobId: String, success: Bool) { - self.jobId = jobId - self.success = success - } -} - -/// Main output structure for file search operations -public struct FileSearchOutput: Codable { - public let results: [FileSearchResult] - public let totalFound: UInt64 - public let searchId: String - public let facets: SearchFacets - public let suggestions: [String] - public let pagination: PaginationInfo - public let executionTimeMs: UInt64 - - private enum CodingKeys: String, CodingKey { - case results = "results" - case totalFound = "total_found" - case searchId = "search_id" - case facets = "facets" - case suggestions = "suggestions" - case pagination = "pagination" - case executionTimeMs = "execution_time_ms" - } - - public init(results: [FileSearchResult], totalFound: UInt64, searchId: String, facets: SearchFacets, suggestions: [String], pagination: PaginationInfo, executionTimeMs: UInt64) { - self.results = results - self.totalFound = totalFound - self.searchId = searchId - self.facets = facets - self.suggestions = suggestions - self.pagination = pagination - self.executionTimeMs = executionTimeMs - } -} - -/// Input for finding files unique to a location -public struct UniqueToLocationInput: Codable { - public let locationId: String - public let limit: UInt32? - - private enum CodingKeys: String, CodingKey { - case locationId = "location_id" - case limit = "limit" - } - - public init(locationId: String, limit: UInt32?) { - self.locationId = locationId - self.limit = limit - } -} - -/// Sorting options for search results -public struct SortOptions: Codable { - public let field: SortField - public let direction: SortDirection - - public init(field: SortField, direction: SortDirection) { - self.field = field - self.direction = direction - } -} - -/// Media-specific metadata -public struct MediaData: Codable { - public let width: UInt32? - public let height: UInt32? - public let duration: Double? - public let bitrate: UInt32? - public let fps: Float? - public let exif: ExifData? - public let extra: JsonValue - - public init(width: UInt32?, height: UInt32?, duration: Double?, bitrate: UInt32?, fps: Float?, exif: ExifData?, extra: JsonValue) { - self.width = width - self.height = height - self.duration = duration - self.bitrate = bitrate - self.fps = fps - self.exif = exif - self.extra = extra - } -} - -/// Internal enum for file conflict resolution strategies -public enum FileConflictResolution: Codable { - case overwrite - case autoModifyName - case abort -} - - -/// Indexing scope determines how much of the directory tree to process -public enum IndexScope: Codable { - case current - case recursive -} - - -/// Sort direction -public enum SortDirection: Codable { - case asc - case desc -} - - -/// Operators for combining tag attributes -public enum CompositionOperator: Codable { - case and - case or - case with - case without -} - - -public struct JobPauseOutput: Codable { - public let jobId: String - public let success: Bool - - private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - case success = "success" - } - - public init(jobId: String, success: Bool) { - self.jobId = jobId - self.success = success - } -} - -public struct SpacedropSendOutput: Codable { - public let jobId: String? - public let sessionId: String? - - private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - case sessionId = "session_id" - } - - public init(jobId: String?, sessionId: String?) { - self.jobId = jobId - self.sessionId = sessionId - } -} - -public struct CoreStatus: Codable { - public let version: String - public let builtAt: String - public let libraryCount: UInt - public let deviceInfo: DeviceInfo - public let libraries: [LibraryInfo] - public let services: ServiceStatus - public let network: NetworkStatus - public let system: SystemInfo - - private enum CodingKeys: String, CodingKey { - case version = "version" - case builtAt = "built_at" - case libraryCount = "library_count" - case deviceInfo = "device_info" - case libraries = "libraries" - case services = "services" - case network = "network" - case system = "system" - } - - public init(version: String, builtAt: String, libraryCount: UInt, deviceInfo: DeviceInfo, libraries: [LibraryInfo], services: ServiceStatus, network: NetworkStatus, system: SystemInfo) { - self.version = version - self.builtAt = builtAt - self.libraryCount = libraryCount - self.deviceInfo = deviceInfo - self.libraries = libraries - self.services = services - self.network = network - self.system = system - } -} - -/// Input for deleting a library -public struct LibraryDeleteInput: Codable { - public let libraryId: String - public let deleteData: Bool - - private enum CodingKeys: String, CodingKey { - case libraryId = "library_id" - case deleteData = "delete_data" - } - - public init(libraryId: String, deleteData: Bool) { - self.libraryId = libraryId - self.deleteData = deleteData - } -} - -public struct JobResumeInput: Codable { - public let jobId: String - - private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - } - - public init(jobId: String) { - self.jobId = jobId - } -} - -public struct ServiceState: Codable { - public let running: Bool - public let details: String? - - public init(running: Bool, details: String?) { - self.running = running - self.details = details - } -} - -/// Copy method preference for file operations -public enum CopyMethod: Codable { - case auto - case atomic - case streaming -} - - -/// APFS volume information within a container -public struct ApfsVolumeInfo: Codable { - public let diskId: String - public let uuid: String - public let role: ApfsVolumeRole - public let name: String - public let mountPoint: String? - public let capacityConsumed: UInt64 - public let sealed: Bool - public let filevault: Bool - - private enum CodingKeys: String, CodingKey { - case diskId = "disk_id" - case uuid = "uuid" - case role = "role" - case name = "name" - case mountPoint = "mount_point" - case capacityConsumed = "capacity_consumed" - case sealed = "sealed" - case filevault = "filevault" - } - - public init(diskId: String, uuid: String, role: ApfsVolumeRole, name: String, mountPoint: String?, capacityConsumed: UInt64, sealed: Bool, filevault: Bool) { - self.diskId = diskId - self.uuid = uuid - self.role = role - self.name = name - self.mountPoint = mountPoint - self.capacityConsumed = capacityConsumed - self.sealed = sealed - self.filevault = filevault - } -} - -public struct PairGenerateInput: Codable { - public let autoAccept: Bool - - private enum CodingKeys: String, CodingKey { - case autoAccept = "auto_accept" - } - - public init(autoAccept: Bool) { - self.autoAccept = autoAccept - } -} - -public struct LocationRemoveInput: Codable { - public let locationId: String - - private enum CodingKeys: String, CodingKey { - case locationId = "location_id" - } - - public init(locationId: String) { - self.locationId = locationId - } -} - -/// APFS volume roles in the container -public enum ApfsVolumeRole: Codable { - case system - case data - case preboot - case recovery - case vM - case other(String) -} - - -/// A path within the Spacedrive Virtual Distributed File System -/// -/// This is the core abstraction that enables cross-device operations. -/// An SdPath can represent: -/// - A physical file at a specific path on a specific device -/// - A content-addressed file that can be sourced from any device -/// -/// This enum-based approach enables resilient file operations by allowing -/// content-based paths to be resolved to optimal physical locations at runtime. -public enum SdPath { - case physical(SdPathPhysical) - case content(SdPathContent) -} -public struct SdPathPhysical: Codable { - public let deviceId: String - public let path: String - - private enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - case path = "path" - } -} - -public struct SdPathContent: Codable { - public let contentId: String - - private enum CodingKeys: String, CodingKey { - case contentId = "content_id" - } -} - - -// MARK: - SdPath Codable Implementation -extension SdPath: Codable { - private enum CodingKeys: String, CodingKey { - case physical = "Physical" - case content = "Content" + case make = "make" + case model = "model" + case dateTaken = "date_taken" + case gps = "gps" + case iso = "iso" + case aperture = "aperture" + case shutterSpeed = "shutter_speed" + case focalLength = "focal_length" } public init(from decoder: Decoder) throws { - // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) - if let container = try? decoder.container(keyedBy: CodingKeys.self) { - if container.allKeys.count == 1 { - let key = container.allKeys.first! - switch key { - case .physical: - let data = try container.decode(SdPathPhysical.self, forKey: .physical) - self = .physical(data) - return - case .content: - let data = try container.decode(SdPathContent.self, forKey: .content) - self = .content(data) - return - } - return - } - } - - // Fallback: try decoding as plain string for unit variants (serde default) - if let stringContainer = try? decoder.singleValueContainer() { - if let variantString = try? stringContainer.decode(String.self) { - switch variantString { - default: - break - } - } - } - - throw DecodingError.dataCorrupted( - DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") - ) + let container = try decoder.container(keyedBy: CodingKeys.self) + make = try container.decodeIfPresent(String.self, forKey: .make) + model = try container.decodeIfPresent(String.self, forKey: .model) + dateTaken = try container.decodeIfPresent(String.self, forKey: .dateTaken) + gps = try container.decodeIfPresent(GpsCoordinates.self, forKey: .gps) + iso = try container.decodeIfPresent(UInt32.self, forKey: .iso) + aperture = try container.decodeIfPresent(Float.self, forKey: .aperture) + shutterSpeed = try container.decodeIfPresent(Float.self, forKey: .shutterSpeed) + focalLength = try container.decodeIfPresent(Float.self, forKey: .focalLength) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - - switch self { - case .physical(let data): - try container.encode(data, forKey: .physical) - case .content(let data): - try container.encode(data, forKey: .content) - } + try container.encode(make, forKey: .make) + try container.encode(model, forKey: .model) + try container.encode(dateTaken, forKey: .dateTaken) + try container.encode(gps, forKey: .gps) + try container.encode(iso, forKey: .iso) + try container.encode(aperture, forKey: .aperture) + try container.encode(shutterSpeed, forKey: .shutterSpeed) + try container.encode(focalLength, forKey: .focalLength) } } -public struct LocationInfo: Codable { - public let id: String - public let path: String - public let name: String? - - public init(id: String, path: String, name: String?) { - self.id = id - self.path = path - self.name = name - } -} - -/// Pagination options -public struct PaginationOptions: Codable { - public let limit: UInt32 - public let offset: UInt32 - - public init(limit: UInt32, offset: UInt32) { - self.limit = limit - self.offset = offset - } -} - -/// A tag with advanced capabilities for contextual organization -public struct Tag: Codable { - public let id: String - public let canonicalName: String - public let displayName: String? - public let formalName: String? - public let abbreviation: String? - public let aliases: [String] - public let namespace: String? - public let tagType: TagType - public let color: String? - public let icon: String? - public let description: String? - public let isOrganizationalAnchor: Bool - public let privacyLevel: PrivacyLevel - public let searchWeight: Int32 - public let attributes: [String: JsonValue] - public let compositionRules: [CompositionRule] - public let createdAt: String - public let updatedAt: String - public let createdByDevice: String - - private enum CodingKeys: String, CodingKey { - case id = "id" - case canonicalName = "canonical_name" - case displayName = "display_name" - case formalName = "formal_name" - case abbreviation = "abbreviation" - case aliases = "aliases" - case namespace = "namespace" - case tagType = "tag_type" - case color = "color" - case icon = "icon" - case description = "description" - case isOrganizationalAnchor = "is_organizational_anchor" - case privacyLevel = "privacy_level" - case searchWeight = "search_weight" - case attributes = "attributes" - case compositionRules = "composition_rules" - case createdAt = "created_at" - case updatedAt = "updated_at" - case createdByDevice = "created_by_device" - } - - public init(id: String, canonicalName: String, displayName: String?, formalName: String?, abbreviation: String?, aliases: [String], namespace: String?, tagType: TagType, color: String?, icon: String?, description: String?, isOrganizationalAnchor: Bool, privacyLevel: PrivacyLevel, searchWeight: Int32, attributes: [String: JsonValue], compositionRules: [CompositionRule], createdAt: String, updatedAt: String, createdByDevice: String) { - self.id = id - self.canonicalName = canonicalName - self.displayName = displayName - self.formalName = formalName - self.abbreviation = abbreviation - self.aliases = aliases - self.namespace = namespace - self.tagType = tagType - self.color = color - self.icon = icon - self.description = description - self.isOrganizationalAnchor = isOrganizationalAnchor - self.privacyLevel = privacyLevel - self.searchWeight = searchWeight - self.attributes = attributes - self.compositionRules = compositionRules - self.createdAt = createdAt - self.updatedAt = updatedAt - self.createdByDevice = createdByDevice - } -} - -/// Library-specific settings -public struct LibrarySettings: Codable { - public let generateThumbnails: Bool - public let thumbnailQuality: UInt8 - public let enableAiTagging: Bool - public let syncEnabled: Bool - public let encryptionEnabled: Bool - public let thumbnailSizes: [UInt32] - public let ignoredExtensions: [String] - public let maxFileSize: UInt64? - public let autoTrackSystemVolumes: Bool - public let autoTrackExternalVolumes: Bool - public let indexer: IndexerSettings? - - private enum CodingKeys: String, CodingKey { - case generateThumbnails = "generate_thumbnails" - case thumbnailQuality = "thumbnail_quality" - case enableAiTagging = "enable_ai_tagging" - case syncEnabled = "sync_enabled" - case encryptionEnabled = "encryption_enabled" - case thumbnailSizes = "thumbnail_sizes" - case ignoredExtensions = "ignored_extensions" - case maxFileSize = "max_file_size" - case autoTrackSystemVolumes = "auto_track_system_volumes" - case autoTrackExternalVolumes = "auto_track_external_volumes" - case indexer = "indexer" - } - - public init(generateThumbnails: Bool, thumbnailQuality: UInt8, enableAiTagging: Bool, syncEnabled: Bool, encryptionEnabled: Bool, thumbnailSizes: [UInt32], ignoredExtensions: [String], maxFileSize: UInt64?, autoTrackSystemVolumes: Bool, autoTrackExternalVolumes: Bool, indexer: IndexerSettings?) { - self.generateThumbnails = generateThumbnails - self.thumbnailQuality = thumbnailQuality - self.enableAiTagging = enableAiTagging - self.syncEnabled = syncEnabled - self.encryptionEnabled = encryptionEnabled - self.thumbnailSizes = thumbnailSizes - self.ignoredExtensions = ignoredExtensions - self.maxFileSize = maxFileSize - self.autoTrackSystemVolumes = autoTrackSystemVolumes - self.autoTrackExternalVolumes = autoTrackExternalVolumes - self.indexer = indexer - } -} - -public struct SpacedropSendInput: Codable { - public let deviceId: String - public let paths: [SdPath] - public let sender: String? - - private enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - case paths = "paths" - case sender = "sender" - } - - public init(deviceId: String, paths: [SdPath], sender: String?) { - self.deviceId = deviceId - self.paths = paths - self.sender = sender - } -} - -public struct SystemInfo: Codable { - public let uptime: UInt64? - public let dataDirectory: String - public let instanceName: String? - public let currentLibrary: String? - - private enum CodingKeys: String, CodingKey { - case uptime = "uptime" - case dataDirectory = "data_directory" - case instanceName = "instance_name" - case currentLibrary = "current_library" - } - - public init(uptime: UInt64?, dataDirectory: String, instanceName: String?, currentLibrary: String?) { - self.uptime = uptime - self.dataDirectory = dataDirectory - self.instanceName = instanceName - self.currentLibrary = currentLibrary - } -} - -/// Rules for composing attributes from multiple tags -public struct CompositionRule: Codable { - public let `operator`: CompositionOperator - public let operands: [String] - public let resultAttribute: String - - private enum CodingKeys: String, CodingKey { - case `operator` = "operator" - case operands = "operands" - case resultAttribute = "result_attribute" - } - - public init(`operator`: CompositionOperator, operands: [String], resultAttribute: String) { - self.`operator` = `operator` - self.operands = operands - self.resultAttribute = resultAttribute - } -} - -/// Output from volume speed test operation -public struct VolumeSpeedTestOutput: Codable { - public let fingerprint: VolumeFingerprint - public let readSpeedMbps: UInt32? - public let writeSpeedMbps: UInt32? - - private enum CodingKeys: String, CodingKey { - case fingerprint = "fingerprint" - case readSpeedMbps = "read_speed_mbps" - case writeSpeedMbps = "write_speed_mbps" - } - - public init(fingerprint: VolumeFingerprint, readSpeedMbps: UInt32?, writeSpeedMbps: UInt32?) { - self.fingerprint = fingerprint - self.readSpeedMbps = readSpeedMbps - self.writeSpeedMbps = writeSpeedMbps - } -} - -public struct JobInfoQueryInput: Codable { - public let jobId: String - - private enum CodingKeys: String, CodingKey { - case jobId = "job_id" - } - - public init(jobId: String) { - self.jobId = jobId - } -} - -/// Query to get a file by its ID with all related data -public struct FileByIdQuery: Codable { - public let fileId: String - - private enum CodingKeys: String, CodingKey { - case fileId = "file_id" - } - - public init(fileId: String) { - self.fileId = fileId - } -} - /// Type of filesystem entry public enum EntryKind { - case file(EntryKindFile) + case file(EntryKindFileData) case directory - case symlink(EntryKindSymlink) + case symlink(EntryKindSymlinkData) } -public struct EntryKindFile: Codable { +public struct EntryKindFileData: Codable { public let `extension`: String? private enum CodingKeys: String, CodingKey { @@ -3176,7 +4319,7 @@ public struct EntryKindFile: Codable { } } -public struct EntryKindSymlink: Codable { +public struct EntryKindSymlinkData: Codable { public let target: String } @@ -3196,14 +4339,14 @@ extension EntryKind: Codable { let key = container.allKeys.first! switch key { case .file: - let data = try container.decode(EntryKindFile.self, forKey: .file) + let data = try container.decode(EntryKindFileData.self, forKey: .file) self = .file(data) return case .directory: self = .directory return case .symlink: - let data = try container.decode(EntryKindSymlink.self, forKey: .symlink) + let data = try container.decode(EntryKindSymlinkData.self, forKey: .symlink) self = .symlink(data) return } @@ -3244,19 +4387,115 @@ extension EntryKind: Codable { } -/// Represents the filesystem type of the volume -public enum FileSystem: Codable { - case nTFS - case fAT32 - case eXT4 - case aPFS - case exFAT - case btrfs - case zFS - case reFS - case other(String) +/// Output from library create action dispatch +public struct LibraryCreateOutput: Codable { + public let libraryId: String + public let name: String + public let path: String + + private enum CodingKeys: String, CodingKey { + case libraryId = "library_id" + case name = "name" + case path = "path" + } + + public init(libraryId: String, name: String, path: String) { + self.libraryId = libraryId + self.name = name + self.path = path + } } +/// Indexing scope determines how much of the directory tree to process +public enum IndexScope: Codable { + case current + case recursive +} + + +public struct LibraryRenameOutput: Codable { + public let libraryId: String + public let oldName: String + public let newName: String + + private enum CodingKeys: String, CodingKey { + case libraryId = "library_id" + case oldName = "old_name" + case newName = "new_name" + } + + public init(libraryId: String, oldName: String, newName: String) { + self.libraryId = libraryId + self.oldName = oldName + self.newName = newName + } +} + +/// Fields that can be used for sorting +public enum SortField: Codable { + case relevance + case name + case size + case modifiedAt + case createdAt +} + + +/// Main input structure for file search operations +public struct FileSearchInput: Codable { + public let query: String + public let scope: SearchScope + public let mode: SearchMode + public let filters: SearchFilters + public let sort: SortOptions + public let pagination: PaginationOptions + + public init(query: String, scope: SearchScope, mode: SearchMode, filters: SearchFilters, sort: SortOptions, pagination: PaginationOptions) { + self.query = query + self.scope = scope + self.mode = mode + self.filters = filters + self.sort = sort + self.pagination = pagination + } +} + +/// Source of tag application +public enum TagSource: Codable { + case user + case aI + case `import` + case sync +} + + +public struct JobResumeOutput: Codable { + public let jobId: String + public let success: Bool + + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + case success = "success" + } + + public init(jobId: String, success: Bool) { + self.jobId = jobId + self.success = success + } +} + +/// Query to get a file by its ID with all related data +public struct FileByIdQuery: Codable { + public let fileId: String + + private enum CodingKeys: String, CodingKey { + case fileId = "file_id" + } + + public init(fileId: String) { + self.fileId = fileId + } +} /// A batch of SdPaths, useful for operations on multiple files public struct SdPathBatch: Codable { @@ -3267,276 +4506,131 @@ public struct SdPathBatch: Codable { } } -/// Detailed information about a library -public struct LibraryInfoOutput: Codable { - public let id: String - public let name: String - public let description: String? - public let path: String - public let createdAt: String - public let updatedAt: String - public let settings: LibrarySettings - public let statistics: LibraryStatistics +public struct JobListOutput: Codable { + public let jobs: [JobListItem] - private enum CodingKeys: String, CodingKey { - case id = "id" - case name = "name" - case description = "description" - case path = "path" - case createdAt = "created_at" - case updatedAt = "updated_at" - case settings = "settings" - case statistics = "statistics" - } - - public init(id: String, name: String, description: String?, path: String, createdAt: String, updatedAt: String, settings: LibrarySettings, statistics: LibraryStatistics) { - self.id = id - self.name = name - self.description = description - self.path = path - self.createdAt = createdAt - self.updatedAt = updatedAt - self.settings = settings - self.statistics = statistics + public init(jobs: [JobListItem]) { + self.jobs = jobs } } -public struct SearchTagsInput: Codable { - public let query: String - public let namespace: String? - public let tagType: TagType? - public let includeArchived: Bool? - public let limit: UInt? - public let resolveAmbiguous: Bool? - public let contextTagIds: [String]? +/// Main output structure for file search operations +public struct FileSearchOutput: Codable { + public let results: [FileSearchResult] + public let totalFound: UInt64 + public let searchId: String + public let facets: SearchFacets + public let suggestions: [String] + public let pagination: PaginationInfo + public let executionTimeMs: UInt64 private enum CodingKeys: String, CodingKey { - case query = "query" - case namespace = "namespace" - case tagType = "tag_type" - case includeArchived = "include_archived" - case limit = "limit" - case resolveAmbiguous = "resolve_ambiguous" - case contextTagIds = "context_tag_ids" + case results = "results" + case totalFound = "total_found" + case searchId = "search_id" + case facets = "facets" + case suggestions = "suggestions" + case pagination = "pagination" + case executionTimeMs = "execution_time_ms" } - public init(query: String, namespace: String?, tagType: TagType?, includeArchived: Bool?, limit: UInt?, resolveAmbiguous: Bool?, contextTagIds: [String]?) { - self.query = query - self.namespace = namespace - self.tagType = tagType - self.includeArchived = includeArchived + public init(results: [FileSearchResult], totalFound: UInt64, searchId: String, facets: SearchFacets, suggestions: [String], pagination: PaginationInfo, executionTimeMs: UInt64) { + self.results = results + self.totalFound = totalFound + self.searchId = searchId + self.facets = facets + self.suggestions = suggestions + self.pagination = pagination + self.executionTimeMs = executionTimeMs + } +} + +/// Pagination options +public struct PaginationOptions: Codable { + public let limit: UInt32 + public let offset: UInt32 + + public init(limit: UInt32, offset: UInt32) { self.limit = limit - self.resolveAmbiguous = resolveAmbiguous - self.contextTagIds = contextTagIds + self.offset = offset } } -/// Generic progress information that all job types can convert into -public struct GenericProgress: Codable { - public let percentage: Float - public let phase: String - public let currentPath: SdPath? - public let message: String - public let completion: ProgressCompletion - public let performance: PerformanceMetrics - - private enum CodingKeys: String, CodingKey { - case percentage = "percentage" - case phase = "phase" - case currentPath = "current_path" - case message = "message" - case completion = "completion" - case performance = "performance" - } - - public init(percentage: Float, phase: String, currentPath: SdPath?, message: String, completion: ProgressCompletion, performance: PerformanceMetrics) { - self.percentage = percentage - self.phase = phase - self.currentPath = currentPath - self.message = message - self.completion = completion - self.performance = performance - } -} - -/// Query to get a file by its local path with all related data -public struct FileByPathQuery: Codable { - public let path: String - - public init(path: String) { - self.path = path - } -} - -/// Output from volume track operation -public struct VolumeTrackOutput: Codable { - public let fingerprint: VolumeFingerprint - public let volumeName: String - - private enum CodingKeys: String, CodingKey { - case fingerprint = "fingerprint" - case volumeName = "volume_name" - } - - public init(fingerprint: VolumeFingerprint, volumeName: String) { - self.fingerprint = fingerprint - self.volumeName = volumeName - } -} - -public struct NetworkStartOutput: Codable { - public let started: Bool - - public init(started: Bool) { - self.started = started - } -} - -/// Time-based fields that can be filtered -public enum DateField: Codable { - case createdAt - case modifiedAt - case accessedAt +/// Sort direction +public enum SortDirection: Codable { + case asc + case desc } -/// Information about a library for listing purposes -public struct LibraryInfo: Codable { - public let id: String +/// Sorting options for search results +public struct SortOptions: Codable { + public let field: SortField + public let direction: SortDirection + + public init(field: SortField, direction: SortDirection) { + self.field = field + self.direction = direction + } +} + +/// Output from library delete action dispatch +public struct LibraryDeleteOutput: Codable { + public let libraryId: String public let name: String - public let path: String - public let stats: LibraryStatistics? - public init(id: String, name: String, path: String, stats: LibraryStatistics?) { - self.id = id + private enum CodingKeys: String, CodingKey { + case libraryId = "library_id" + case name = "name" + } + + public init(libraryId: String, name: String) { + self.libraryId = libraryId self.name = name - self.path = path - self.stats = stats } } -/// Unique identifier for a job -public struct JobId: Codable { - let value: String -} - -/// Individual search result -public struct FileSearchResult: Codable { - public let entry: Entry - public let score: Float - public let scoreBreakdown: ScoreBreakdown - public let highlights: [TextHighlight] - public let matchedContent: String? - - private enum CodingKeys: String, CodingKey { - case entry = "entry" - case score = "score" - case scoreBreakdown = "score_breakdown" - case highlights = "highlights" - case matchedContent = "matched_content" - } - - public init(entry: Entry, score: Float, scoreBreakdown: ScoreBreakdown, highlights: [TextHighlight], matchedContent: String?) { - self.entry = entry - self.score = score - self.scoreBreakdown = scoreBreakdown - self.highlights = highlights - self.matchedContent = matchedContent - } -} - -/// GPS coordinates -public struct GpsCoordinates: Codable { - public let latitude: Double - public let longitude: Double - public let altitude: Float? - - public init(latitude: Double, longitude: Double, altitude: Float?) { - self.latitude = latitude - self.longitude = longitude - self.altitude = altitude - } -} - -/// Determines whether indexing results are persisted to database or kept in memory -public enum IndexPersistence: Codable { - case persistent - case ephemeral -} - - -public struct JobListInput: Codable { - public let status: JobStatus? - - public init(status: JobStatus?) { - self.status = status - } -} - -public struct NetworkStartInput: Codable { -} - -public struct NetworkStopOutput: Codable { - public let stopped: Bool - - public init(stopped: Bool) { - self.stopped = stopped - } -} - -public struct PairGenerateOutput: Codable { - public let code: String - public let sessionId: String - public let expiresAt: String - - private enum CodingKeys: String, CodingKey { - case code = "code" - case sessionId = "session_id" - case expiresAt = "expires_at" - } - - public init(code: String, sessionId: String, expiresAt: String) { - self.code = code - self.sessionId = sessionId - self.expiresAt = expiresAt - } -} - -public struct LocationsListQueryInput: Codable { -} - -/// How SdPath is stored in the database -public struct SdPathSerialized: Codable { - public let deviceId: String - public let path: String - - private enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - case path = "path" - } - - public init(deviceId: String, path: String) { - self.deviceId = deviceId - self.path = path - } -} - -/// Output from location remove action dispatch -public struct LocationRemoveOutput: Codable { +/// Output from location add action dispatch +public struct LocationAddOutput: Codable { public let locationId: String - public let path: String? + public let path: String + public let name: String? + public let jobId: String? + public init(locationId: String, path: String, name: String?, jobId: String?) { + self.locationId = locationId + self.path = path + self.name = name + self.jobId = jobId + } +} +// MARK: - LocationAddOutput Custom Codable Implementation +extension LocationAddOutput { private enum CodingKeys: String, CodingKey { case locationId = "location_id" case path = "path" + case name = "name" + case jobId = "job_id" } - public init(locationId: String, path: String?) { - self.locationId = locationId - self.path = path + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + locationId = try container.decode(String.self, forKey: .locationId) + path = try container.decode(String.self, forKey: .path) + name = try container.decodeIfPresent(String.self, forKey: .name) + jobId = try container.decodeIfPresent(String.self, forKey: .jobId) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(locationId, forKey: .locationId) + try container.encode(path, forKey: .path) + try container.encode(name, forKey: .name) + try container.encode(jobId, forKey: .jobId) } } + public enum SerializablePairingState { case idle case generatingCode @@ -3552,9 +4646,9 @@ public enum SerializablePairingState { case responsePending case responseSent case completed - case failed(SerializablePairingStateFailed) + case failed(SerializablePairingStateFailedData) } -public struct SerializablePairingStateFailed: Codable { +public struct SerializablePairingStateFailedData: Codable { public let reason: String } @@ -3628,7 +4722,7 @@ extension SerializablePairingState: Codable { self = .completed return case .failed: - let data = try container.decode(SerializablePairingStateFailed.self, forKey: .failed) + let data = try container.decode(SerializablePairingStateFailedData.self, forKey: .failed) self = .failed(data) return } @@ -3732,72 +4826,223 @@ extension SerializablePairingState: Codable { } -/// Represents a file within the Spacedrive VDFS. -/// -/// This is a computed domain model that aggregates data from Entry, ContentIdentity, -/// Tags, and Sidecars. It provides a rich, developer-friendly interface without -/// duplicating data in the database. -public struct File: Codable { - public let id: String - public let sdPath: SdPath - public let name: String - public let size: UInt64 - public let contentIdentity: ContentIdentity? - public let alternatePaths: [SdPath] - public let tags: [Tag] - public let sidecars: [Sidecar] - public let createdAt: String - public let modifiedAt: String - public let accessedAt: String? - public let contentKind: ContentKind - public let `extension`: String? - public let isLocal: Bool +/// Input for deleting a library +public struct LibraryDeleteInput: Codable { + public let libraryId: String + public let deleteData: Bool private enum CodingKeys: String, CodingKey { - case id = "id" - case sdPath = "sd_path" + case libraryId = "library_id" + case deleteData = "delete_data" + } + + public init(libraryId: String, deleteData: Bool) { + self.libraryId = libraryId + self.deleteData = deleteData + } +} + +/// Output containing directory contents +public struct DirectoryListingOutput: Codable { + public let files: [File] + public let totalCount: UInt32 + public let hasMore: Bool + + private enum CodingKeys: String, CodingKey { + case files = "files" + case totalCount = "total_count" + case hasMore = "has_more" + } + + public init(files: [File], totalCount: UInt32, hasMore: Bool) { + self.files = files + self.totalCount = totalCount + self.hasMore = hasMore + } +} + +public struct SpacedropSendInput: Codable { + public let deviceId: String + public let paths: [SdPath] + public let sender: String? + + public init(deviceId: String, paths: [SdPath], sender: String?) { + self.deviceId = deviceId + self.paths = paths + self.sender = sender + } +} +// MARK: - SpacedropSendInput Custom Codable Implementation +extension SpacedropSendInput { + private enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + case paths = "paths" + case sender = "sender" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + deviceId = try container.decode(String.self, forKey: .deviceId) + paths = try container.decode([SdPath].self, forKey: .paths) + sender = try container.decodeIfPresent(String.self, forKey: .sender) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(deviceId, forKey: .deviceId) + try container.encode(paths, forKey: .paths) + try container.encode(sender, forKey: .sender) + } +} + + +public struct VolumeTrackInput: Codable { + public let fingerprint: VolumeFingerprint + public let name: String? + + public init(fingerprint: VolumeFingerprint, name: String?) { + self.fingerprint = fingerprint + self.name = name + } +} +// MARK: - VolumeTrackInput Custom Codable Implementation +extension VolumeTrackInput { + private enum CodingKeys: String, CodingKey { + case fingerprint = "fingerprint" case name = "name" - case size = "size" - case contentIdentity = "content_identity" - case alternatePaths = "alternate_paths" - case tags = "tags" - case sidecars = "sidecars" - case createdAt = "created_at" - case modifiedAt = "modified_at" - case accessedAt = "accessed_at" - case contentKind = "content_kind" - case `extension` = "extension" - case isLocal = "is_local" } - public init(id: String, sdPath: SdPath, name: String, size: UInt64, contentIdentity: ContentIdentity?, alternatePaths: [SdPath], tags: [Tag], sidecars: [Sidecar], createdAt: String, modifiedAt: String, accessedAt: String?, contentKind: ContentKind, `extension`: String?, isLocal: Bool) { - self.id = id - self.sdPath = sdPath - self.name = name - self.size = size - self.contentIdentity = contentIdentity - self.alternatePaths = alternatePaths - self.tags = tags - self.sidecars = sidecars - self.createdAt = createdAt - self.modifiedAt = modifiedAt - self.accessedAt = accessedAt - self.contentKind = contentKind - self.`extension` = `extension` - self.isLocal = isLocal + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + fingerprint = try container.decode(VolumeFingerprint.self, forKey: .fingerprint) + name = try container.decodeIfPresent(String.self, forKey: .name) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fingerprint, forKey: .fingerprint) + try container.encode(name, forKey: .name) } } -/// Input for creating a new library -public struct LibraryCreateInput: Codable { - public let name: String - public let path: String? - public init(name: String, path: String?) { - self.name = name +/// Input for directory listing +public struct DirectoryListingInput: Codable { + public let path: SdPath + public let limit: UInt32? + public let includeHidden: Bool? + public let sortBy: DirectorySortBy + + public init(path: SdPath, limit: UInt32?, includeHidden: Bool?, sortBy: DirectorySortBy) { self.path = path + self.limit = limit + self.includeHidden = includeHidden + self.sortBy = sortBy } } +// MARK: - DirectoryListingInput Custom Codable Implementation +extension DirectoryListingInput { + private enum CodingKeys: String, CodingKey { + case path = "path" + case limit = "limit" + case includeHidden = "include_hidden" + case sortBy = "sort_by" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + path = try container.decode(SdPath.self, forKey: .path) + limit = try container.decodeIfPresent(UInt32.self, forKey: .limit) + includeHidden = try container.decodeIfPresent(Bool.self, forKey: .includeHidden) + sortBy = try container.decode(DirectorySortBy.self, forKey: .sortBy) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(path, forKey: .path) + try container.encode(limit, forKey: .limit) + try container.encode(includeHidden, forKey: .includeHidden) + try container.encode(sortBy, forKey: .sortBy) + } +} + + +public struct JobCancelInput: Codable { + public let jobId: String + + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + } + + public init(jobId: String) { + self.jobId = jobId + } +} + +/// Current status of a job +public enum JobStatus: String, Codable { + case queued = "queued" + case running = "running" + case paused = "paused" + case completed = "completed" + case failed = "failed" + case cancelled = "cancelled" +} + +public struct DeviceRevokeOutput: Codable { + public let revoked: Bool + + public init(revoked: Bool) { + self.revoked = revoked + } +} + +/// Text highlighting information +public struct TextHighlight: Codable { + public let field: String + public let text: String + public let start: UInt + public let end: UInt + + public init(field: String, text: String, start: UInt, end: UInt) { + self.field = field + self.text = text + self.start = start + self.end = end + } +} + +public struct PairJoinInput: Codable { + public let code: String + + public init(code: String) { + self.code = code + } +} + +public struct JobInfoQueryInput: Codable { + public let jobId: String + + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + } + + public init(jobId: String) { + self.jobId = jobId + } +} + +/// Classification of volume types for UX and auto-tracking decisions +public enum VolumeType: Codable { + case primary + case userData + case external + case secondary + case system + case network + case unknown +} + /// Input for exporting a library public struct LibraryExportInput: Codable { @@ -3821,149 +5066,556 @@ public struct LibraryExportInput: Codable { } } +public struct VolumeUntrackInput: Codable { + public let fingerprint: VolumeFingerprint + + public init(fingerprint: VolumeFingerprint) { + self.fingerprint = fingerprint + } +} + +/// Pagination information +public struct PaginationInfo: Codable { + public let currentPage: UInt32 + public let totalPages: UInt32 + public let hasNext: Bool + public let hasPrevious: Bool + public let limit: UInt32 + public let offset: UInt32 + + private enum CodingKeys: String, CodingKey { + case currentPage = "current_page" + case totalPages = "total_pages" + case hasNext = "has_next" + case hasPrevious = "has_previous" + case limit = "limit" + case offset = "offset" + } + + public init(currentPage: UInt32, totalPages: UInt32, hasNext: Bool, hasPrevious: Bool, limit: UInt32, offset: UInt32) { + self.currentPage = currentPage + self.totalPages = totalPages + self.hasNext = hasNext + self.hasPrevious = hasPrevious + self.limit = limit + self.offset = offset + } +} + +public struct NetworkStartOutput: Codable { + public let started: Bool + + public init(started: Bool) { + self.started = started + } +} + +public struct JobResumeInput: Codable { + public let jobId: String + + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + } + + public init(jobId: String) { + self.jobId = jobId + } +} + +/// Types of file operations +public enum FileOperation: Codable { + case copy + case move + case delete + case rename +} + + +/// Domain representation of a sidecar +public struct Sidecar: Codable { + public let id: Int32 + public let contentUuid: String + public let kind: String + public let variant: String + public let format: String + public let status: String + public let size: Int64 + public let createdAt: String + public let updatedAt: String + + private enum CodingKeys: String, CodingKey { + case id = "id" + case contentUuid = "content_uuid" + case kind = "kind" + case variant = "variant" + case format = "format" + case status = "status" + case size = "size" + case createdAt = "created_at" + case updatedAt = "updated_at" + } + + public init(id: Int32, contentUuid: String, kind: String, variant: String, format: String, status: String, size: Int64, createdAt: String, updatedAt: String) { + self.id = id + self.contentUuid = contentUuid + self.kind = kind + self.variant = variant + self.format = format + self.status = status + self.size = size + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} + +public struct JobCancelOutput: Codable { + public let jobId: String + public let success: Bool + + private enum CodingKeys: String, CodingKey { + case jobId = "job_id" + case success = "success" + } + + public init(jobId: String, success: Bool) { + self.jobId = jobId + self.success = success + } +} + +public struct PairGenerateInput: Codable { + public let autoAccept: Bool + + private enum CodingKeys: String, CodingKey { + case autoAccept = "auto_accept" + } + + public init(autoAccept: Bool) { + self.autoAccept = autoAccept + } +} + +/// Output from location remove action dispatch +public struct LocationRemoveOutput: Codable { + public let locationId: String + public let path: String? + + public init(locationId: String, path: String?) { + self.locationId = locationId + self.path = path + } +} +// MARK: - LocationRemoveOutput Custom Codable Implementation +extension LocationRemoveOutput { + private enum CodingKeys: String, CodingKey { + case locationId = "location_id" + case path = "path" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + locationId = try container.decode(String.self, forKey: .locationId) + path = try container.decodeIfPresent(String.self, forKey: .path) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(locationId, forKey: .locationId) + try container.encode(path, forKey: .path) + } +} + + +public struct NetworkStatusQueryInput: Codable { +} + +/// Unique identifier for a job +public struct JobId: Codable { + let value: String +} + /// Input for library info query public struct LibraryInfoQueryInput: Codable { } -public struct ListDevicesInput: Codable { - public let pairedOnly: Bool - public let connectedOnly: Bool +/// Container for all structured filters +public struct SearchFilters: Codable { + public let fileTypes: [String]? + public let tags: TagFilter? + public let dateRange: DateRangeFilter? + public let sizeRange: SizeRangeFilter? + public let locations: [String]? + public let contentTypes: [ContentKind]? + public let includeHidden: Bool? + public let includeArchived: Bool? + public init(fileTypes: [String]?, tags: TagFilter?, dateRange: DateRangeFilter?, sizeRange: SizeRangeFilter?, locations: [String]?, contentTypes: [ContentKind]?, includeHidden: Bool?, includeArchived: Bool?) { + self.fileTypes = fileTypes + self.tags = tags + self.dateRange = dateRange + self.sizeRange = sizeRange + self.locations = locations + self.contentTypes = contentTypes + self.includeHidden = includeHidden + self.includeArchived = includeArchived + } +} +// MARK: - SearchFilters Custom Codable Implementation +extension SearchFilters { private enum CodingKeys: String, CodingKey { - case pairedOnly = "paired_only" - case connectedOnly = "connected_only" + case fileTypes = "file_types" + case tags = "tags" + case dateRange = "date_range" + case sizeRange = "size_range" + case locations = "locations" + case contentTypes = "content_types" + case includeHidden = "include_hidden" + case includeArchived = "include_archived" } - public init(pairedOnly: Bool, connectedOnly: Bool) { - self.pairedOnly = pairedOnly - self.connectedOnly = connectedOnly + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + fileTypes = try container.decodeIfPresent([String].self, forKey: .fileTypes) + tags = try container.decodeIfPresent(TagFilter.self, forKey: .tags) + dateRange = try container.decodeIfPresent(DateRangeFilter.self, forKey: .dateRange) + sizeRange = try container.decodeIfPresent(SizeRangeFilter.self, forKey: .sizeRange) + locations = try container.decodeIfPresent([String].self, forKey: .locations) + contentTypes = try container.decodeIfPresent([ContentKind].self, forKey: .contentTypes) + includeHidden = try container.decodeIfPresent(Bool.self, forKey: .includeHidden) + includeArchived = try container.decodeIfPresent(Bool.self, forKey: .includeArchived) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fileTypes, forKey: .fileTypes) + try container.encode(tags, forKey: .tags) + try container.encode(dateRange, forKey: .dateRange) + try container.encode(sizeRange, forKey: .sizeRange) + try container.encode(locations, forKey: .locations) + try container.encode(contentTypes, forKey: .contentTypes) + try container.encode(includeHidden, forKey: .includeHidden) + try container.encode(includeArchived, forKey: .includeArchived) } } -/// Current status of a job -public enum JobStatus: String, Codable { - case queued = "queued" - case running = "running" - case paused = "paused" - case completed = "completed" - case failed = "failed" - case cancelled = "cancelled" -} -public struct CreateTagInput: Codable { - public let canonicalName: String - public let displayName: String? - public let formalName: String? - public let abbreviation: String? - public let aliases: [String] +public struct SearchTagsInput: Codable { + public let query: String public let namespace: String? public let tagType: TagType? - public let color: String? - public let icon: String? - public let description: String? - public let isOrganizationalAnchor: Bool? - public let privacyLevel: PrivacyLevel? - public let searchWeight: Int32? - public let attributes: [String: JsonValue]? + public let includeArchived: Bool? + public let limit: UInt? + public let resolveAmbiguous: Bool? + public let contextTagIds: [String]? - private enum CodingKeys: String, CodingKey { - case canonicalName = "canonical_name" - case displayName = "display_name" - case formalName = "formal_name" - case abbreviation = "abbreviation" - case aliases = "aliases" - case namespace = "namespace" - case tagType = "tag_type" - case color = "color" - case icon = "icon" - case description = "description" - case isOrganizationalAnchor = "is_organizational_anchor" - case privacyLevel = "privacy_level" - case searchWeight = "search_weight" - case attributes = "attributes" - } - - public init(canonicalName: String, displayName: String?, formalName: String?, abbreviation: String?, aliases: [String], namespace: String?, tagType: TagType?, color: String?, icon: String?, description: String?, isOrganizationalAnchor: Bool?, privacyLevel: PrivacyLevel?, searchWeight: Int32?, attributes: [String: JsonValue]?) { - self.canonicalName = canonicalName - self.displayName = displayName - self.formalName = formalName - self.abbreviation = abbreviation - self.aliases = aliases + public init(query: String, namespace: String?, tagType: TagType?, includeArchived: Bool?, limit: UInt?, resolveAmbiguous: Bool?, contextTagIds: [String]?) { + self.query = query self.namespace = namespace self.tagType = tagType - self.color = color - self.icon = icon - self.description = description - self.isOrganizationalAnchor = isOrganizationalAnchor - self.privacyLevel = privacyLevel - self.searchWeight = searchWeight - self.attributes = attributes + self.includeArchived = includeArchived + self.limit = limit + self.resolveAmbiguous = resolveAmbiguous + self.contextTagIds = contextTagIds + } +} +// MARK: - SearchTagsInput Custom Codable Implementation +extension SearchTagsInput { + private enum CodingKeys: String, CodingKey { + case query = "query" + case namespace = "namespace" + case tagType = "tag_type" + case includeArchived = "include_archived" + case limit = "limit" + case resolveAmbiguous = "resolve_ambiguous" + case contextTagIds = "context_tag_ids" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + query = try container.decode(String.self, forKey: .query) + namespace = try container.decodeIfPresent(String.self, forKey: .namespace) + tagType = try container.decodeIfPresent(TagType.self, forKey: .tagType) + includeArchived = try container.decodeIfPresent(Bool.self, forKey: .includeArchived) + limit = try container.decodeIfPresent(UInt.self, forKey: .limit) + resolveAmbiguous = try container.decodeIfPresent(Bool.self, forKey: .resolveAmbiguous) + contextTagIds = try container.decodeIfPresent([String].self, forKey: .contextTagIds) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(query, forKey: .query) + try container.encode(namespace, forKey: .namespace) + try container.encode(tagType, forKey: .tagType) + try container.encode(includeArchived, forKey: .includeArchived) + try container.encode(limit, forKey: .limit) + try container.encode(resolveAmbiguous, forKey: .resolveAmbiguous) + try container.encode(contextTagIds, forKey: .contextTagIds) } } -/// Represents an APFS container (physical storage with multiple volumes) -public struct ApfsContainer: Codable { - public let containerId: String - public let uuid: String - public let physicalStore: String - public let totalCapacity: UInt64 - public let capacityInUse: UInt64 - public let capacityFree: UInt64 - public let volumes: [ApfsVolumeInfo] + +/// Library statistics +public struct LibraryStatistics: Codable { + public let totalFiles: UInt64 + public let totalSize: UInt64 + public let locationCount: UInt32 + public let tagCount: UInt32 + public let thumbnailCount: UInt64 + public let databaseSize: UInt64 + public let lastIndexed: String? + public let updatedAt: String + + public init(totalFiles: UInt64, totalSize: UInt64, locationCount: UInt32, tagCount: UInt32, thumbnailCount: UInt64, databaseSize: UInt64, lastIndexed: String?, updatedAt: String) { + self.totalFiles = totalFiles + self.totalSize = totalSize + self.locationCount = locationCount + self.tagCount = tagCount + self.thumbnailCount = thumbnailCount + self.databaseSize = databaseSize + self.lastIndexed = lastIndexed + self.updatedAt = updatedAt + } +} +// MARK: - LibraryStatistics Custom Codable Implementation +extension LibraryStatistics { + private enum CodingKeys: String, CodingKey { + case totalFiles = "total_files" + case totalSize = "total_size" + case locationCount = "location_count" + case tagCount = "tag_count" + case thumbnailCount = "thumbnail_count" + case databaseSize = "database_size" + case lastIndexed = "last_indexed" + case updatedAt = "updated_at" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + totalFiles = try container.decode(UInt64.self, forKey: .totalFiles) + totalSize = try container.decode(UInt64.self, forKey: .totalSize) + locationCount = try container.decode(UInt32.self, forKey: .locationCount) + tagCount = try container.decode(UInt32.self, forKey: .tagCount) + thumbnailCount = try container.decode(UInt64.self, forKey: .thumbnailCount) + databaseSize = try container.decode(UInt64.self, forKey: .databaseSize) + lastIndexed = try container.decodeIfPresent(String.self, forKey: .lastIndexed) + updatedAt = try container.decode(String.self, forKey: .updatedAt) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(totalFiles, forKey: .totalFiles) + try container.encode(totalSize, forKey: .totalSize) + try container.encode(locationCount, forKey: .locationCount) + try container.encode(tagCount, forKey: .tagCount) + try container.encode(thumbnailCount, forKey: .thumbnailCount) + try container.encode(databaseSize, forKey: .databaseSize) + try container.encode(lastIndexed, forKey: .lastIndexed) + try container.encode(updatedAt, forKey: .updatedAt) + } +} + + +public struct LocationRescanOutput: Codable { + public let locationId: String + public let locationPath: String + public let jobId: String + public let fullRescan: Bool private enum CodingKeys: String, CodingKey { - case containerId = "container_id" - case uuid = "uuid" - case physicalStore = "physical_store" - case totalCapacity = "total_capacity" - case capacityInUse = "capacity_in_use" - case capacityFree = "capacity_free" - case volumes = "volumes" + case locationId = "location_id" + case locationPath = "location_path" + case jobId = "job_id" + case fullRescan = "full_rescan" } - public init(containerId: String, uuid: String, physicalStore: String, totalCapacity: UInt64, capacityInUse: UInt64, capacityFree: UInt64, volumes: [ApfsVolumeInfo]) { - self.containerId = containerId - self.uuid = uuid - self.physicalStore = physicalStore - self.totalCapacity = totalCapacity - self.capacityInUse = capacityInUse - self.capacityFree = capacityFree - self.volumes = volumes + public init(locationId: String, locationPath: String, jobId: String, fullRescan: Bool) { + self.locationId = locationId + self.locationPath = locationPath + self.jobId = jobId + self.fullRescan = fullRescan } } -public struct DeviceInfoLite: Codable { - public let id: String - public let name: String - public let osVersion: String - public let appVersion: String - public let isConnected: Bool - public let lastSeen: String +public struct LocationRemoveInput: Codable { + public let locationId: String + private enum CodingKeys: String, CodingKey { + case locationId = "location_id" + } + + public init(locationId: String) { + self.locationId = locationId + } +} + +/// Type of content +public enum ContentKind: String, Codable { + case unknown = "unknown" + case image = "image" + case video = "video" + case audio = "audio" + case document = "document" + case archive = "archive" + case code = "code" + case text = "text" + case database = "database" + case book = "book" + case font = "font" + case mesh = "mesh" + case config = "config" + case encrypted = "encrypted" + case key = "key" + case executable = "executable" + case binary = "binary" +} + +/// Comprehensive metrics for indexing operations +public struct IndexerMetrics: Codable { + public let totalDuration: RustDuration + public let discoveryDuration: RustDuration + public let processingDuration: RustDuration + public let contentDuration: RustDuration + public let filesPerSecond: Float + public let bytesPerSecond: Double + public let dirsPerSecond: Float + public let dbWrites: UInt64 + public let dbReads: UInt64 + public let batchCount: UInt64 + public let avgBatchSize: Float + public let totalErrors: UInt64 + public let criticalErrors: UInt64 + public let nonCriticalErrors: UInt64 + public let skippedPaths: UInt64 + public let peakMemoryBytes: UInt64? + public let avgMemoryBytes: UInt64? + + public init(totalDuration: RustDuration, discoveryDuration: RustDuration, processingDuration: RustDuration, contentDuration: RustDuration, filesPerSecond: Float, bytesPerSecond: Double, dirsPerSecond: Float, dbWrites: UInt64, dbReads: UInt64, batchCount: UInt64, avgBatchSize: Float, totalErrors: UInt64, criticalErrors: UInt64, nonCriticalErrors: UInt64, skippedPaths: UInt64, peakMemoryBytes: UInt64?, avgMemoryBytes: UInt64?) { + self.totalDuration = totalDuration + self.discoveryDuration = discoveryDuration + self.processingDuration = processingDuration + self.contentDuration = contentDuration + self.filesPerSecond = filesPerSecond + self.bytesPerSecond = bytesPerSecond + self.dirsPerSecond = dirsPerSecond + self.dbWrites = dbWrites + self.dbReads = dbReads + self.batchCount = batchCount + self.avgBatchSize = avgBatchSize + self.totalErrors = totalErrors + self.criticalErrors = criticalErrors + self.nonCriticalErrors = nonCriticalErrors + self.skippedPaths = skippedPaths + self.peakMemoryBytes = peakMemoryBytes + self.avgMemoryBytes = avgMemoryBytes + } +} +// MARK: - IndexerMetrics Custom Codable Implementation +extension IndexerMetrics { + private enum CodingKeys: String, CodingKey { + case totalDuration = "total_duration" + case discoveryDuration = "discovery_duration" + case processingDuration = "processing_duration" + case contentDuration = "content_duration" + case filesPerSecond = "files_per_second" + case bytesPerSecond = "bytes_per_second" + case dirsPerSecond = "dirs_per_second" + case dbWrites = "db_writes" + case dbReads = "db_reads" + case batchCount = "batch_count" + case avgBatchSize = "avg_batch_size" + case totalErrors = "total_errors" + case criticalErrors = "critical_errors" + case nonCriticalErrors = "non_critical_errors" + case skippedPaths = "skipped_paths" + case peakMemoryBytes = "peak_memory_bytes" + case avgMemoryBytes = "avg_memory_bytes" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + totalDuration = try container.decode(RustDuration.self, forKey: .totalDuration) + discoveryDuration = try container.decode(RustDuration.self, forKey: .discoveryDuration) + processingDuration = try container.decode(RustDuration.self, forKey: .processingDuration) + contentDuration = try container.decode(RustDuration.self, forKey: .contentDuration) + filesPerSecond = try container.decode(Float.self, forKey: .filesPerSecond) + bytesPerSecond = try container.decode(Double.self, forKey: .bytesPerSecond) + dirsPerSecond = try container.decode(Float.self, forKey: .dirsPerSecond) + dbWrites = try container.decode(UInt64.self, forKey: .dbWrites) + dbReads = try container.decode(UInt64.self, forKey: .dbReads) + batchCount = try container.decode(UInt64.self, forKey: .batchCount) + avgBatchSize = try container.decode(Float.self, forKey: .avgBatchSize) + totalErrors = try container.decode(UInt64.self, forKey: .totalErrors) + criticalErrors = try container.decode(UInt64.self, forKey: .criticalErrors) + nonCriticalErrors = try container.decode(UInt64.self, forKey: .nonCriticalErrors) + skippedPaths = try container.decode(UInt64.self, forKey: .skippedPaths) + peakMemoryBytes = try container.decodeIfPresent(UInt64.self, forKey: .peakMemoryBytes) + avgMemoryBytes = try container.decodeIfPresent(UInt64.self, forKey: .avgMemoryBytes) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(totalDuration, forKey: .totalDuration) + try container.encode(discoveryDuration, forKey: .discoveryDuration) + try container.encode(processingDuration, forKey: .processingDuration) + try container.encode(contentDuration, forKey: .contentDuration) + try container.encode(filesPerSecond, forKey: .filesPerSecond) + try container.encode(bytesPerSecond, forKey: .bytesPerSecond) + try container.encode(dirsPerSecond, forKey: .dirsPerSecond) + try container.encode(dbWrites, forKey: .dbWrites) + try container.encode(dbReads, forKey: .dbReads) + try container.encode(batchCount, forKey: .batchCount) + try container.encode(avgBatchSize, forKey: .avgBatchSize) + try container.encode(totalErrors, forKey: .totalErrors) + try container.encode(criticalErrors, forKey: .criticalErrors) + try container.encode(nonCriticalErrors, forKey: .nonCriticalErrors) + try container.encode(skippedPaths, forKey: .skippedPaths) + try container.encode(peakMemoryBytes, forKey: .peakMemoryBytes) + try container.encode(avgMemoryBytes, forKey: .avgMemoryBytes) + } +} + + +public struct LocationInfo: Codable { + public let id: String + public let path: String + public let name: String? + + public init(id: String, path: String, name: String?) { + self.id = id + self.path = path + self.name = name + } +} +// MARK: - LocationInfo Custom Codable Implementation +extension LocationInfo { private enum CodingKeys: String, CodingKey { case id = "id" + case path = "path" case name = "name" - case osVersion = "os_version" - case appVersion = "app_version" - case isConnected = "is_connected" - case lastSeen = "last_seen" } - public init(id: String, name: String, osVersion: String, appVersion: String, isConnected: Bool, lastSeen: String) { - self.id = id - self.name = name - self.osVersion = osVersion - self.appVersion = appVersion - self.isConnected = isConnected - self.lastSeen = lastSeen + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + path = try container.decode(String.self, forKey: .path) + name = try container.decodeIfPresent(String.self, forKey: .name) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(path, forKey: .path) + try container.encode(name, forKey: .name) } } -public struct PairCancelOutput: Codable { - public let cancelled: Bool - public init(cancelled: Bool) { - self.cancelled = cancelled +/// Output from volume untrack operation +public struct VolumeUntrackOutput: Codable { + public let fingerprint: VolumeFingerprint + + public init(fingerprint: VolumeFingerprint) { + self.fingerprint = fingerprint } } @@ -3982,204 +5634,6 @@ public struct LibraryRenameInput: Codable { } } -/// Indexing mode determines the depth of indexing -public enum IndexMode: Codable { - case shallow - case content - case deep -} - - -public struct CreateTagOutput: Codable { - public let tagId: String - public let canonicalName: String - public let namespace: String? - public let message: String - - private enum CodingKeys: String, CodingKey { - case tagId = "tag_id" - case canonicalName = "canonical_name" - case namespace = "namespace" - case message = "message" - } - - public init(tagId: String, canonicalName: String, namespace: String?, message: String) { - self.tagId = tagId - self.canonicalName = canonicalName - self.namespace = namespace - self.message = message - } -} - -/// Defines the scope of the filesystem to search within -public enum SearchScope { - case library - case location(SearchScopeLocation) - case path(SearchScopePath) -} -public struct SearchScopeLocation: Codable { - public let locationId: String - - private enum CodingKeys: String, CodingKey { - case locationId = "location_id" - } -} - -public struct SearchScopePath: Codable { - public let path: SdPath -} - - -// MARK: - SearchScope Codable Implementation -extension SearchScope: Codable { - private enum CodingKeys: String, CodingKey { - case library = "Library" - case location = "Location" - case path = "Path" - } - - public init(from decoder: Decoder) throws { - // Try externally-tagged format first (e.g., {"WaitingForConnection": null}) - if let container = try? decoder.container(keyedBy: CodingKeys.self) { - if container.allKeys.count == 1 { - let key = container.allKeys.first! - switch key { - case .library: - self = .library - return - case .location: - let data = try container.decode(SearchScopeLocation.self, forKey: .location) - self = .location(data) - return - case .path: - let data = try container.decode(SearchScopePath.self, forKey: .path) - self = .path(data) - return - } - return - } - } - - // Fallback: try decoding as plain string for unit variants (serde default) - if let stringContainer = try? decoder.singleValueContainer() { - if let variantString = try? stringContainer.decode(String.self) { - switch variantString { - case "Library": - self = .library - return - default: - break - } - } - } - - throw DecodingError.dataCorrupted( - DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode enum - expected externally-tagged object or string for unit variants") - ) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - switch self { - case .library: - try container.encodeNil(forKey: .library) - case .location(let data): - try container.encode(data, forKey: .location) - case .path(let data): - try container.encode(data, forKey: .path) - } - } -} - - -/// Main input structure for file search operations -public struct FileSearchInput: Codable { - public let query: String - public let scope: SearchScope - public let mode: SearchMode - public let filters: SearchFilters - public let sort: SortOptions - public let pagination: PaginationOptions - - public init(query: String, scope: SearchScope, mode: SearchMode, filters: SearchFilters, sort: SortOptions, pagination: PaginationOptions) { - self.query = query - self.scope = scope - self.mode = mode - self.filters = filters - self.sort = sort - self.pagination = pagination - } -} - -public struct NetworkStatus: Codable { - public let running: Bool - public let nodeId: String? - public let addresses: [String] - public let pairedDevices: UInt - public let connectedDevices: UInt - public let version: String - - private enum CodingKeys: String, CodingKey { - case running = "running" - case nodeId = "node_id" - case addresses = "addresses" - case pairedDevices = "paired_devices" - case connectedDevices = "connected_devices" - case version = "version" - } - - public init(running: Bool, nodeId: String?, addresses: [String], pairedDevices: UInt, connectedDevices: UInt, version: String) { - self.running = running - self.nodeId = nodeId - self.addresses = addresses - self.pairedDevices = pairedDevices - self.connectedDevices = connectedDevices - self.version = version - } -} - -public struct PairStatusQueryInput: Codable { -} - -/// Privacy levels for tag visibility control -public enum PrivacyLevel: Codable { - case normal - case archive - case hidden -} - - -/// Types of semantic tags with different behaviors -public enum TagType: Codable { - case standard - case organizational - case privacy - case system -} - - -public struct TagSearchFilters: Codable { - public let namespace: String? - public let tagType: String? - public let includeArchived: Bool - public let limit: UInt? - - private enum CodingKeys: String, CodingKey { - case namespace = "namespace" - case tagType = "tag_type" - case includeArchived = "include_archived" - case limit = "limit" - } - - public init(namespace: String?, tagType: String?, includeArchived: Bool, limit: UInt?) { - self.namespace = namespace - self.tagType = tagType - self.includeArchived = includeArchived - self.limit = limit - } -} - /// Complete Spacedrive API structure public enum SpacedriveApi { @@ -4202,124 +5656,124 @@ extension SpacedriveApi: Codable { /// Core-scoped actions public enum CoreAction { - case NetworkStop(input: NetworkStopInput, output: NetworkStopOutput) - case LibrariesDelete(input: LibraryDeleteInput, output: LibraryDeleteOutput) - case NetworkDeviceRevoke(input: DeviceRevokeInput, output: DeviceRevokeOutput) - case NetworkPairGenerate(input: PairGenerateInput, output: PairGenerateOutput) - case NetworkStart(input: NetworkStartInput, output: NetworkStartOutput) - case NetworkSpacedropSend(input: SpacedropSendInput, output: SpacedropSendOutput) case NetworkPairCancel(input: PairCancelInput, output: PairCancelOutput) - case NetworkPairJoin(input: PairJoinInput, output: PairJoinOutput) + case NetworkSpacedropSend(input: SpacedropSendInput, output: SpacedropSendOutput) + case NetworkDeviceRevoke(input: DeviceRevokeInput, output: DeviceRevokeOutput) + case NetworkStop(input: NetworkStopInput, output: NetworkStopOutput) case LibrariesCreate(input: LibraryCreateInput, output: LibraryCreateOutput) + case NetworkPairGenerate(input: PairGenerateInput, output: PairGenerateOutput) + case NetworkPairJoin(input: PairJoinInput, output: PairJoinOutput) + case NetworkStart(input: NetworkStartInput, output: NetworkStartOutput) + case LibrariesDelete(input: LibraryDeleteInput, output: LibraryDeleteOutput) } extension CoreAction: Codable { public var wireMethod: String { switch self { - case .NetworkStop: return "action:network.stop.input.v1" - case .LibrariesDelete: return "action:libraries.delete.input.v1" - case .NetworkDeviceRevoke: return "action:network.device.revoke.input.v1" - case .NetworkPairGenerate: return "action:network.pair.generate.input.v1" - case .NetworkStart: return "action:network.start.input.v1" - case .NetworkSpacedropSend: return "action:network.spacedrop.send.input.v1" case .NetworkPairCancel: return "action:network.pair.cancel.input.v1" - case .NetworkPairJoin: return "action:network.pair.join.input.v1" + case .NetworkSpacedropSend: return "action:network.spacedrop.send.input.v1" + case .NetworkDeviceRevoke: return "action:network.device.revoke.input.v1" + case .NetworkStop: return "action:network.stop.input.v1" case .LibrariesCreate: return "action:libraries.create.input.v1" + case .NetworkPairGenerate: return "action:network.pair.generate.input.v1" + case .NetworkPairJoin: return "action:network.pair.join.input.v1" + case .NetworkStart: return "action:network.start.input.v1" + case .LibrariesDelete: return "action:libraries.delete.input.v1" } } } /// Library-scoped actions public enum LibraryAction { - case MediaThumbnail(input: ThumbnailInput, output: JobReceipt) - case LocationsRemove(input: LocationRemoveInput, output: LocationRemoveOutput) case FilesCopy(input: FileCopyInput, output: JobReceipt) + case LibrariesRename(input: LibraryRenameInput, output: LibraryRenameOutput) + case LocationsRemove(input: LocationRemoveInput, output: LocationRemoveOutput) + case LocationsRescan(input: LocationRescanInput, output: LocationRescanOutput) + case JobsCancel(input: JobCancelInput, output: JobCancelOutput) + case TagsCreate(input: CreateTagInput, output: CreateTagOutput) + case JobsPause(input: JobPauseInput, output: JobPauseOutput) case TagsApply(input: ApplyTagsInput, output: ApplyTagsOutput) + case MediaThumbnail(input: ThumbnailInput, output: JobReceipt) case LocationsAdd(input: LocationAddInput, output: LocationAddOutput) case IndexingStart(input: IndexInput, output: JobReceipt) - case LibrariesRename(input: LibraryRenameInput, output: LibraryRenameOutput) case VolumesSpeedTest(input: VolumeSpeedTestInput, output: VolumeSpeedTestOutput) - case JobsCancel(input: JobCancelInput, output: JobCancelOutput) - case JobsResume(input: JobResumeInput, output: JobResumeOutput) - case TagsCreate(input: CreateTagInput, output: CreateTagOutput) - case LocationsRescan(input: LocationRescanInput, output: LocationRescanOutput) - case VolumesTrack(input: VolumeTrackInput, output: VolumeTrackOutput) - case JobsPause(input: JobPauseInput, output: JobPauseOutput) case VolumesUntrack(input: VolumeUntrackInput, output: VolumeUntrackOutput) case LibrariesExport(input: LibraryExportInput, output: LibraryExportOutput) + case JobsResume(input: JobResumeInput, output: JobResumeOutput) + case VolumesTrack(input: VolumeTrackInput, output: VolumeTrackOutput) } extension LibraryAction: Codable { public var wireMethod: String { switch self { - case .MediaThumbnail: return "action:media.thumbnail.input.v1" - case .LocationsRemove: return "action:locations.remove.input.v1" case .FilesCopy: return "action:files.copy.input.v1" + case .LibrariesRename: return "action:libraries.rename.input.v1" + case .LocationsRemove: return "action:locations.remove.input.v1" + case .LocationsRescan: return "action:locations.rescan.input.v1" + case .JobsCancel: return "action:jobs.cancel.input.v1" + case .TagsCreate: return "action:tags.create.input.v1" + case .JobsPause: return "action:jobs.pause.input.v1" case .TagsApply: return "action:tags.apply.input.v1" + case .MediaThumbnail: return "action:media.thumbnail.input.v1" case .LocationsAdd: return "action:locations.add.input.v1" case .IndexingStart: return "action:indexing.start.input.v1" - case .LibrariesRename: return "action:libraries.rename.input.v1" case .VolumesSpeedTest: return "action:volumes.speed_test.input.v1" - case .JobsCancel: return "action:jobs.cancel.input.v1" - case .JobsResume: return "action:jobs.resume.input.v1" - case .TagsCreate: return "action:tags.create.input.v1" - case .LocationsRescan: return "action:locations.rescan.input.v1" - case .VolumesTrack: return "action:volumes.track.input.v1" - case .JobsPause: return "action:jobs.pause.input.v1" case .VolumesUntrack: return "action:volumes.untrack.input.v1" case .LibrariesExport: return "action:libraries.export.input.v1" + case .JobsResume: return "action:jobs.resume.input.v1" + case .VolumesTrack: return "action:volumes.track.input.v1" } } } /// Core-scoped queries public enum CoreQuery { - case LibrariesList(input: ListLibrariesInput, output: [LibraryInfo]) - case NetworkDevices(input: ListDevicesInput, output: [DeviceInfoLite]) - case CoreStatus(input: Empty, output: CoreStatus) case NetworkPairStatus(input: PairStatusQueryInput, output: PairStatusOutput) + case NetworkDevices(input: ListDevicesInput, output: [DeviceInfoLite]) case NetworkStatus(input: NetworkStatusQueryInput, output: NetworkStatus) + case CoreStatus(input: Empty, output: CoreStatus) + case LibrariesList(input: ListLibrariesInput, output: [LibraryInfo]) } extension CoreQuery: Codable { public var wireMethod: String { switch self { - case .LibrariesList: return "query:libraries.list.v1" - case .NetworkDevices: return "query:network.devices.v1" - case .CoreStatus: return "query:core.status.v1" case .NetworkPairStatus: return "query:network.pair.status.v1" + case .NetworkDevices: return "query:network.devices.v1" case .NetworkStatus: return "query:network.status.v1" + case .CoreStatus: return "query:core.status.v1" + case .LibrariesList: return "query:libraries.list.v1" } } } /// Library-scoped queries public enum LibraryQuery { - case FilesUniqueToLocation(input: UniqueToLocationInput, output: UniqueToLocationOutput) - case TagsSearch(input: SearchTagsInput, output: SearchTagsOutput) - case LibrariesInfo(input: LibraryInfoQueryInput, output: LibraryInfoOutput) - case JobsInfo(input: JobInfoQueryInput, output: JobInfoOutput) - case FilesByPath(input: FileByPathQuery, output: File) - case JobsList(input: JobListInput, output: JobListOutput) - case LocationsList(input: LocationsListQueryInput, output: LocationsListOutput) - case SearchFiles(input: FileSearchInput, output: FileSearchOutput) case FilesDirectoryListing(input: DirectoryListingInput, output: DirectoryListingOutput) + case TagsSearch(input: SearchTagsInput, output: SearchTagsOutput) + case FilesUniqueToLocation(input: UniqueToLocationInput, output: UniqueToLocationOutput) + case JobsInfo(input: JobInfoQueryInput, output: JobInfoOutput) + case SearchFiles(input: FileSearchInput, output: FileSearchOutput) + case JobsList(input: JobListInput, output: JobListOutput) + case FilesByPath(input: FileByPathQuery, output: File) + case LibrariesInfo(input: LibraryInfoQueryInput, output: LibraryInfoOutput) case FilesById(input: FileByIdQuery, output: File) + case LocationsList(input: LocationsListQueryInput, output: LocationsListOutput) } extension LibraryQuery: Codable { public var wireMethod: String { switch self { - case .FilesUniqueToLocation: return "query:files.unique_to_location.v1" - case .TagsSearch: return "query:tags.search.v1" - case .LibrariesInfo: return "query:libraries.info.v1" - case .JobsInfo: return "query:jobs.info.v1" - case .FilesByPath: return "query:files.by_path.v1" - case .JobsList: return "query:jobs.list.v1" - case .LocationsList: return "query:locations.list.v1" - case .SearchFiles: return "query:search.files.v1" case .FilesDirectoryListing: return "query:files.directory_listing.v1" + case .TagsSearch: return "query:tags.search.v1" + case .FilesUniqueToLocation: return "query:files.unique_to_location.v1" + case .JobsInfo: return "query:jobs.info.v1" + case .SearchFiles: return "query:search.files.v1" + case .JobsList: return "query:jobs.list.v1" + case .FilesByPath: return "query:files.by_path.v1" + case .LibrariesInfo: return "query:libraries.info.v1" case .FilesById: return "query:files.by_id.v1" + case .LocationsList: return "query:locations.list.v1" } } }