NCUtility.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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 NextcloudKit
  25. import PDFKit
  26. import Accelerate
  27. import CoreMedia
  28. import Photos
  29. import Alamofire
  30. class NCUtility: NSObject {
  31. let utilityFileSystem = NCUtilityFileSystem()
  32. func isSimulatorOrTestFlight() -> Bool {
  33. guard let path = Bundle.main.appStoreReceiptURL?.path else {
  34. return false
  35. }
  36. return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
  37. }
  38. func isSimulator() -> Bool {
  39. guard let path = Bundle.main.appStoreReceiptURL?.path else {
  40. return false
  41. }
  42. return path.contains("CoreSimulator")
  43. }
  44. func isTypeFileRichDocument(_ metadata: tableMetadata) -> Bool {
  45. guard metadata.fileNameView != "." else { return false }
  46. let fileExtension = (metadata.fileNameView as NSString).pathExtension
  47. guard !fileExtension.isEmpty else { return false }
  48. guard let mimeType = UTType(tag: fileExtension.uppercased(), tagClass: .filenameExtension, conformingTo: nil)?.identifier else { return false }
  49. /// contentype
  50. if !NCGlobal.shared.capabilityRichDocumentsMimetypes.filter({ $0.contains(metadata.contentType) || $0.contains("text/plain") }).isEmpty {
  51. return true
  52. }
  53. /// mimetype
  54. if !NCGlobal.shared.capabilityRichDocumentsMimetypes.isEmpty && mimeType.components(separatedBy: ".").count > 2 {
  55. let mimeTypeArray = mimeType.components(separatedBy: ".")
  56. let mimeType = mimeTypeArray[mimeTypeArray.count - 2] + "." + mimeTypeArray[mimeTypeArray.count - 1]
  57. if !NCGlobal.shared.capabilityRichDocumentsMimetypes.filter({ $0.contains(mimeType) }).isEmpty {
  58. return true
  59. }
  60. }
  61. return false
  62. }
  63. func editorsDirectEditing(account: String, contentType: String) -> [String] {
  64. var editor: [String] = []
  65. guard let results = NCManageDatabase.shared.getDirectEditingEditors(account: account) else { return editor }
  66. for result: tableDirectEditingEditors in results {
  67. for mimetype in result.mimetypes {
  68. if mimetype == contentType {
  69. editor.append(result.editor)
  70. }
  71. // HARDCODE
  72. // https://github.com/nextcloud/text/issues/913
  73. if mimetype == "text/markdown" && contentType == "text/x-markdown" {
  74. editor.append(result.editor)
  75. }
  76. if contentType == "text/html" {
  77. editor.append(result.editor)
  78. }
  79. }
  80. for mimetype in result.optionalMimetypes {
  81. if mimetype == contentType {
  82. editor.append(result.editor)
  83. }
  84. }
  85. }
  86. return Array(Set(editor))
  87. }
  88. func permissionsContainsString(_ metadataPermissions: String, permissions: String) -> Bool {
  89. for char in permissions {
  90. if metadataPermissions.contains(char) == false {
  91. return false
  92. }
  93. }
  94. return true
  95. }
  96. func getCustomUserAgentNCText() -> String {
  97. if UIDevice.current.userInterfaceIdiom == .phone {
  98. // NOTE: Hardcoded (May 2022)
  99. // Tested for iPhone SE (1st), iOS 12 iPhone Pro Max, iOS 15.4
  100. // 605.1.15 = WebKit build version
  101. // 15E148 = frozen iOS build number according to: https://chromestatus.com/feature/4558585463832576
  102. return userAgent + " " + "AppleWebKit/605.1.15 Mobile/15E148"
  103. } else {
  104. return userAgent
  105. }
  106. }
  107. func getCustomUserAgentOnlyOffice() -> String {
  108. let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString")!
  109. if UIDevice.current.userInterfaceIdiom == .pad {
  110. return "Mozilla/5.0 (iPad) Nextcloud-iOS/\(appVersion)"
  111. } else {
  112. return "Mozilla/5.0 (iPhone) Mobile Nextcloud-iOS/\(appVersion)"
  113. }
  114. }
  115. func isQuickLookDisplayable(metadata: tableMetadata) -> Bool {
  116. return true
  117. }
  118. func ocIdToFileId(ocId: String?) -> String? {
  119. guard let ocId = ocId else { return nil }
  120. let items = ocId.components(separatedBy: "oc")
  121. if items.count < 2 { return nil }
  122. guard let intFileId = Int(items[0]) else { return nil }
  123. return String(intFileId)
  124. }
  125. @objc func getVersionApp(withBuild: Bool = true) -> String {
  126. if let dictionary = Bundle.main.infoDictionary {
  127. if let version = dictionary["CFBundleShortVersionString"], let build = dictionary["CFBundleVersion"] {
  128. if withBuild {
  129. return "\(version).\(build)"
  130. } else {
  131. return "\(version)"
  132. }
  133. }
  134. }
  135. return ""
  136. }
  137. /*
  138. Facebook's comparison algorithm:
  139. */
  140. func compare(tolerance: Float, expected: Data, observed: Data) throws -> Bool {
  141. enum customError: Error {
  142. case unableToGetUIImageFromData
  143. case unableToGetCGImageFromData
  144. case unableToGetColorSpaceFromCGImage
  145. case imagesHasDifferentSizes
  146. case unableToInitializeContext
  147. }
  148. guard let expectedUIImage = UIImage(data: expected), let observedUIImage = UIImage(data: observed) else {
  149. throw customError.unableToGetUIImageFromData
  150. }
  151. guard let expectedCGImage = expectedUIImage.cgImage, let observedCGImage = observedUIImage.cgImage else {
  152. throw customError.unableToGetCGImageFromData
  153. }
  154. guard let expectedColorSpace = expectedCGImage.colorSpace, let observedColorSpace = observedCGImage.colorSpace else {
  155. throw customError.unableToGetColorSpaceFromCGImage
  156. }
  157. if expectedCGImage.width != observedCGImage.width || expectedCGImage.height != observedCGImage.height {
  158. throw customError.imagesHasDifferentSizes
  159. }
  160. let imageSize = CGSize(width: expectedCGImage.width, height: expectedCGImage.height)
  161. let numberOfPixels = Int(imageSize.width * imageSize.height)
  162. // Checking that our `UInt32` buffer has same number of bytes as image has.
  163. let bytesPerRow = min(expectedCGImage.bytesPerRow, observedCGImage.bytesPerRow)
  164. assert(MemoryLayout<UInt32>.stride == bytesPerRow / Int(imageSize.width))
  165. let expectedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
  166. let observedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
  167. let expectedPixelsRaw = UnsafeMutableRawPointer(expectedPixels)
  168. let observedPixelsRaw = UnsafeMutableRawPointer(observedPixels)
  169. let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
  170. guard let expectedContext = CGContext(data: expectedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
  171. bitsPerComponent: expectedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
  172. space: expectedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
  173. expectedPixels.deallocate()
  174. observedPixels.deallocate()
  175. throw customError.unableToInitializeContext
  176. }
  177. guard let observedContext = CGContext(data: observedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
  178. bitsPerComponent: observedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
  179. space: observedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
  180. expectedPixels.deallocate()
  181. observedPixels.deallocate()
  182. throw customError.unableToInitializeContext
  183. }
  184. expectedContext.draw(expectedCGImage, in: CGRect(origin: .zero, size: imageSize))
  185. observedContext.draw(observedCGImage, in: CGRect(origin: .zero, size: imageSize))
  186. let expectedBuffer = UnsafeBufferPointer(start: expectedPixels, count: numberOfPixels)
  187. let observedBuffer = UnsafeBufferPointer(start: observedPixels, count: numberOfPixels)
  188. var isEqual = true
  189. if tolerance == 0 {
  190. isEqual = expectedBuffer.elementsEqual(observedBuffer)
  191. } else {
  192. // Go through each pixel in turn and see if it is different
  193. var numDiffPixels = 0
  194. for pixel in 0 ..< numberOfPixels where expectedBuffer[pixel] != observedBuffer[pixel] {
  195. // If this pixel is different, increment the pixel diff count and see if we have hit our limit.
  196. numDiffPixels += 1
  197. let percentage = 100 * Float(numDiffPixels) / Float(numberOfPixels)
  198. if percentage > tolerance {
  199. isEqual = false
  200. break
  201. }
  202. }
  203. }
  204. expectedPixels.deallocate()
  205. observedPixels.deallocate()
  206. return isEqual
  207. }
  208. func getLocation(latitude: Double, longitude: Double, completion: @escaping (String?) -> Void) {
  209. let geocoder = CLGeocoder()
  210. let llocation = CLLocation(latitude: latitude, longitude: longitude)
  211. if let location = NCManageDatabase.shared.getLocationFromLatAndLong(latitude: latitude, longitude: longitude) {
  212. completion(location)
  213. } else {
  214. geocoder.reverseGeocodeLocation(llocation) { placemarks, error in
  215. if error == nil, let placemark = placemarks?.first {
  216. let locationComponents: [String] = [placemark.name, placemark.locality, placemark.country]
  217. .compactMap {$0}
  218. let location = locationComponents.joined(separator: ", ")
  219. NCManageDatabase.shared.addGeocoderLocation(location, latitude: latitude, longitude: longitude)
  220. completion(location)
  221. }
  222. }
  223. }
  224. }
  225. // https://stackoverflow.com/questions/5887248/ios-app-maximum-memory-budget/19692719#19692719
  226. // https://stackoverflow.com/questions/27556807/swift-pointer-problems-with-mach-task-basic-info/27559770#27559770
  227. func getMemoryUsedAndDeviceTotalInMegabytes() -> (Float, Float) {
  228. var usedmegabytes: Float = 0
  229. let totalbytes = Float(ProcessInfo.processInfo.physicalMemory)
  230. let totalmegabytes = totalbytes / 1024.0 / 1024.0
  231. var info = mach_task_basic_info()
  232. var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
  233. let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
  234. $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
  235. task_info(
  236. mach_task_self_,
  237. task_flavor_t(MACH_TASK_BASIC_INFO),
  238. $0,
  239. &count
  240. )
  241. }
  242. }
  243. if kerr == KERN_SUCCESS {
  244. let usedbytes: Float = Float(info.resident_size)
  245. usedmegabytes = usedbytes / 1024.0 / 1024.0
  246. }
  247. return (usedmegabytes, totalmegabytes)
  248. }
  249. func removeForbiddenCharacters(_ fileName: String) -> String {
  250. var fileName = fileName
  251. for character in NCGlobal.shared.forbiddenCharacters {
  252. fileName = fileName.replacingOccurrences(of: character, with: "")
  253. }
  254. return fileName
  255. }
  256. }