FileUtility.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. @objc public class FileUtility: NSObject {
  23. @objc public static let appDataDirectory: URL? = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppGroup.groupId())
  24. @objc public static let appDocumentsDirectory: URL? = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
  25. /**
  26. Get total size and total free size of the device.
  27. - Returns:
  28. - totalSize: total size in bytes of device
  29. - totalFreeSize: total free size in bytes on device
  30. */
  31. public static func deviceSizeInBytes() -> (totalSize: Int64?, totalFreeSize: Int64?) {
  32. var size: Int64?
  33. var freeSize: Int64?
  34. if #available(iOS 11.0, *) {
  35. let homeDirectory: URL = URL(fileURLWithPath: NSHomeDirectory())
  36. if let systemResources = try? homeDirectory.resourceValues(forKeys: [.volumeTotalCapacityKey, .volumeAvailableCapacityForImportantUsageKey]) {
  37. size = systemResources.allValues[.volumeTotalCapacityKey] as? Int64
  38. freeSize = systemResources.allValues[.volumeAvailableCapacityForImportantUsageKey] as? Int64
  39. }
  40. }
  41. else {
  42. if let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last,
  43. let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: documentDirectory) {
  44. size = systemAttributes[FileAttributeKey.systemSize] as? Int64
  45. freeSize = systemAttributes[.systemFreeSize] as? Int64
  46. }
  47. }
  48. return (size, freeSize)
  49. }
  50. /**
  51. Get size of dictionary, including subdirectries.
  52. - Parameters:
  53. - pathUrl: root url to get size
  54. - size: total size of directory
  55. */
  56. public static func pathSizeInBytes(pathUrl: URL, size: inout Int64) {
  57. let fileManager = FileManager.default
  58. do {
  59. let resourceKeys: [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
  60. if let urls = fileManager.enumerator(at: pathUrl, includingPropertiesForKeys: resourceKeys) {
  61. for case let url as URL in urls {
  62. let resourceValues = try url.resourceValues(forKeys: Set(resourceKeys))
  63. DDLogVerbose("\(url.path) \(resourceValues.creationDate!) \(resourceValues.isDirectory!)")
  64. if let isDirectory = resourceValues.isDirectory,
  65. !isDirectory {
  66. if let fileSize = fileSizeInBytes(fileUrl: url) {
  67. size = size + fileSize
  68. }
  69. }
  70. }
  71. }
  72. }
  73. catch {
  74. DDLogError(error.localizedDescription)
  75. }
  76. }
  77. /**
  78. Get size of file.
  79. - Parameters:
  80. - fileUrl: url of file
  81. - Returns: file size in bytes
  82. */
  83. public static func fileSizeInBytes(fileUrl: URL) -> Int64? {
  84. let fileManager = FileManager.default
  85. do {
  86. let fileAttr = try fileManager.attributesOfItem(atPath: fileUrl.path)
  87. return fileAttr[FileAttributeKey.size] as? Int64
  88. }
  89. catch {
  90. DDLogError(error.localizedDescription)
  91. }
  92. return nil
  93. }
  94. @objc public static func getTemporaryFileName() -> String {
  95. var filename = ProcessInfo().globallyUniqueString
  96. let url = FileManager.default.temporaryDirectory
  97. var fileURL = url.appendingPathComponent(filename)
  98. while FileUtility.isExists(fileUrl: fileURL) {
  99. filename = ProcessInfo().globallyUniqueString
  100. fileURL = url.appendingPathComponent(filename)
  101. }
  102. return filename
  103. }
  104. @objc public static func getTemporarySendableFileName(base : String, directoryURL : URL, pathExtension : String? = nil) -> String {
  105. let filename = base + "-" + DateFormatter.getDateForWeb(Date())
  106. var newFilename : String?
  107. var fileURL = directoryURL.appendingPathComponent(filename)
  108. if pathExtension != nil {
  109. fileURL = fileURL.appendingPathExtension(pathExtension!)
  110. }
  111. var i = 0
  112. while FileUtility.isExists(fileUrl: fileURL) {
  113. newFilename = filename.appending("-\(i)")
  114. fileURL = directoryURL.appendingPathComponent(newFilename!)
  115. if pathExtension != nil {
  116. fileURL = fileURL.appendingPathExtension(pathExtension!)
  117. }
  118. i += 1
  119. }
  120. if newFilename != nil {
  121. return newFilename!
  122. }
  123. return filename
  124. }
  125. @objc public static func getTemporarySendableFileName(base : String) -> String {
  126. let url = FileManager.default.temporaryDirectory
  127. return getTemporarySendableFileName(base: base, directoryURL: url)
  128. }
  129. public static func isExists(fileUrl: URL?) -> Bool {
  130. guard let fileUrl = fileUrl else {
  131. return false;
  132. }
  133. let fileManager = FileManager.default
  134. return fileManager.fileExists(atPath: fileUrl.path)
  135. }
  136. @objc public static func dir(pathUrl: URL?) -> [String]? {
  137. guard let pathUrl = pathUrl else {
  138. return nil;
  139. }
  140. var items: [String]?
  141. let fileManager = FileManager.default
  142. if fileManager.fileExists(atPath: pathUrl.path) {
  143. items = try? fileManager.contentsOfDirectory(atPath: pathUrl.path)
  144. }
  145. return items
  146. }
  147. /**
  148. Delete file if exists.
  149. - Parameters:
  150. - fileUrl: File to delete
  151. */
  152. @objc public static func delete(fileUrl: URL?) {
  153. guard let fileUrl = fileUrl else {
  154. return;
  155. }
  156. let fileManager = FileManager.default
  157. if fileManager.fileExists(atPath: fileUrl.path) {
  158. try? fileManager.removeItem(atPath: fileUrl.path)
  159. }
  160. }
  161. public static func write(fileUrl: URL?, text: String) -> Bool {
  162. guard let fileUrl = fileUrl else {
  163. return false
  164. }
  165. return FileUtility.write(fileUrl: fileUrl, contents: text.data(using: .utf8))
  166. }
  167. public static func write(fileUrl: URL?, contents: Data? ) -> Bool {
  168. guard let fileUrl = fileUrl else {
  169. return false;
  170. }
  171. let fileManager = FileManager.default
  172. return fileManager.createFile(atPath: fileUrl.path, contents: contents, attributes: nil)
  173. }
  174. /**
  175. Append text to the end of file.
  176. - Parameters:
  177. - filePath: path to appending file
  178. - text: content to addend
  179. */
  180. public static func append(fileUrl:URL?, text: String) -> Bool {
  181. guard let fileUrl = fileUrl else {
  182. return false;
  183. }
  184. var result: Bool = false
  185. let fileManager = FileManager.default
  186. if fileManager.fileExists(atPath: fileUrl.path) {
  187. if let data = text.data(using: .utf8) {
  188. do {
  189. let fileHandle = try FileHandle(forWritingTo: fileUrl)
  190. fileHandle.seekToEndOfFile()
  191. fileHandle.write(data)
  192. fileHandle.closeFile()
  193. }
  194. catch {
  195. DDLogError(error.localizedDescription)
  196. }
  197. }
  198. result = true;
  199. }
  200. else {
  201. result = FileUtility.write(fileUrl: fileUrl, text: text)
  202. }
  203. return result
  204. }
  205. /**
  206. Log list directories and files and write log file to application documents folder.
  207. - Parameters:
  208. - path: Root directory to list objects
  209. - logFileName: Name of log file stored in application documents folder
  210. */
  211. @objc public static func logDirectoriesAndFiles(path: URL, logFileName: String) {
  212. let fileManager = FileManager.default
  213. do {
  214. DDLogInfo("Log files form \(path.path) into \(logFileName)")
  215. let resourceKeys: [URLResourceKey] = [.creationDateKey, .isDirectoryKey, .fileSizeKey]
  216. var logFiles: String = ""
  217. if let urls = fileManager.enumerator(at: path, includingPropertiesForKeys: resourceKeys) {
  218. for case let url as URL in urls {
  219. let resourceValues = try url.resourceValues(forKeys: Set(resourceKeys))
  220. let logFile: String = "\(url.path) \(ByteCountFormatter.string(fromByteCount: Int64(resourceValues.fileSize ?? 0), countStyle: ByteCountFormatter.CountStyle.file)) \(resourceValues.creationDate!) \(resourceValues.isDirectory!)"
  221. DDLogVerbose(logFile)
  222. logFiles += "\(logFile)\n"
  223. }
  224. }
  225. if let appDocuments = appDocumentsDirectory {
  226. let documentsPath = URL(fileURLWithPath: appDocuments.path)
  227. let filePath = documentsPath.appendingPathComponent(logFileName)
  228. do {
  229. if fileManager.fileExists(atPath: filePath.path) {
  230. try fileManager.removeItem(at: filePath)
  231. }
  232. if logFiles.count > 0 {
  233. try logFiles.write(to: filePath, atomically: false, encoding: .utf8)
  234. }
  235. }
  236. catch {
  237. DDLogError(error.localizedDescription)
  238. }
  239. }
  240. }
  241. catch {
  242. DDLogError(error.localizedDescription)
  243. }
  244. }
  245. }