NCUtility.swift 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. //
  2. // NCUtility.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 25/06/18.
  6. // Copyright © 2018 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import UIKit
  24. import SVGKit
  25. import KTVHTTPCache
  26. import NextcloudKit
  27. import PDFKit
  28. import Accelerate
  29. import CoreMedia
  30. import Queuer
  31. import Photos
  32. import JGProgressHUD
  33. class NCUtility: NSObject {
  34. @objc static let shared: NCUtility = {
  35. let instance = NCUtility()
  36. return instance
  37. }()
  38. func convertSVGtoPNGWriteToUserData(svgUrlString: String, fileName: String?, width: CGFloat?, rewrite: Bool, account: String, closure: @escaping (String?) -> Void) {
  39. var fileNamePNG = ""
  40. guard let svgUrlString = svgUrlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
  41. let iconURL = URL(string: svgUrlString) else {
  42. return closure(nil)
  43. }
  44. if let fileName = fileName {
  45. fileNamePNG = fileName
  46. } else {
  47. fileNamePNG = iconURL.deletingPathExtension().lastPathComponent + ".png"
  48. }
  49. let imageNamePath = CCUtility.getDirectoryUserData() + "/" + fileNamePNG
  50. if !FileManager.default.fileExists(atPath: imageNamePath) || rewrite == true {
  51. NextcloudKit.shared.downloadContent(serverUrl: iconURL.absoluteString) { _, data, error in
  52. if error == .success && data != nil {
  53. if let image = UIImage(data: data!) {
  54. var newImage: UIImage = image
  55. if width != nil {
  56. let ratio = image.size.height / image.size.width
  57. let newSize = CGSize(width: width!, height: width! * ratio)
  58. let renderFormat = UIGraphicsImageRendererFormat.default()
  59. renderFormat.opaque = false
  60. let renderer = UIGraphicsImageRenderer(size: CGSize(width: newSize.width, height: newSize.height), format: renderFormat)
  61. newImage = renderer.image {
  62. _ in
  63. image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
  64. }
  65. }
  66. guard let pngImageData = newImage.pngData() else {
  67. return closure(nil)
  68. }
  69. try? pngImageData.write(to: URL(fileURLWithPath: imageNamePath))
  70. return closure(imageNamePath)
  71. } else {
  72. guard let svgImage: SVGKImage = SVGKImage(data: data) else {
  73. return closure(nil)
  74. }
  75. if width != nil {
  76. let scale = svgImage.size.height / svgImage.size.width
  77. svgImage.size = CGSize(width: width!, height: width! * scale)
  78. }
  79. guard let image: UIImage = svgImage.uiImage else {
  80. return closure(nil)
  81. }
  82. guard let pngImageData = image.pngData() else {
  83. return closure(nil)
  84. }
  85. try? pngImageData.write(to: URL(fileURLWithPath: imageNamePath))
  86. return closure(imageNamePath)
  87. }
  88. } else {
  89. return closure(nil)
  90. }
  91. }
  92. } else {
  93. return closure(imageNamePath)
  94. }
  95. }
  96. @objc func isSimulatorOrTestFlight() -> Bool {
  97. guard let path = Bundle.main.appStoreReceiptURL?.path else {
  98. return false
  99. }
  100. return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
  101. }
  102. @objc func isSimulator() -> Bool {
  103. guard let path = Bundle.main.appStoreReceiptURL?.path else {
  104. return false
  105. }
  106. return path.contains("CoreSimulator")
  107. }
  108. @objc func isRichDocument(_ metadata: tableMetadata) -> Bool {
  109. guard let mimeType = CCUtility.getMimeType(metadata.fileNameView) else {
  110. return false
  111. }
  112. guard let richdocumentsMimetypes = NCManageDatabase.shared.getCapabilitiesServerArray(account: metadata.account, elements: NCElementsJSON.shared.capabilitiesRichdocumentsMimetypes) else {
  113. return false
  114. }
  115. // contentype
  116. for richdocumentMimetype: String in richdocumentsMimetypes {
  117. if richdocumentMimetype.contains(metadata.contentType) || metadata.contentType == "text/plain" {
  118. return true
  119. }
  120. }
  121. // mimetype
  122. if richdocumentsMimetypes.count > 0 && mimeType.components(separatedBy: ".").count > 2 {
  123. let mimeTypeArray = mimeType.components(separatedBy: ".")
  124. let mimeType = mimeTypeArray[mimeTypeArray.count - 2] + "." + mimeTypeArray[mimeTypeArray.count - 1]
  125. for richdocumentMimetype: String in richdocumentsMimetypes {
  126. if richdocumentMimetype.contains(mimeType) {
  127. return true
  128. }
  129. }
  130. }
  131. return false
  132. }
  133. @objc func isDirectEditing(account: String, contentType: String) -> [String] {
  134. var editor: [String] = []
  135. guard let results = NCManageDatabase.shared.getDirectEditingEditors(account: account) else {
  136. return editor
  137. }
  138. for result: tableDirectEditingEditors in results {
  139. for mimetype in result.mimetypes {
  140. if mimetype == contentType {
  141. editor.append(result.editor)
  142. }
  143. // HARDCODE
  144. // https://github.com/nextcloud/text/issues/913
  145. if mimetype == "text/markdown" && contentType == "text/x-markdown" {
  146. editor.append(result.editor)
  147. }
  148. if contentType == "text/html" {
  149. editor.append(result.editor)
  150. }
  151. }
  152. for mimetype in result.optionalMimetypes {
  153. if mimetype == contentType {
  154. editor.append(result.editor)
  155. }
  156. }
  157. }
  158. // HARDCODE
  159. // if editor.count == 0 {
  160. // editor.append(NCGlobal.shared.editorText)
  161. // }
  162. return Array(Set(editor))
  163. }
  164. @objc func removeAllSettings() {
  165. URLCache.shared.memoryCapacity = 0
  166. URLCache.shared.diskCapacity = 0
  167. KTVHTTPCache.cacheDeleteAllCaches()
  168. NCManageDatabase.shared.clearDatabase(account: nil, removeAccount: true)
  169. CCUtility.removeGroupDirectoryProviderStorage()
  170. CCUtility.removeGroupLibraryDirectory()
  171. CCUtility.removeDocumentsDirectory()
  172. CCUtility.removeTemporaryDirectory()
  173. CCUtility.createDirectoryStandard()
  174. CCUtility.deleteAllChainStore()
  175. }
  176. @objc func permissionsContainsString(_ metadataPermissions: String, permissions: String) -> Bool {
  177. for char in permissions {
  178. if metadataPermissions.contains(char) == false {
  179. return false
  180. }
  181. }
  182. return true
  183. }
  184. @objc func getCustomUserAgentNCText() -> String {
  185. let userAgent: String = CCUtility.getUserAgent()
  186. if UIDevice.current.userInterfaceIdiom == .phone {
  187. // NOTE: Hardcoded (May 2022)
  188. // Tested for iPhone SE (1st), iOS 12; iPhone Pro Max, iOS 15.4
  189. // 605.1.15 = WebKit build version
  190. // 15E148 = frozen iOS build number according to: https://chromestatus.com/feature/4558585463832576
  191. return userAgent + " " + "AppleWebKit/605.1.15 Mobile/15E148"
  192. } else {
  193. return userAgent
  194. }
  195. }
  196. @objc func getCustomUserAgentOnlyOffice() -> String {
  197. let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString")!
  198. if UIDevice.current.userInterfaceIdiom == .pad {
  199. return "Mozilla/5.0 (iPad) Nextcloud-iOS/\(appVersion)"
  200. } else {
  201. return "Mozilla/5.0 (iPhone) Mobile Nextcloud-iOS/\(appVersion)"
  202. }
  203. }
  204. @objc func pdfThumbnail(url: URL, width: CGFloat = 240) -> UIImage? {
  205. guard let data = try? Data(contentsOf: url), let page = PDFDocument(data: data)?.page(at: 0) else {
  206. return nil
  207. }
  208. let pageSize = page.bounds(for: .mediaBox)
  209. let pdfScale = width / pageSize.width
  210. // Apply if you're displaying the thumbnail on screen
  211. let scale = UIScreen.main.scale * pdfScale
  212. let screenSize = CGSize(width: pageSize.width * scale, height: pageSize.height * scale)
  213. return page.thumbnail(of: screenSize, for: .mediaBox)
  214. }
  215. @objc func isQuickLookDisplayable(metadata: tableMetadata) -> Bool {
  216. return true
  217. }
  218. @objc func ocIdToFileId(ocId: String?) -> String? {
  219. guard let ocId = ocId else { return nil }
  220. let items = ocId.components(separatedBy: "oc")
  221. if items.count < 2 { return nil }
  222. guard let intFileId = Int(items[0]) else { return nil }
  223. return String(intFileId)
  224. }
  225. func getUserStatus(userIcon: String?, userStatus: String?, userMessage: String?) -> (onlineStatus: UIImage?, statusMessage: String, descriptionMessage: String) {
  226. var onlineStatus: UIImage?
  227. var statusMessage: String = ""
  228. var descriptionMessage: String = ""
  229. var messageUserDefined: String = ""
  230. if userStatus?.lowercased() == "online" {
  231. onlineStatus = UIImage(named: "circle_fill")!.image(color: UIColor(red: 103.0/255.0, green: 176.0/255.0, blue: 134.0/255.0, alpha: 1.0), size: 50)
  232. messageUserDefined = NSLocalizedString("_online_", comment: "")
  233. }
  234. if userStatus?.lowercased() == "away" {
  235. onlineStatus = UIImage(named: "userStatusAway")!.image(color: UIColor(red: 233.0/255.0, green: 166.0/255.0, blue: 75.0/255.0, alpha: 1.0), size: 50)
  236. messageUserDefined = NSLocalizedString("_away_", comment: "")
  237. }
  238. if userStatus?.lowercased() == "dnd" {
  239. onlineStatus = UIImage(named: "userStatusDnd")?.resizeImage(size: CGSize(width: 100, height: 100), isAspectRation: false)
  240. messageUserDefined = NSLocalizedString("_dnd_", comment: "")
  241. descriptionMessage = NSLocalizedString("_dnd_description_", comment: "")
  242. }
  243. if userStatus?.lowercased() == "offline" || userStatus?.lowercased() == "invisible" {
  244. onlineStatus = UIImage(named: "userStatusOffline")!.image(color: .black, size: 50)
  245. messageUserDefined = NSLocalizedString("_invisible_", comment: "")
  246. descriptionMessage = NSLocalizedString("_invisible_description_", comment: "")
  247. }
  248. if let userIcon = userIcon {
  249. statusMessage = userIcon + " "
  250. }
  251. if let userMessage = userMessage {
  252. statusMessage += userMessage
  253. }
  254. statusMessage = statusMessage.trimmingCharacters(in: .whitespaces)
  255. if statusMessage == "" {
  256. statusMessage = messageUserDefined
  257. }
  258. return(onlineStatus, statusMessage, descriptionMessage)
  259. }
  260. // MARK: -
  261. func extractFiles(from metadata: tableMetadata, viewController: UIViewController?, hud: JGProgressHUD, completition: @escaping (_ metadatas: [tableMetadata]) -> Void) {
  262. let chunckSize = CCUtility.getChunkSize() * 1000000
  263. var metadatas: [tableMetadata] = []
  264. let metadataSource = tableMetadata.init(value: metadata)
  265. guard !metadata.isExtractFile else { return completition([metadataSource]) }
  266. guard !metadataSource.assetLocalIdentifier.isEmpty else {
  267. let filePath = CCUtility.getDirectoryProviderStorageOcId(metadataSource.ocId, fileNameView: metadataSource.fileName)!
  268. metadataSource.size = NCUtilityFileSystem.shared.getFileSize(filePath: filePath)
  269. let results = NKCommon.shared.getInternalType(fileName: metadataSource.fileNameView, mimeType: metadataSource.contentType, directory: false)
  270. metadataSource.contentType = results.mimeType
  271. metadataSource.iconName = results.iconName
  272. metadataSource.classFile = results.classFile
  273. if let date = NCUtilityFileSystem.shared.getFileCreationDate(filePath: filePath) {
  274. metadataSource.creationDate = date
  275. }
  276. if let date = NCUtilityFileSystem.shared.getFileModificationDate(filePath: filePath) {
  277. metadataSource.date = date
  278. }
  279. metadataSource.chunk = chunckSize != 0 && metadata.size > chunckSize
  280. metadataSource.isExtractFile = true
  281. if let metadata = NCManageDatabase.shared.addMetadata(metadataSource) {
  282. metadatas.append(metadata)
  283. }
  284. return completition(metadatas)
  285. }
  286. extractImageVideoFromAssetLocalIdentifier(metadata: metadataSource, modifyMetadataForUpload: true, viewController: viewController, hud: hud) { metadata, fileNamePath, returnError in
  287. if let metadata = metadata, let fileNamePath = fileNamePath, !returnError {
  288. metadatas.append(metadata)
  289. let toPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  290. NCUtilityFileSystem.shared.moveFile(atPath: fileNamePath, toPath: toPath)
  291. } else {
  292. return completition(metadatas)
  293. }
  294. let fetchAssets = PHAsset.fetchAssets(withLocalIdentifiers: [metadataSource.assetLocalIdentifier], options: nil)
  295. if metadataSource.livePhoto, fetchAssets.count > 0 {
  296. NCUtility.shared.createMetadataLivePhotoFromMetadata(metadataSource, asset: fetchAssets.firstObject) { metadata in
  297. if let metadata = metadata, let metadata = NCManageDatabase.shared.addMetadata(metadata) {
  298. metadatas.append(metadata)
  299. }
  300. completition(metadatas)
  301. }
  302. } else {
  303. completition(metadatas)
  304. }
  305. }
  306. }
  307. func extractImageVideoFromAssetLocalIdentifier(metadata: tableMetadata, modifyMetadataForUpload: Bool, viewController: UIViewController?, hud: JGProgressHUD, completion: @escaping (_ metadata: tableMetadata?, _ fileNamePath: String?, _ error: Bool) -> ()) {
  308. var fileNamePath: String?
  309. let metadata = tableMetadata.init(value: metadata)
  310. let chunckSize = CCUtility.getChunkSize() * 1000000
  311. var compatibilityFormat: Bool = false
  312. let isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(metadata: metadata)
  313. func callCompletionWithError(_ error: Bool = true) {
  314. if error {
  315. completion(nil, nil, true)
  316. } else {
  317. var metadataReturn = metadata
  318. if modifyMetadataForUpload {
  319. metadata.chunk = chunckSize != 0 && metadata.size > chunckSize
  320. metadata.isExtractFile = true
  321. if let metadata = NCManageDatabase.shared.addMetadata(metadata) {
  322. metadataReturn = metadata
  323. }
  324. }
  325. completion(metadataReturn, fileNamePath, error)
  326. }
  327. }
  328. let fetchAssets = PHAsset.fetchAssets(withLocalIdentifiers: [metadata.assetLocalIdentifier], options: nil)
  329. guard fetchAssets.count > 0, let asset = fetchAssets.firstObject, let extensionAsset = (asset.value(forKey: "filename") as? NSString)?.pathExtension.uppercased() else { return callCompletionWithError() }
  330. let creationDate = asset.creationDate ?? Date()
  331. let modificationDate = asset.modificationDate ?? Date()
  332. if asset.mediaType == PHAssetMediaType.image && (extensionAsset == "HEIC" || extensionAsset == "DNG") && CCUtility.getFormatCompatibility() {
  333. let fileName = (metadata.fileNameView as NSString).deletingPathExtension + ".jpg"
  334. metadata.contentType = "image/jpeg"
  335. fileNamePath = NSTemporaryDirectory() + fileName
  336. metadata.fileNameView = fileName
  337. if !isDirectoryE2EE {
  338. metadata.fileName = fileName
  339. }
  340. compatibilityFormat = true
  341. } else {
  342. fileNamePath = NSTemporaryDirectory() + metadata.fileNameView
  343. }
  344. guard let fileNamePath = fileNamePath else { return callCompletionWithError() }
  345. if asset.mediaType == PHAssetMediaType.image {
  346. let options = PHImageRequestOptions()
  347. options.isNetworkAccessAllowed = true
  348. if compatibilityFormat {
  349. options.deliveryMode = .opportunistic
  350. } else {
  351. options.deliveryMode = .highQualityFormat
  352. }
  353. options.isSynchronous = true
  354. if extensionAsset == "DNG" {
  355. options.version = PHImageRequestOptionsVersion.original
  356. }
  357. options.progressHandler = { (progress, error, stop, info) in
  358. print(progress)
  359. if error != nil { return callCompletionWithError() }
  360. }
  361. PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { data, dataUI, orientation, info in
  362. guard var data = data else { return callCompletionWithError() }
  363. if compatibilityFormat {
  364. guard let ciImage = CIImage.init(data: data), let colorSpace = ciImage.colorSpace, let dataJPEG = CIContext().jpegRepresentation(of: ciImage, colorSpace: colorSpace) else { return callCompletionWithError() }
  365. data = dataJPEG
  366. }
  367. NCUtilityFileSystem.shared.deleteFile(filePath: fileNamePath)
  368. do {
  369. try data.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic)
  370. } catch { return callCompletionWithError() }
  371. metadata.creationDate = creationDate as NSDate
  372. metadata.date = modificationDate as NSDate
  373. metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  374. return callCompletionWithError(false)
  375. }
  376. } else if asset.mediaType == PHAssetMediaType.video {
  377. let options = PHVideoRequestOptions()
  378. options.isNetworkAccessAllowed = true
  379. options.version = PHVideoRequestOptionsVersion.current
  380. options.progressHandler = { (progress, error, stop, info) in
  381. print(progress)
  382. if error != nil { return callCompletionWithError() }
  383. }
  384. PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { asset, audioMix, info in
  385. if let asset = asset as? AVURLAsset {
  386. NCUtilityFileSystem.shared.deleteFile(filePath: fileNamePath)
  387. do {
  388. try FileManager.default.copyItem(at: asset.url, to: URL(fileURLWithPath: fileNamePath))
  389. metadata.creationDate = creationDate as NSDate
  390. metadata.date = modificationDate as NSDate
  391. metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  392. return callCompletionWithError(false)
  393. } catch { return callCompletionWithError() }
  394. } else if let asset = asset as? AVComposition, asset.tracks.count > 1, let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality), let viewController = viewController {
  395. DispatchQueue.main.async {
  396. hud.indicatorView = JGProgressHUDRingIndicatorView()
  397. if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
  398. indicatorView.ringWidth = 1.5
  399. }
  400. hud.textLabel.text = NSLocalizedString("_exporting_video_", comment: "")
  401. hud.show(in: viewController.view)
  402. hud.tapOnHUDViewBlock = { hud in
  403. exporter.cancelExport()
  404. }
  405. }
  406. exporter.outputURL = URL(fileURLWithPath: fileNamePath)
  407. exporter.outputFileType = AVFileType.mp4
  408. exporter.shouldOptimizeForNetworkUse = true
  409. exporter.exportAsynchronously {
  410. DispatchQueue.main.async { hud.dismiss() }
  411. if exporter.status == .completed {
  412. metadata.creationDate = creationDate as NSDate
  413. metadata.date = modificationDate as NSDate
  414. metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  415. return callCompletionWithError(false)
  416. } else { return callCompletionWithError() }
  417. }
  418. while exporter.status == AVAssetExportSession.Status.exporting || exporter.status == AVAssetExportSession.Status.waiting {
  419. hud.progress = exporter.progress
  420. }
  421. } else {
  422. return callCompletionWithError()
  423. }
  424. }
  425. } else {
  426. return callCompletionWithError()
  427. }
  428. }
  429. func createMetadataLivePhotoFromMetadata(_ metadata: tableMetadata, asset: PHAsset?, completion: @escaping (_ metadata: tableMetadata?) -> ()) {
  430. guard let asset = asset else { return completion(nil) }
  431. let options = PHLivePhotoRequestOptions()
  432. options.deliveryMode = PHImageRequestOptionsDeliveryMode.fastFormat
  433. options.isNetworkAccessAllowed = true
  434. let chunckSize = CCUtility.getChunkSize() * 1000000
  435. let ocId = NSUUID().uuidString
  436. let fileName = (metadata.fileName as NSString).deletingPathExtension + ".mov"
  437. let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)!
  438. PHImageManager.default().requestLivePhoto(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.default, options: options) { livePhoto, info in
  439. guard let livePhoto = livePhoto else { return completion(nil) }
  440. var videoResource: PHAssetResource?
  441. for resource in PHAssetResource.assetResources(for: livePhoto) {
  442. if resource.type == PHAssetResourceType.pairedVideo {
  443. videoResource = resource
  444. break
  445. }
  446. }
  447. guard let videoResource = videoResource else { return completion(nil) }
  448. NCUtilityFileSystem.shared.deleteFile(filePath: fileNamePath)
  449. PHAssetResourceManager.default().writeData(for: videoResource, toFile: URL(fileURLWithPath: fileNamePath), options: nil) { error in
  450. if error != nil { return completion(nil) }
  451. let metadataLivePhoto = NCManageDatabase.shared.createMetadata(account: metadata.account, user: metadata.user, userId: metadata.userId, fileName: fileName, fileNameView: fileName, ocId: ocId, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, url: "", contentType: "", isLivePhoto: true)
  452. metadataLivePhoto.classFile = NKCommon.typeClassFile.video.rawValue
  453. metadataLivePhoto.isExtractFile = true
  454. metadataLivePhoto.session = metadata.session
  455. metadataLivePhoto.sessionSelector = metadata.sessionSelector
  456. metadataLivePhoto.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  457. metadataLivePhoto.status = metadata.status
  458. metadataLivePhoto.chunk = chunckSize != 0 && metadata.size > chunckSize
  459. return completion(NCManageDatabase.shared.addMetadata(metadataLivePhoto))
  460. }
  461. }
  462. }
  463. func imageFromVideo(url: URL, at time: TimeInterval) -> UIImage? {
  464. let asset = AVURLAsset(url: url)
  465. let assetIG = AVAssetImageGenerator(asset: asset)
  466. assetIG.appliesPreferredTrackTransform = true
  467. assetIG.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels
  468. let cmTime = CMTime(seconds: time, preferredTimescale: 60)
  469. let thumbnailImageRef: CGImage
  470. do {
  471. thumbnailImageRef = try assetIG.copyCGImage(at: cmTime, actualTime: nil)
  472. } catch let error {
  473. print("Error: \(error)")
  474. return nil
  475. }
  476. return UIImage(cgImage: thumbnailImageRef)
  477. }
  478. func imageFromVideo(url: URL, at time: TimeInterval, completion: @escaping (UIImage?) -> Void) {
  479. DispatchQueue.global().async {
  480. let asset = AVURLAsset(url: url)
  481. let assetIG = AVAssetImageGenerator(asset: asset)
  482. assetIG.appliesPreferredTrackTransform = true
  483. assetIG.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels
  484. let cmTime = CMTime(seconds: time, preferredTimescale: 60)
  485. let thumbnailImageRef: CGImage
  486. do {
  487. thumbnailImageRef = try assetIG.copyCGImage(at: cmTime, actualTime: nil)
  488. } catch let error {
  489. print("Error: \(error)")
  490. return completion(nil)
  491. }
  492. DispatchQueue.main.async {
  493. completion(UIImage(cgImage: thumbnailImageRef))
  494. }
  495. }
  496. }
  497. func createImageFrom(fileNameView: String, ocId: String, etag: String, classFile: String) {
  498. var originalImage, scaleImagePreview, scaleImageIcon: UIImage?
  499. let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView)!
  500. let fileNamePathPreview = CCUtility.getDirectoryProviderStoragePreviewOcId(ocId, etag: etag)!
  501. let fileNamePathIcon = CCUtility.getDirectoryProviderStorageIconOcId(ocId, etag: etag)!
  502. if CCUtility.fileProviderStorageSize(ocId, fileNameView: fileNameView) > 0 && FileManager().fileExists(atPath: fileNamePathPreview) && FileManager().fileExists(atPath: fileNamePathIcon) { return }
  503. if classFile != NKCommon.typeClassFile.image.rawValue && classFile != NKCommon.typeClassFile.video.rawValue { return }
  504. if classFile == NKCommon.typeClassFile.image.rawValue {
  505. originalImage = UIImage(contentsOfFile: fileNamePath)
  506. scaleImagePreview = originalImage?.resizeImage(size: CGSize(width: NCGlobal.shared.sizePreview, height: NCGlobal.shared.sizePreview))
  507. scaleImageIcon = originalImage?.resizeImage(size: CGSize(width: NCGlobal.shared.sizeIcon, height: NCGlobal.shared.sizeIcon))
  508. try? scaleImagePreview?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathPreview))
  509. try? scaleImageIcon?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathIcon))
  510. } else if classFile == NKCommon.typeClassFile.video.rawValue {
  511. let videoPath = NSTemporaryDirectory()+"tempvideo.mp4"
  512. NCUtilityFileSystem.shared.linkItem(atPath: fileNamePath, toPath: videoPath)
  513. originalImage = imageFromVideo(url: URL(fileURLWithPath: videoPath), at: 0)
  514. try? originalImage?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathPreview))
  515. try? originalImage?.jpegData(compressionQuality: 0.7)?.write(to: URL(fileURLWithPath: fileNamePathIcon))
  516. }
  517. }
  518. @objc func getVersionApp(withBuild: Bool = true) -> String {
  519. if let dictionary = Bundle.main.infoDictionary {
  520. if let version = dictionary["CFBundleShortVersionString"], let build = dictionary["CFBundleVersion"] {
  521. if withBuild {
  522. return "\(version).\(build)"
  523. } else {
  524. return "\(version)"
  525. }
  526. }
  527. }
  528. return ""
  529. }
  530. func loadImage(named imageName: String, color: UIColor = NCBrandColor.shared.gray, size: CGFloat = 50, symbolConfiguration: Any? = nil) -> UIImage {
  531. var image: UIImage?
  532. // see https://stackoverflow.com/questions/71764255
  533. let sfSymbolName = imageName.replacingOccurrences(of: "_", with: ".")
  534. if let symbolConfiguration = symbolConfiguration {
  535. image = UIImage(systemName: sfSymbolName, withConfiguration: symbolConfiguration as? UIImage.Configuration)?.withTintColor(color, renderingMode: .alwaysOriginal)
  536. } else {
  537. image = UIImage(systemName: sfSymbolName)?.withTintColor(color, renderingMode: .alwaysOriginal)
  538. }
  539. if image == nil {
  540. image = UIImage(named: imageName)?.image(color: color, size: size)
  541. }
  542. if let image = image {
  543. return image
  544. }
  545. return UIImage(named: "file")!.image(color: color, size: size)
  546. }
  547. @objc func loadUserImage(for user: String, displayName: String?, userBaseUrl: NCUserBaseUrl) -> UIImage {
  548. let fileName = userBaseUrl.userBaseUrl + "-" + user + ".png"
  549. let localFilePath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
  550. if let localImage = UIImage(contentsOfFile: localFilePath) {
  551. return createAvatar(image: localImage, size: 30)
  552. } else if let loadedAvatar = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
  553. return loadedAvatar
  554. } else if let displayName = displayName, !displayName.isEmpty, let avatarImg = createAvatar(displayName: displayName, size: 30) {
  555. return avatarImg
  556. } else { return getDefaultUserIcon() }
  557. }
  558. func getDefaultUserIcon() -> UIImage {
  559. let config = UIImage.SymbolConfiguration(pointSize: 30)
  560. return NCUtility.shared.loadImage(named: "person.crop.circle", symbolConfiguration: config)
  561. }
  562. @objc func createAvatar(image: UIImage, size: CGFloat) -> UIImage {
  563. var avatarImage = image
  564. let rect = CGRect(x: 0, y: 0, width: size, height: size)
  565. UIGraphicsBeginImageContextWithOptions(rect.size, false, 3.0)
  566. UIBezierPath(roundedRect: rect, cornerRadius: rect.size.height).addClip()
  567. avatarImage.draw(in: rect)
  568. avatarImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
  569. UIGraphicsEndImageContext()
  570. return avatarImage
  571. }
  572. func createAvatar(displayName: String, size: CGFloat) -> UIImage? {
  573. guard let initials = displayName.uppercaseInitials else {
  574. return nil
  575. }
  576. let userColor = NCGlobal.shared.usernameToColor(displayName)
  577. let rect = CGRect(x: 0, y: 0, width: size, height: size)
  578. var avatarImage: UIImage?
  579. UIGraphicsBeginImageContextWithOptions(rect.size, false, 3.0)
  580. let context = UIGraphicsGetCurrentContext()
  581. UIBezierPath(roundedRect: rect, cornerRadius: rect.size.height).addClip()
  582. context?.setFillColor(userColor)
  583. context?.fill(rect)
  584. let textStyle = NSMutableParagraphStyle()
  585. textStyle.alignment = NSTextAlignment.center
  586. let lineHeight = UIFont.systemFont(ofSize: UIFont.systemFontSize).pointSize
  587. NSString(string: initials)
  588. .draw(
  589. in: CGRect(x: 0, y: (size - lineHeight) / 2, width: size, height: lineHeight),
  590. withAttributes: [NSAttributedString.Key.paragraphStyle: textStyle])
  591. avatarImage = UIGraphicsGetImageFromCurrentImageContext()
  592. UIGraphicsEndImageContext()
  593. return avatarImage
  594. }
  595. /*
  596. Facebook's comparison algorithm:
  597. */
  598. func compare(tolerance: Float, expected: Data, observed: Data) throws -> Bool {
  599. enum customError: Error {
  600. case unableToGetUIImageFromData
  601. case unableToGetCGImageFromData
  602. case unableToGetColorSpaceFromCGImage
  603. case imagesHasDifferentSizes
  604. case unableToInitializeContext
  605. }
  606. guard let expectedUIImage = UIImage(data: expected), let observedUIImage = UIImage(data: observed) else {
  607. throw customError.unableToGetUIImageFromData
  608. }
  609. guard let expectedCGImage = expectedUIImage.cgImage, let observedCGImage = observedUIImage.cgImage else {
  610. throw customError.unableToGetCGImageFromData
  611. }
  612. guard let expectedColorSpace = expectedCGImage.colorSpace, let observedColorSpace = observedCGImage.colorSpace else {
  613. throw customError.unableToGetColorSpaceFromCGImage
  614. }
  615. if expectedCGImage.width != observedCGImage.width || expectedCGImage.height != observedCGImage.height {
  616. throw customError.imagesHasDifferentSizes
  617. }
  618. let imageSize = CGSize(width: expectedCGImage.width, height: expectedCGImage.height)
  619. let numberOfPixels = Int(imageSize.width * imageSize.height)
  620. // Checking that our `UInt32` buffer has same number of bytes as image has.
  621. let bytesPerRow = min(expectedCGImage.bytesPerRow, observedCGImage.bytesPerRow)
  622. assert(MemoryLayout<UInt32>.stride == bytesPerRow / Int(imageSize.width))
  623. let expectedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
  624. let observedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
  625. let expectedPixelsRaw = UnsafeMutableRawPointer(expectedPixels)
  626. let observedPixelsRaw = UnsafeMutableRawPointer(observedPixels)
  627. let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
  628. guard let expectedContext = CGContext(data: expectedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
  629. bitsPerComponent: expectedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
  630. space: expectedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
  631. expectedPixels.deallocate()
  632. observedPixels.deallocate()
  633. throw customError.unableToInitializeContext
  634. }
  635. guard let observedContext = CGContext(data: observedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
  636. bitsPerComponent: observedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
  637. space: observedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
  638. expectedPixels.deallocate()
  639. observedPixels.deallocate()
  640. throw customError.unableToInitializeContext
  641. }
  642. expectedContext.draw(expectedCGImage, in: CGRect(origin: .zero, size: imageSize))
  643. observedContext.draw(observedCGImage, in: CGRect(origin: .zero, size: imageSize))
  644. let expectedBuffer = UnsafeBufferPointer(start: expectedPixels, count: numberOfPixels)
  645. let observedBuffer = UnsafeBufferPointer(start: observedPixels, count: numberOfPixels)
  646. var isEqual = true
  647. if tolerance == 0 {
  648. isEqual = expectedBuffer.elementsEqual(observedBuffer)
  649. } else {
  650. // Go through each pixel in turn and see if it is different
  651. var numDiffPixels = 0
  652. for pixel in 0 ..< numberOfPixels where expectedBuffer[pixel] != observedBuffer[pixel] {
  653. // If this pixel is different, increment the pixel diff count and see if we have hit our limit.
  654. numDiffPixels += 1
  655. let percentage = 100 * Float(numDiffPixels) / Float(numberOfPixels)
  656. if percentage > tolerance {
  657. isEqual = false
  658. break
  659. }
  660. }
  661. }
  662. expectedPixels.deallocate()
  663. observedPixels.deallocate()
  664. return isEqual
  665. }
  666. func stringFromTime(_ time: CMTime) -> String {
  667. let interval = Int(CMTimeGetSeconds(time))
  668. let seconds = interval % 60
  669. let minutes = (interval / 60) % 60
  670. let hours = (interval / 3600)
  671. if hours > 0 {
  672. return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
  673. } else {
  674. return String(format: "%02d:%02d", minutes, seconds)
  675. }
  676. }
  677. func colorNavigationController(_ navigationController: UINavigationController?, backgroundColor: UIColor, titleColor: UIColor, tintColor: UIColor?, withoutShadow: Bool) {
  678. let appearance = UINavigationBarAppearance()
  679. appearance.titleTextAttributes = [.foregroundColor: titleColor]
  680. appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
  681. if withoutShadow {
  682. appearance.shadowColor = .clear
  683. appearance.shadowImage = UIImage()
  684. }
  685. if let tintColor = tintColor {
  686. navigationController?.navigationBar.tintColor = tintColor
  687. }
  688. navigationController?.view.backgroundColor = backgroundColor
  689. navigationController?.navigationBar.barTintColor = titleColor
  690. navigationController?.navigationBar.standardAppearance = appearance
  691. navigationController?.navigationBar.compactAppearance = appearance
  692. navigationController?.navigationBar.scrollEdgeAppearance = appearance
  693. }
  694. func getEncondingDataType(data: Data) -> String.Encoding? {
  695. if let _ = String(data: data, encoding: .utf8) {
  696. return .utf8
  697. }
  698. if let _ = String(data: data, encoding: .ascii) {
  699. return .ascii
  700. }
  701. if let _ = String(data: data, encoding: .isoLatin1) {
  702. return .isoLatin1
  703. }
  704. if let _ = String(data: data, encoding: .isoLatin2) {
  705. return .isoLatin2
  706. }
  707. if let _ = String(data: data, encoding: .windowsCP1250) {
  708. return .windowsCP1250
  709. }
  710. if let _ = String(data: data, encoding: .windowsCP1251) {
  711. return .windowsCP1251
  712. }
  713. if let _ = String(data: data, encoding: .windowsCP1252) {
  714. return .windowsCP1252
  715. }
  716. if let _ = String(data: data, encoding: .windowsCP1253) {
  717. return .windowsCP1253
  718. }
  719. if let _ = String(data: data, encoding: .windowsCP1254) {
  720. return .windowsCP1254
  721. }
  722. if let _ = String(data: data, encoding: .macOSRoman) {
  723. return .macOSRoman
  724. }
  725. if let _ = String(data: data, encoding: .japaneseEUC) {
  726. return .japaneseEUC
  727. }
  728. if let _ = String(data: data, encoding: .nextstep) {
  729. return .nextstep
  730. }
  731. if let _ = String(data: data, encoding: .nonLossyASCII) {
  732. return .nonLossyASCII
  733. }
  734. if let _ = String(data: data, encoding: .shiftJIS) {
  735. return .shiftJIS
  736. }
  737. if let _ = String(data: data, encoding: .symbol) {
  738. return .symbol
  739. }
  740. if let _ = String(data: data, encoding: .unicode) {
  741. return .unicode
  742. }
  743. if let _ = String(data: data, encoding: .utf16) {
  744. return .utf16
  745. }
  746. if let _ = String(data: data, encoding: .utf16BigEndian) {
  747. return .utf16BigEndian
  748. }
  749. if let _ = String(data: data, encoding: .utf16LittleEndian) {
  750. return .utf16LittleEndian
  751. }
  752. if let _ = String(data: data, encoding: .utf32) {
  753. return .utf32
  754. }
  755. if let _ = String(data: data, encoding: .utf32BigEndian) {
  756. return .utf32BigEndian
  757. }
  758. if let _ = String(data: data, encoding: .utf32LittleEndian) {
  759. return .utf32LittleEndian
  760. }
  761. return nil
  762. }
  763. func SYSTEM_VERSION_LESS_THAN(version: String) -> Bool {
  764. return UIDevice.current.systemVersion.compare(version,
  765. options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
  766. }
  767. func getAvatarFromIconUrl(metadata: tableMetadata) -> String? {
  768. var ownerId: String?
  769. if metadata.iconUrl.contains("http") && metadata.iconUrl.contains("avatar") {
  770. let splitIconUrl = metadata.iconUrl.components(separatedBy: "/")
  771. var found:Bool = false
  772. for item in splitIconUrl {
  773. if found {
  774. ownerId = item
  775. break
  776. }
  777. if item == "avatar" { found = true}
  778. }
  779. }
  780. return ownerId
  781. }
  782. // https://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift
  783. func isValidEmail(_ email: String) -> Bool {
  784. let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
  785. let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
  786. return emailPred.evaluate(with: email)
  787. }
  788. func createFilePreviewImage(ocId: String, etag: String, fileNameView: String, classFile: String, status: Int, createPreviewMedia: Bool) -> UIImage? {
  789. var imagePreview: UIImage?
  790. let filePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView)!
  791. let iconImagePath = CCUtility.getDirectoryProviderStorageIconOcId(ocId, etag: etag)!
  792. if FileManager().fileExists(atPath: iconImagePath) {
  793. imagePreview = UIImage(contentsOfFile: iconImagePath)
  794. } else if !createPreviewMedia {
  795. return nil
  796. } else if createPreviewMedia && status >= NCGlobal.shared.metadataStatusNormal && classFile == NKCommon.typeClassFile.image.rawValue && FileManager().fileExists(atPath: filePath) {
  797. if let image = UIImage(contentsOfFile: filePath), let image = image.resizeImage(size: CGSize(width: NCGlobal.shared.sizeIcon, height: NCGlobal.shared.sizeIcon)), let data = image.jpegData(compressionQuality: 0.5) {
  798. do {
  799. try data.write(to: URL.init(fileURLWithPath: iconImagePath), options: .atomic)
  800. imagePreview = image
  801. } catch { }
  802. }
  803. } else if createPreviewMedia && status >= NCGlobal.shared.metadataStatusNormal && classFile == NKCommon.typeClassFile.video.rawValue && FileManager().fileExists(atPath: filePath) {
  804. if let image = NCUtility.shared.imageFromVideo(url: URL(fileURLWithPath: filePath), at: 0), let image = image.resizeImage(size: CGSize(width: NCGlobal.shared.sizeIcon, height: NCGlobal.shared.sizeIcon)), let data = image.jpegData(compressionQuality: 0.5) {
  805. do {
  806. try data.write(to: URL.init(fileURLWithPath: iconImagePath), options: .atomic)
  807. imagePreview = image
  808. } catch { }
  809. }
  810. }
  811. return imagePreview
  812. }
  813. @discardableResult
  814. func convertDataToImage(data: Data?, size:CGSize, fileNameToWrite: String?) -> UIImage? {
  815. guard let data = data else { return nil }
  816. var returnImage: UIImage?
  817. if let image = UIImage(data: data), let image = image.resizeImage(size: size) {
  818. returnImage = image
  819. } else if let image = SVGKImage(data: data) {
  820. image.size = size
  821. returnImage = image.uiImage
  822. } else {
  823. print("error")
  824. }
  825. if let fileName = fileNameToWrite, let image = returnImage {
  826. do {
  827. let fileNamePath: String = CCUtility.getDirectoryUserData() + "/" + fileName + ".png"
  828. try image.pngData()?.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic)
  829. } catch { }
  830. }
  831. return returnImage
  832. }
  833. func isDirectoryE2EE(serverUrl: String, userBase: NCUserBaseUrl) -> Bool {
  834. return isDirectoryE2EE(serverUrl: serverUrl, account: userBase.account, urlBase: userBase.urlBase, userId: userBase.userId)
  835. }
  836. func isDirectoryE2EE(file: NKFile) -> Bool {
  837. return isDirectoryE2EE(serverUrl: file.serverUrl, account: file.account, urlBase: file.urlBase, userId: file.userId)
  838. }
  839. @objc func isDirectoryE2EE(metadata: tableMetadata) -> Bool {
  840. return isDirectoryE2EE(serverUrl: metadata.serverUrl, account: metadata.account, urlBase: metadata.urlBase, userId: metadata.userId)
  841. }
  842. @objc func isDirectoryE2EE(serverUrl: String, account: String, urlBase: String, userId: String) -> Bool {
  843. if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId) || serverUrl == ".." { return false }
  844. if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", account, serverUrl)) {
  845. return directory.e2eEncrypted
  846. }
  847. return false
  848. }
  849. func createViewImageAndText(image: UIImage, title: String? = nil) -> UIView {
  850. let imageView = UIImageView()
  851. let titleView = UIView()
  852. let label = UILabel()
  853. if let title = title {
  854. label.text = title + " "
  855. } else {
  856. label.text = " "
  857. }
  858. label.sizeToFit()
  859. label.center = titleView.center
  860. label.textAlignment = NSTextAlignment.center
  861. imageView.image = image
  862. let imageAspect = (imageView.image?.size.width ?? 0) / (imageView.image?.size.height ?? 0)
  863. let imageX = label.frame.origin.x - label.frame.size.height * imageAspect
  864. let imageY = label.frame.origin.y
  865. let imageWidth = label.frame.size.height * imageAspect
  866. let imageHeight = label.frame.size.height
  867. if title != nil {
  868. imageView.frame = CGRect(x: imageX, y: imageY, width: imageWidth, height: imageHeight)
  869. titleView.addSubview(label)
  870. } else {
  871. imageView.frame = CGRect(x: imageX / 2, y: imageY, width: imageWidth, height: imageHeight)
  872. }
  873. imageView.contentMode = UIView.ContentMode.scaleAspectFit
  874. titleView.addSubview(imageView)
  875. titleView.sizeToFit()
  876. return titleView
  877. }
  878. }