ShareViewController.swift 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. //
  2. // ShareViewController.swift
  3. // ShareViewController
  4. //
  5. // Created by leizhe on 2022/3/17.
  6. //
  7. import MobileCoreServices
  8. import Social
  9. import UIKit
  10. import UniformTypeIdentifiers
  11. class ShareViewController: UIViewController {
  12. private var sharedData: SharedData = SharedData.init(resources: [])
  13. var groupContainerUrl: URL? {
  14. return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.logseq.logseq")
  15. }
  16. override public func viewDidAppear(_ animated: Bool) {
  17. super.viewDidAppear(animated)
  18. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
  19. self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
  20. }
  21. }
  22. private func sendData() {
  23. let encoder: JSONEncoder = JSONEncoder()
  24. let data = try? encoder.encode(self.sharedData)
  25. let queryPayload = String(decoding: data!, as: UTF8.self)
  26. let queryItems =
  27. [
  28. URLQueryItem(
  29. name: "payload",
  30. value: queryPayload.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
  31. ]
  32. var urlComps = URLComponents(string: "logseq://shared?")!
  33. urlComps.queryItems = queryItems
  34. openURL(urlComps.url!)
  35. }
  36. fileprivate func createSharedFileUrl(_ url: URL?) -> URL? {
  37. let tempFilename = url!
  38. .lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
  39. let copyFileUrl = groupContainerUrl!.appendingPathComponent(tempFilename)
  40. try? Data(contentsOf: url!).write(to: copyFileUrl)
  41. return copyFileUrl
  42. }
  43. private func saveData(_ data: Data, fileExtension: String) -> URL? {
  44. let dateFormatter = DateFormatter()
  45. dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
  46. let filename = dateFormatter.string(from: Date()) + "." + fileExtension
  47. let copyFileUrl = groupContainerUrl!.appendingPathComponent(filename)
  48. do {
  49. try data.write(to: copyFileUrl)
  50. return copyFileUrl
  51. } catch {
  52. print(error.localizedDescription)
  53. return nil
  54. }
  55. }
  56. // Screenshots, shared images from some system App are passed as UIImage
  57. func saveUIImage(_ image: UIImage) -> URL? {
  58. let dateFormatter = DateFormatter()
  59. dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
  60. let filename = dateFormatter.string(from: Date()) + ".png"
  61. let copyFileUrl = groupContainerUrl!.appendingPathComponent(filename)
  62. do {
  63. try image.pngData()?.write(to: copyFileUrl)
  64. return copyFileUrl
  65. } catch {
  66. print(error.localizedDescription)
  67. return nil
  68. }
  69. }
  70. // Can be a path or a web URL
  71. fileprivate func handleTypeUrl(_ attachment: NSItemProvider)
  72. async throws -> SharedResource
  73. {
  74. let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil)
  75. let url = results as! URL?
  76. var res = SharedResource()
  77. if url!.isFileURL {
  78. res.name = url!.lastPathComponent
  79. res.ext = url!.pathExtension
  80. res.type = url!.pathExtensionAsMimeType()
  81. res.url = createSharedFileUrl(url)
  82. } else {
  83. res.name = url!.absoluteString
  84. res.type = "text/plain"
  85. }
  86. return res
  87. }
  88. // Some shares (notably system screenshots) can come through as a file URL item.
  89. fileprivate func handleTypeFileUrl(_ attachment: NSItemProvider)
  90. async throws -> SharedResource
  91. {
  92. let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil)
  93. let url = results as! URL?
  94. var res = SharedResource()
  95. if let url, url.isFileURL {
  96. res.name = url.lastPathComponent
  97. res.ext = url.pathExtension.lowercased()
  98. res.type = url.pathExtensionAsMimeType()
  99. res.url = createSharedFileUrl(url)
  100. } else if let url {
  101. res.name = url.absoluteString
  102. res.type = "text/plain"
  103. }
  104. return res
  105. }
  106. fileprivate func handleTypeText(_ attachment: NSItemProvider)
  107. async throws -> SharedResource?
  108. {
  109. let item = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil)
  110. self.sharedData.text = item as? String
  111. return nil
  112. }
  113. fileprivate func handleTypeMovie(_ attachment: NSItemProvider)
  114. async throws -> SharedResource
  115. {
  116. let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil)
  117. let url = results as! URL?
  118. let name = url!.lastPathComponent
  119. let ext = url!.pathExtension.lowercased()
  120. let type = url!.pathExtensionAsMimeType()
  121. let sharedUrl = createSharedFileUrl(url)
  122. let res = SharedResource(name: name, ext: ext, type: type, url: sharedUrl)
  123. return res
  124. }
  125. fileprivate func handleTypeImage(_ attachment: NSItemProvider)
  126. async throws -> SharedResource
  127. {
  128. let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil)
  129. var res = SharedResource()
  130. switch data {
  131. case let image as UIImage:
  132. res.url = self.saveUIImage(image)
  133. res.ext = "png"
  134. res.name = res.url?.lastPathComponent
  135. res.type = res.url?.pathExtensionAsMimeType()
  136. case let data as Data:
  137. let ext: String
  138. if attachment.hasItemConformingToTypeIdentifier(UTType.png.identifier) {
  139. ext = "png"
  140. } else if attachment.hasItemConformingToTypeIdentifier(UTType.jpeg.identifier) {
  141. ext = "jpg"
  142. } else if attachment.hasItemConformingToTypeIdentifier(UTType.heic.identifier) {
  143. ext = "heic"
  144. } else {
  145. ext = "png"
  146. }
  147. res.url = self.saveData(data, fileExtension: ext)
  148. res.ext = ext
  149. res.name = res.url?.lastPathComponent
  150. res.type = res.url?.pathExtensionAsMimeType()
  151. case let url as URL:
  152. res.name = url.lastPathComponent
  153. res.ext = url.pathExtension.lowercased()
  154. res.type = url.pathExtensionAsMimeType()
  155. res.url = self.createSharedFileUrl(url)
  156. default:
  157. print("Unexpected image data:", type(of: data))
  158. }
  159. return res
  160. }
  161. override public func viewDidLoad() {
  162. super.viewDidLoad()
  163. sharedData.empty()
  164. let inputItems = extensionContext?.inputItems as! [NSExtensionItem]
  165. Task {
  166. try await withThrowingTaskGroup(
  167. of: SharedResource?.self,
  168. body: { taskGroup in
  169. for extensionItem in inputItems {
  170. for attachment in extensionItem.attachments! {
  171. if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
  172. taskGroup.addTask {
  173. return try await self.handleTypeUrl(attachment)
  174. }
  175. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
  176. taskGroup.addTask {
  177. return try await self.handleTypeFileUrl(attachment)
  178. }
  179. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
  180. taskGroup.addTask {
  181. return try await self.handleTypeText(attachment)
  182. }
  183. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
  184. taskGroup.addTask {
  185. return try await self.handleTypeMovie(attachment)
  186. }
  187. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
  188. taskGroup.addTask {
  189. return try await self.handleTypeImage(attachment)
  190. }
  191. } else {
  192. // Useful for diagnosing shares that don't match the legacy kUTType checks.
  193. print("Unhandled attachment types:", attachment.registeredTypeIdentifiers)
  194. }
  195. }
  196. }
  197. for try await item in taskGroup {
  198. if let item = item {
  199. self.sharedData.resources.append(item)
  200. }
  201. }
  202. })
  203. self.sendData()
  204. }
  205. }
  206. @discardableResult
  207. @objc func openURL(_ url: URL) {
  208. var responder: UIResponder? = self
  209. while responder != nil {
  210. if let application = responder as? UIApplication {
  211. application.open(url, options: [:], completionHandler: nil)
  212. return
  213. }
  214. responder = responder?.next
  215. }
  216. }
  217. }
  218. extension URL {
  219. func pathExtensionAsMimeType() -> String? {
  220. let type = UTType(filenameExtension: self.pathExtension)
  221. return type?.preferredMIMEType
  222. }
  223. }