TemporaryScheduleOverride.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. //
  2. // TemporaryScheduleOverride.swift
  3. // LoopKit
  4. //
  5. // Created by Michael Pangburn on 1/1/19.
  6. // Copyright © 2019 LoopKit Authors. All rights reserved.
  7. //
  8. import Foundation
  9. import HealthKit
  10. public struct TemporaryScheduleOverride: Hashable {
  11. public enum Context: Hashable {
  12. case preMeal
  13. case legacyWorkout
  14. case preset(TemporaryScheduleOverridePreset)
  15. case custom
  16. }
  17. public enum EnactTrigger: Hashable {
  18. case local
  19. case remote(String)
  20. }
  21. public enum Duration: Hashable, Comparable {
  22. case finite(TimeInterval)
  23. case indefinite
  24. public var timeInterval: TimeInterval {
  25. switch self {
  26. case .finite(let interval):
  27. return interval
  28. case .indefinite:
  29. return .infinity
  30. }
  31. }
  32. public var isFinite: Bool {
  33. return timeInterval.isFinite
  34. }
  35. public var isInfinite: Bool {
  36. return timeInterval.isInfinite
  37. }
  38. public static func < (lhs: Duration, rhs: Duration) -> Bool {
  39. return lhs.timeInterval < rhs.timeInterval
  40. }
  41. }
  42. public var context: Context
  43. public var settings: TemporaryScheduleOverrideSettings
  44. public var startDate: Date
  45. public let enactTrigger: EnactTrigger
  46. public let syncIdentifier: UUID
  47. public var actualEnd: End = .natural
  48. public var actualEndDate: Date {
  49. switch actualEnd {
  50. case .natural:
  51. return scheduledEndDate
  52. case .early(let endDate):
  53. return endDate
  54. case .deleted:
  55. return scheduledEndDate
  56. }
  57. }
  58. public var duration: Duration {
  59. didSet {
  60. precondition(duration.timeInterval > 0)
  61. }
  62. }
  63. public var scheduledEndDate: Date {
  64. get {
  65. return startDate + duration.timeInterval
  66. }
  67. set {
  68. precondition(newValue > startDate)
  69. if newValue == .distantFuture {
  70. duration = .indefinite
  71. } else {
  72. duration = .finite(newValue.timeIntervalSince(startDate))
  73. }
  74. }
  75. }
  76. public var activeInterval: DateInterval {
  77. get {
  78. return DateInterval(start: startDate, end: actualEndDate)
  79. }
  80. set {
  81. startDate = newValue.start
  82. scheduledEndDate = newValue.end
  83. }
  84. }
  85. public func hasFinished(relativeTo date: Date = Date()) -> Bool {
  86. return date > actualEndDate
  87. }
  88. public init(context: Context, settings: TemporaryScheduleOverrideSettings, startDate: Date, duration: Duration, enactTrigger: EnactTrigger, syncIdentifier: UUID) {
  89. precondition(duration.timeInterval > 0)
  90. self.context = context
  91. self.settings = settings
  92. self.startDate = startDate
  93. self.duration = duration
  94. self.enactTrigger = enactTrigger
  95. self.syncIdentifier = syncIdentifier
  96. }
  97. public func isActive(at date: Date = Date()) -> Bool {
  98. return activeInterval.contains(date)
  99. }
  100. }
  101. extension TemporaryScheduleOverride: RawRepresentable {
  102. public typealias RawValue = [String: Any]
  103. public init?(rawValue: RawValue) {
  104. guard
  105. let contextRawValue = rawValue["context"] as? Context.RawValue,
  106. let context = Context(rawValue: contextRawValue),
  107. let settingsRawValue = rawValue["settings"] as? TemporaryScheduleOverrideSettings.RawValue,
  108. let settings = TemporaryScheduleOverrideSettings(rawValue: settingsRawValue),
  109. let startDateSeconds = rawValue["startDate"] as? TimeInterval,
  110. let durationRawValue = rawValue["duration"] as? Duration.RawValue,
  111. let duration = Duration(rawValue: durationRawValue)
  112. else {
  113. return nil
  114. }
  115. let startDate = Date(timeIntervalSince1970: startDateSeconds)
  116. let enactTrigger: EnactTrigger
  117. if let enactTriggerRaw = rawValue["enactTrigger"] as? EnactTrigger.RawValue,
  118. let storedEnactTrigger = EnactTrigger(rawValue: enactTriggerRaw)
  119. {
  120. enactTrigger = storedEnactTrigger
  121. } else {
  122. enactTrigger = .local
  123. }
  124. let syncIdentifier: UUID
  125. if let syncIdentifierRaw = rawValue["syncIdentifier"] as? String,
  126. let storedSyncIdentifier = UUID(uuidString: syncIdentifierRaw) {
  127. syncIdentifier = storedSyncIdentifier
  128. } else {
  129. syncIdentifier = UUID()
  130. }
  131. self.init(context: context, settings: settings, startDate: startDate, duration: duration, enactTrigger: enactTrigger, syncIdentifier: syncIdentifier)
  132. }
  133. public var rawValue: RawValue {
  134. return [
  135. "context": context.rawValue,
  136. "settings": settings.rawValue,
  137. "startDate": startDate.timeIntervalSince1970,
  138. "duration": duration.rawValue,
  139. "syncIdentifier": syncIdentifier.uuidString,
  140. "enactTrigger": enactTrigger.rawValue,
  141. ]
  142. }
  143. }
  144. extension TemporaryScheduleOverride: Codable {}
  145. extension TemporaryScheduleOverride.Context: RawRepresentable {
  146. public typealias RawValue = [String: Any]
  147. public init?(rawValue: RawValue) {
  148. guard let context = rawValue["context"] as? String else {
  149. return nil
  150. }
  151. switch context {
  152. case "premeal":
  153. self = .preMeal
  154. case "legacyWorkout":
  155. self = .legacyWorkout
  156. case "preset":
  157. guard
  158. let presetRawValue = rawValue["preset"] as? TemporaryScheduleOverridePreset.RawValue,
  159. let preset = TemporaryScheduleOverridePreset(rawValue: presetRawValue)
  160. else {
  161. return nil
  162. }
  163. self = .preset(preset)
  164. case "custom":
  165. self = .custom
  166. default:
  167. return nil
  168. }
  169. }
  170. public var rawValue: RawValue {
  171. switch self {
  172. case .preMeal:
  173. return ["context": "premeal"]
  174. case .legacyWorkout:
  175. return ["context": "legacyWorkout"]
  176. case .preset(let preset):
  177. return [
  178. "context": "preset",
  179. "preset": preset.rawValue
  180. ]
  181. case .custom:
  182. return ["context": "custom"]
  183. }
  184. }
  185. }
  186. extension TemporaryScheduleOverride.Context: Codable {
  187. public init(from decoder: Decoder) throws {
  188. if let string = try? decoder.singleValueContainer().decode(String.self) {
  189. switch string {
  190. case CodableKeys.preMeal.rawValue:
  191. self = .preMeal
  192. case CodableKeys.legacyWorkout.rawValue:
  193. self = .legacyWorkout
  194. case CodableKeys.custom.rawValue:
  195. self = .custom
  196. default:
  197. throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
  198. }
  199. } else {
  200. let container = try decoder.container(keyedBy: CodableKeys.self)
  201. if let preset = try container.decodeIfPresent(Preset.self, forKey: .preset) {
  202. self = .preset(preset.preset)
  203. } else {
  204. throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
  205. }
  206. }
  207. }
  208. public func encode(to encoder: Encoder) throws {
  209. switch self {
  210. case .preMeal:
  211. var container = encoder.singleValueContainer()
  212. try container.encode(CodableKeys.preMeal.rawValue)
  213. case .legacyWorkout:
  214. var container = encoder.singleValueContainer()
  215. try container.encode(CodableKeys.legacyWorkout.rawValue)
  216. case .preset(let preset):
  217. var container = encoder.container(keyedBy: CodableKeys.self)
  218. try container.encode(Preset(preset: preset), forKey: .preset)
  219. case .custom:
  220. var container = encoder.singleValueContainer()
  221. try container.encode(CodableKeys.custom.rawValue)
  222. }
  223. }
  224. private struct Preset: Codable {
  225. let preset: TemporaryScheduleOverridePreset
  226. }
  227. private enum CodableKeys: String, CodingKey {
  228. case preMeal
  229. case legacyWorkout
  230. case preset
  231. case custom
  232. }
  233. }
  234. extension TemporaryScheduleOverride.Duration: RawRepresentable {
  235. public typealias RawValue = [String: Any]
  236. public init?(rawValue: RawValue) {
  237. guard let duration = rawValue["duration"] as? String else {
  238. return nil
  239. }
  240. switch duration {
  241. case "finite":
  242. guard let interval = rawValue["interval"] as? TimeInterval else {
  243. return nil
  244. }
  245. self = .finite(interval)
  246. case "indefinite":
  247. self = .indefinite
  248. default:
  249. return nil
  250. }
  251. }
  252. public var rawValue: RawValue {
  253. switch self {
  254. case .finite(let interval):
  255. return [
  256. "duration": "finite",
  257. "interval": interval
  258. ]
  259. case .indefinite:
  260. return ["duration": "indefinite"]
  261. }
  262. }
  263. }
  264. extension TemporaryScheduleOverride.Duration: Codable {
  265. public init(from decoder: Decoder) throws {
  266. if let string = try? decoder.singleValueContainer().decode(String.self) {
  267. switch string {
  268. case CodableKeys.indefinite.rawValue:
  269. self = .indefinite
  270. default:
  271. throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
  272. }
  273. } else {
  274. let container = try decoder.container(keyedBy: CodableKeys.self)
  275. if let finite = try container.decodeIfPresent(Finite.self, forKey: .finite) {
  276. self = .finite(finite.duration)
  277. } else {
  278. throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
  279. }
  280. }
  281. }
  282. public func encode(to encoder: Encoder) throws {
  283. switch self {
  284. case .finite(let duration):
  285. var container = encoder.container(keyedBy: CodableKeys.self)
  286. try container.encode(Finite(duration: duration), forKey: .finite)
  287. case .indefinite:
  288. var container = encoder.singleValueContainer()
  289. try container.encode(CodableKeys.indefinite.rawValue)
  290. }
  291. }
  292. private struct Finite: Codable {
  293. let duration: TimeInterval
  294. }
  295. private enum CodableKeys: String, CodingKey {
  296. case finite
  297. case indefinite
  298. }
  299. }
  300. extension TemporaryScheduleOverride.EnactTrigger: RawRepresentable {
  301. public typealias RawValue = [String: Any]
  302. public init?(rawValue: RawValue) {
  303. guard let trigger = rawValue["trigger"] as? String else {
  304. return nil
  305. }
  306. switch trigger {
  307. case "local":
  308. self = .local
  309. case "remote":
  310. guard let remoteAddress = rawValue["remoteAddress"] as? String else {
  311. return nil
  312. }
  313. self = .remote(remoteAddress)
  314. default:
  315. return nil
  316. }
  317. }
  318. public var rawValue: RawValue {
  319. switch self {
  320. case .local:
  321. return ["trigger": "local"]
  322. case .remote(let remoteAddress):
  323. return [
  324. "trigger": "remote",
  325. "remoteAddress": remoteAddress
  326. ]
  327. }
  328. }
  329. }
  330. extension TemporaryScheduleOverride.EnactTrigger: Codable {
  331. public init(from decoder: Decoder) throws {
  332. if let string = try? decoder.singleValueContainer().decode(String.self) {
  333. switch string {
  334. case CodableKeys.local.rawValue:
  335. self = .local
  336. default:
  337. throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
  338. }
  339. } else {
  340. let container = try decoder.container(keyedBy: CodableKeys.self)
  341. if let remote = try container.decodeIfPresent(Remote.self, forKey: .remote) {
  342. self = .remote(remote.address)
  343. } else {
  344. throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
  345. }
  346. }
  347. }
  348. public func encode(to encoder: Encoder) throws {
  349. switch self {
  350. case .local:
  351. var container = encoder.singleValueContainer()
  352. try container.encode(CodableKeys.local.rawValue)
  353. case .remote(let address):
  354. var container = encoder.container(keyedBy: CodableKeys.self)
  355. try container.encode(Remote(address: address), forKey: .remote)
  356. }
  357. }
  358. private struct Remote: Codable {
  359. let address: String
  360. }
  361. private enum CodableKeys: String, CodingKey {
  362. case local
  363. case remote
  364. }
  365. }