PluginManager.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import Foundation
  2. import LoopKit
  3. import LoopKitUI
  4. import Swinject
  5. protocol PluginManager {
  6. var availablePumpManagers: [PumpManagerDescriptor] { get }
  7. var availableCGMManagers: [CGMManagerDescriptor] { get }
  8. var availableServices: [ServiceDescriptor] { get }
  9. func getPumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type?
  10. func getCGMManagerTypeByIdentifier(_ identifier: String) -> CGMManagerUI.Type?
  11. func getServiceTypeByIdentifier(_ identifier: String) -> ServiceUI.Type?
  12. }
  13. class BasePluginManager: Injectable, PluginManager {
  14. let pluginBundles: [Bundle]
  15. init(resolver: Resolver) {
  16. let pluginsURL: URL? = Bundle.main.privateFrameworksURL
  17. var bundles = [Bundle]()
  18. if let pluginsURL = pluginsURL {
  19. do {
  20. for pluginURL in try FileManager.default.contentsOfDirectory(at: pluginsURL, includingPropertiesForKeys: nil)
  21. .filter({ $0.path.hasSuffix(".framework") })
  22. {
  23. if let bundle = Bundle(url: pluginURL) {
  24. if let bname = bundle.object(forInfoDictionaryKey: "CFBundleName") as? String {
  25. debug(.deviceManager, "bundle name2:\(bname)")
  26. }
  27. if let bcgm = bundle.object(forInfoDictionaryKey: "com.loopkit.Loop.CGMManagerIdentifier") as? String {
  28. debug(.deviceManager, "bundle is CGM")
  29. }
  30. if bundle.isLoopPlugin {
  31. debug(.deviceManager, "Found loop plugin:\(pluginURL.absoluteString)")
  32. bundles.append(bundle)
  33. }
  34. }
  35. }
  36. } catch {
  37. debug(.deviceManager, "Error loading plugin: \(error)")
  38. }
  39. }
  40. pluginBundles = bundles
  41. injectServices(resolver)
  42. }
  43. func getPumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type? {
  44. for bundle in pluginBundles {
  45. if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String,
  46. name == identifier
  47. {
  48. do {
  49. try bundle.loadAndReturnError()
  50. if let principalClass = bundle.principalClass as? NSObject.Type {
  51. if let plugin = principalClass.init() as? PumpManagerUIPlugin {
  52. return plugin.pumpManagerType
  53. } else {
  54. fatalError("PrincipalClass does not conform to PumpManagerUIPlugin")
  55. }
  56. } else {
  57. fatalError("PrincipalClass not found")
  58. }
  59. } catch {
  60. debug(.deviceManager, "Error loading plugin: \(error)")
  61. }
  62. }
  63. }
  64. return nil
  65. }
  66. var availablePumpManagers: [PumpManagerDescriptor] {
  67. pluginBundles.compactMap({ (bundle) -> PumpManagerDescriptor? in
  68. guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerDisplayName.rawValue) as? String,
  69. let identifier = bundle
  70. .object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String
  71. else {
  72. return nil
  73. }
  74. return PumpManagerDescriptor(identifier: identifier, localizedTitle: title)
  75. })
  76. }
  77. func getCGMManagerTypeByIdentifier(_ identifier: String) -> CGMManagerUI.Type? {
  78. for bundle in pluginBundles {
  79. if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String,
  80. name == identifier
  81. {
  82. do {
  83. try bundle.loadAndReturnError()
  84. if let principalClass = bundle.principalClass as? NSObject.Type {
  85. if let plugin = principalClass.init() as? CGMManagerUIPlugin {
  86. return plugin.cgmManagerType
  87. } else {
  88. fatalError("PrincipalClass does not conform to CGMManagerUIPlugin")
  89. }
  90. } else {
  91. fatalError("PrincipalClass not found")
  92. }
  93. } catch {
  94. debug(.deviceManager, "Error loading plugin: \(error)")
  95. }
  96. }
  97. }
  98. return nil
  99. }
  100. var availableCGMManagers: [CGMManagerDescriptor] {
  101. pluginBundles.compactMap({ (bundle) -> CGMManagerDescriptor? in
  102. guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerDisplayName.rawValue) as? String,
  103. let identifier = bundle
  104. .object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String
  105. else {
  106. return nil
  107. }
  108. return CGMManagerDescriptor(identifier: identifier, localizedTitle: title)
  109. })
  110. }
  111. func getServiceTypeByIdentifier(_ identifier: String) -> ServiceUI.Type? {
  112. for bundle in pluginBundles {
  113. if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceIdentifier.rawValue) as? String,
  114. name == identifier
  115. {
  116. do {
  117. try bundle.loadAndReturnError()
  118. if let principalClass = bundle.principalClass as? NSObject.Type {
  119. if let plugin = principalClass.init() as? ServiceUIPlugin {
  120. return plugin.serviceType
  121. } else {
  122. fatalError("PrincipalClass does not conform to ServiceUIPlugin")
  123. }
  124. } else {
  125. fatalError("PrincipalClass not found")
  126. }
  127. } catch {
  128. debug(.deviceManager, "Error loading plugin: \(error)")
  129. }
  130. }
  131. }
  132. return nil
  133. }
  134. var availableServices: [ServiceDescriptor] {
  135. pluginBundles.compactMap({ (bundle) -> ServiceDescriptor? in
  136. guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceDisplayName.rawValue) as? String,
  137. let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceIdentifier.rawValue) as? String
  138. else {
  139. return nil
  140. }
  141. return ServiceDescriptor(identifier: identifier, localizedTitle: title)
  142. })
  143. }
  144. func getStatefulPluginTypeByIdentifier(_ identifier: String) -> StatefulPluggable.Type? {
  145. for bundle in pluginBundles {
  146. if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.statefulPluginIdentifier.rawValue) as? String,
  147. name == identifier
  148. {
  149. do {
  150. try bundle.loadAndReturnError()
  151. if let principalClass = bundle.principalClass as? NSObject.Type {
  152. if let plugin = principalClass.init() as? StatefulPlugin {
  153. return plugin.pluginType
  154. } else {
  155. fatalError("PrincipalClass does not conform to StatefulPlugin")
  156. }
  157. } else {
  158. fatalError("PrincipalClass not found")
  159. }
  160. } catch {
  161. debug(.deviceManager, "Error loading plugin: \(error)")
  162. }
  163. }
  164. }
  165. return nil
  166. }
  167. var availableStatefulPluginIdentifiers: [String] {
  168. pluginBundles.compactMap({ (bundle) -> String? in
  169. bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.statefulPluginIdentifier.rawValue) as? String
  170. })
  171. }
  172. func getOnboardingTypeByIdentifier(_ identifier: String) -> OnboardingUI.Type? {
  173. for bundle in pluginBundles {
  174. if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.onboardingIdentifier.rawValue) as? String,
  175. name == identifier
  176. {
  177. do {
  178. try bundle.loadAndReturnError()
  179. if let principalClass = bundle.principalClass as? NSObject.Type {
  180. if let plugin = principalClass.init() as? OnboardingUIPlugin {
  181. return plugin.onboardingType
  182. } else {
  183. fatalError("PrincipalClass does not conform to OnboardingUIPlugin")
  184. }
  185. } else {
  186. fatalError("PrincipalClass not found")
  187. }
  188. } catch {
  189. debug(.deviceManager, "Error loading plugin: \(error)")
  190. }
  191. }
  192. }
  193. return nil
  194. }
  195. var availableOnboardingIdentifiers: [String] {
  196. pluginBundles.compactMap({ (bundle) -> String? in
  197. bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.onboardingIdentifier.rawValue) as? String
  198. })
  199. }
  200. func getSupportUITypeByIdentifier(_ identifier: String) -> SupportUI.Type? {
  201. for bundle in pluginBundles {
  202. if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.supportIdentifier.rawValue) as? String,
  203. name == identifier
  204. {
  205. do {
  206. try bundle.loadAndReturnError()
  207. if let principalClass = bundle.principalClass as? NSObject.Type {
  208. if let plugin = principalClass.init() as? SupportUIPlugin {
  209. return type(of: plugin.support)
  210. } else {
  211. fatalError("PrincipalClass does not conform to SupportUIPlugin")
  212. }
  213. } else {
  214. fatalError("PrincipalClass not found")
  215. }
  216. } catch {
  217. debug(.deviceManager, "Error loading plugin: \(error)")
  218. }
  219. }
  220. }
  221. return nil
  222. }
  223. }
  224. extension Bundle {
  225. var isPumpManagerPlugin: Bool {
  226. object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String != nil }
  227. var isCGMManagerPlugin: Bool {
  228. object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String != nil }
  229. var isStatefulPlugin: Bool {
  230. object(forInfoDictionaryKey: LoopPluginBundleKey.statefulPluginIdentifier.rawValue) as? String != nil }
  231. var isServicePlugin: Bool { object(forInfoDictionaryKey: LoopPluginBundleKey.serviceIdentifier.rawValue) as? String != nil }
  232. var isOnboardingPlugin: Bool {
  233. object(forInfoDictionaryKey: LoopPluginBundleKey.onboardingIdentifier.rawValue) as? String != nil }
  234. var isSupportPlugin: Bool { object(forInfoDictionaryKey: LoopPluginBundleKey.supportIdentifier.rawValue) as? String != nil }
  235. var isLoopPlugin: Bool {
  236. isPumpManagerPlugin || isCGMManagerPlugin || isStatefulPlugin || isServicePlugin || isOnboardingPlugin || isSupportPlugin
  237. }
  238. var isLoopExtension: Bool { object(forInfoDictionaryKey: LoopPluginBundleKey.extensionIdentifier.rawValue) as? String != nil }
  239. var isSimulator: Bool { object(forInfoDictionaryKey: LoopPluginBundleKey.pluginIsSimulator.rawValue) as? Bool == true }
  240. }